|
| 1 | +# Guide |
| 2 | + |
| 3 | +This will guide you through the basic features of `pwdlib`. |
| 4 | + |
| 5 | +## Password hashing |
| 6 | + |
| 7 | +To manage password hashes, `pwdlib` exposes the [`PasswordHash`](./reference/pwdlib.md#pwdlib.PasswordHash) class. The difference with a "plain" hash algorithm helper is that it supports **multiple algorithms**. Why it's useful? |
| 8 | + |
| 9 | +Currently, the safest and recommended algorithm for password hashing is [Argon2](https://en.wikipedia.org/wiki/Argon2). But it's probable that in the coming years, a new and even safer algorithm emerges. Thanks to a wrapper like [`PasswordHash`](./reference/pwdlib.md#pwdlib.PasswordHash), we'll be able to verify hash generated with the older algorithm while upgrading them to the newer one. |
| 10 | + |
| 11 | +This is how you create an instance of [`PasswordHash`](./reference/pwdlib.md#pwdlib.PasswordHash) with Argon2 and Bcrypt support: |
| 12 | + |
| 13 | +```py |
| 14 | +from pwdlib import PasswordHash, exceptions |
| 15 | +from pwdlib.hashers.argon2 import Argon2Hasher |
| 16 | +from pwdlib.hashers.bcrypt import BcryptHasher |
| 17 | + |
| 18 | +password_hash = PasswordHash(( |
| 19 | + Argon2Hasher(), |
| 20 | + BcryptHasher(), |
| 21 | +)) |
| 22 | +``` |
| 23 | + |
| 24 | +[`PasswordHash`](./reference/pwdlib.md#pwdlib.PasswordHash) expects a tuple (or list) of hashing algorithms. |
| 25 | + |
| 26 | +!!! warning "The corresponding dependencies must be installed" |
| 27 | + |
| 28 | + Hashing algorithms are available through third-party implementations. Therefore, you should install the extra for all the algorithms you want to use. |
| 29 | + |
| 30 | + **For Argon2** |
| 31 | + |
| 32 | + ```sh |
| 33 | + pip install 'pwdlib[argon2]' |
| 34 | + ``` |
| 35 | + |
| 36 | + **For Bcrypt** |
| 37 | + |
| 38 | + ```sh |
| 39 | + pip install 'pwdlib[bcrypt]' |
| 40 | + ``` |
| 41 | + |
| 42 | + **Both!** |
| 43 | + |
| 44 | + ```sh |
| 45 | + pip install 'pwdlib[argon2,bcrypt]' |
| 46 | + ``` |
| 47 | + |
| 48 | +The **first** algorithm in the list is considered the **current algorithm**. New and updated hashes will use this algorithm. |
| 49 | + |
| 50 | +!!! tip "I don't know what to choose!" |
| 51 | + |
| 52 | + If you start a new application, you can instantiate [`PasswordHash`](./reference/pwdlib.md#pwdlib.PasswordHash) using the [`recommended`](./reference/pwdlib.md#pwdlib.PasswordHash.recommended) class method. It'll use the Argon2 algorithm under the hood. |
| 53 | + |
| 54 | + ```py |
| 55 | + password_hash = PasswordHash.recommended() |
| 56 | + ``` |
| 57 | + |
| 58 | +### Hash a password |
| 59 | + |
| 60 | +Once you have a [`PasswordHash`](./reference/pwdlib.md#pwdlib.PasswordHash) instance, you can [`hash`](./reference/pwdlib.md#pwdlib.PasswordHash.hash) a user's password: |
| 61 | + |
| 62 | +```py |
| 63 | +hash = password_hash.hash("herminetincture") |
| 64 | +``` |
| 65 | + |
| 66 | +The resulting string is a hash generated from the **current algorithm**, which you can safely store in your database. |
| 67 | + |
| 68 | +!!! question "Where is the salt?" |
| 69 | + |
| 70 | + If you know about password hashing algorithms, you probably know that *salting* the password is an important security feature. Back in the days, it was common to store the salt separately from the hash. Nowadays, most algorithms have a specific structure allowing them to store algorithm parameters and salt along with the hash in one string. |
| 71 | + |
| 72 | + For example, here is an Argon2 hash: |
| 73 | + |
| 74 | + ``` |
| 75 | + $argon2id$v=19$m=65536,t=3,p=4$ugY9gvgMUycvF7hrnoi8oQ$uqcZOh96YysaG+s3A+RcZIccgaiQsynxfBlqUNxeRT4 |
| 76 | + ``` |
| 77 | + |
| 78 | +### Verify a password |
| 79 | + |
| 80 | +When you want to verify a password corresponds to a hash, use the [`verify`](./reference/pwdlib.md#pwdlib.PasswordHash.verify) method: |
| 81 | + |
| 82 | +```py |
| 83 | +valid = password_hash.verify(hash, "herminetincture") |
| 84 | +``` |
| 85 | + |
| 86 | +Under the hood, it'll check against all the enabled algorithms. It means that if older users still have a hash with a legacy algorithm, we'll still be able to verify it. |
| 87 | + |
| 88 | +The resulting boolean indicates you if the hash corresponds to this password or not. |
| 89 | + |
| 90 | +However, in most cases, you probably want to use the method we describe below. |
| 91 | + |
| 92 | +### Verify and update a password |
| 93 | + |
| 94 | +As we said, [`verify`](./reference/pwdlib.md#pwdlib.PasswordHash.verify) checks the hash against all the enabled algorithms. However, it's generally a good idea to take this chance to update the hash to the more secure current algorithm. |
| 95 | + |
| 96 | +This is the purpose of the [`verify_and_update`](./reference/pwdlib.md#pwdlib.PasswordHash.verify_and_update) method: it verifies the password against a hash and, if it's valid and using an outdated algorithm, will hash it again using the current algorithm. |
| 97 | + |
| 98 | + |
| 99 | +```py |
| 100 | +valid, updated_hash = password_hash.verify_and_update(hash, "herminetincture") |
| 101 | +``` |
| 102 | + |
| 103 | +If the hash needs to be updated, `updated_hash` will be a string. Otherwise, it's `None`. Then, don't forget to update it in your database. |
| 104 | + |
| 105 | +It's worth to note that the hash is also upgraded if the **settings of the algorithm** has been changed, like the time or memory cost. |
0 commit comments