diff --git a/docs/header_names/allowed_framework_names.adoc b/docs/header_names/allowed_framework_names.adoc index 8de155f86a..9a87620dbb 100644 --- a/docs/header_names/allowed_framework_names.adoc +++ b/docs/header_names/allowed_framework_names.adoc @@ -76,6 +76,8 @@ // Python * aiohttp * Amazon DynamoDB +* Argon2-cffi +* Bcrypt * Cryptodome * Django * Django Templates @@ -98,6 +100,7 @@ * Python Standard Library * PyYAML * Requests +* Scrypt * SignXML * SQLAlchemy * ssl @@ -132,4 +135,4 @@ // Go * Go Standard Library // Kubernetes -* Helm \ No newline at end of file +* Helm diff --git a/rules/S5344/common/extra-mile/argon-cli.adoc b/rules/S5344/common/extra-mile/argon-cli.adoc new file mode 100644 index 0000000000..d679efc8c9 --- /dev/null +++ b/rules/S5344/common/extra-mile/argon-cli.adoc @@ -0,0 +1,13 @@ +==== Selecting safe custom parameters for Argon2 + +To determine which one is the most appropriate for your application, you can use +the argon2 CLI, for example with OWASP's first recommendation: + +[source,shell] +---- +$ pip install argon2 +$ python -m argon2 -t 1 -m 47104 -p 1 -l 32 +---- + +https://argon2-cffi.readthedocs.io/en/stable/api.html#module-argon2.profiles[Learn more here]. + diff --git a/rules/S5344/common/extra-mile/peppering.adoc b/rules/S5344/common/extra-mile/peppering.adoc new file mode 100644 index 0000000000..2a5abaf635 --- /dev/null +++ b/rules/S5344/common/extra-mile/peppering.adoc @@ -0,0 +1,9 @@ +==== Pepper + +In a defense-in-depth security approach, **peppering** can also be used. This is +a security technique where an external secret value is added to a password +before it is hashed. + +This makes it more difficult for an attacker to crack the hashed passwords, as +they would need to know the secret value to generate the correct hash. + +https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#peppering[Learn more here]. + diff --git a/rules/S5344/common/fix/argon-parameters.adoc b/rules/S5344/common/fix/argon-parameters.adoc new file mode 100644 index 0000000000..d5d9694f05 --- /dev/null +++ b/rules/S5344/common/fix/argon-parameters.adoc @@ -0,0 +1,55 @@ +==== Select the correct Argon2 parameters + +In general, the default values of the Argon2 library are considered safe. If you +need to change the parameters, you should note the following: + +First, Argon2 comes in three forms: Argon2i, Argon2d and Argon2id. +Argon2i is optimized for hashing passwords and uses data-independent memory +access. Argon2d is faster and uses data-dependent memory access, making it +suitable for applications where there is no risk of side-channel attacks. + +Argon2id is a mixture of Argon2i and Argon2d and is recommended for most applications. + +Argon2id has three different parameters that can be configured: the basic +minimum memory size (m), the minimum number of iterations (t) and the degree of +parallelism (p). + +The higher the values of m, t and p result in safer hashes, but come at the cost of higher +resource usage. There exist general recommendations that balance security and speed in an +optimal way. + +Hashes should be at least 32 bytes long and salts should be at least 16 bytes long. + +Next, the recommended parameters for Argon2id are: + +[options="header",cols="a,a,a,a"] +|=== +|Recommendation type |Time Cost |Memory Cost |Parallelism +|Argon2 Creators +|1 +|2097152 (2 GiB) +|4 +|Argon2 Creators +|3 +|65536 (64 MiB) +|4 +|OWASP +|1 +|47104 (46 MiB) +|1 +|OWASP +|2 +|19456 (19 MiB) +|1 +|OWASP +|3 +|12288 (12 MiB) +|1 +|OWASP +|4 +|9216 (9 MiB) +|1 +|OWASP +|5 +|7168 (7 MiB) +|1 +|=== + diff --git a/rules/S5344/common/fix/bcrypt-parameters.adoc b/rules/S5344/common/fix/bcrypt-parameters.adoc new file mode 100644 index 0000000000..86107fb471 --- /dev/null +++ b/rules/S5344/common/fix/bcrypt-parameters.adoc @@ -0,0 +1,12 @@ +==== Select the correct Bcrypt parameters + +When bcrypt's hashing function is used, it is important to select a round count +that is high enough to make the function slow enough to prevent brute force: +More than 12 rounds. + +For bcrypt's key derivation function, the number of rounds should likewise be +high enough to make the function slow enough to prevent brute force: More than +4096 rounds `+(2**12)+`. + +This number is not the same coefficient as the first one because it uses +a different algorithm. + diff --git a/rules/S5344/common/fix/password-hashing.adoc b/rules/S5344/common/fix/password-hashing.adoc index f1ec26a1e5..041237aaeb 100644 --- a/rules/S5344/common/fix/password-hashing.adoc +++ b/rules/S5344/common/fix/password-hashing.adoc @@ -1,11 +1,26 @@ -==== Use specific password hashing algorithms +==== Use secure password hashing algorithms -In general, rely on an algorithm with no known weaknesses, and rule out the -others, such as MD5 or SHA-1. +In general, you should rely on an algorithm that has no known security +vulnerabilities. The MD5 and SHA-1 algorithms should not be used. -While considered strong for some use cases, some algorithms, like SHA-family -functions, are too fast to compute and therefore susceptible to brute force -attacks, especially with attack-dedicated hardware. + -Modern, slow, password-hashing algorithms such as *bcrypt*, *PBKDF2* or *argon2* -are recommended. +Some algorithms, such as the SHA family functions, are considered strong for +some use cases, but are too fast in computation and therefore vulnerable to +brute force attacks, especially with bruteforce-attack-oriented hardware. +To protect passwords, it is therefore important to choose modern, slow +password-hashing algorithms. The following algorithms are, in order of strength, +the most secure password hashing algorithms to date: + +. Argon2 +. scrypt +. bcrypt +. PBKDF2 + +Argon2 should be the best choice, and others should be used when the previous +one is not available. For systems that must use FIPS-140-certified algorithms, +PBKDF2 should be used. + +Whenever possible, choose the strongest algorithm available. If the algorithm +currently used by your system should be upgraded, OWASP documents possible +upgrade methods here: +https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#upgrading-legacy-hashes[Upgrading Legacy Hashes]. diff --git a/rules/S5344/common/fix/pbkdf2-parameters.adoc b/rules/S5344/common/fix/pbkdf2-parameters.adoc new file mode 100644 index 0000000000..5cd897077d --- /dev/null +++ b/rules/S5344/common/fix/pbkdf2-parameters.adoc @@ -0,0 +1,15 @@ +==== Select the correct PBKDF2 parameters + +If PBKDF2 must be used, be aware that default values might not be considered +secure. + +Depending on the algorithm used, the number of iterations should be adjusted to +ensure that the derived key is secure. The following are the recommended number +of iterations for PBKDF2: + +* PBKDF2-HMAC-SHA1: 1,300,000 iterations +* PBKDF2-HMAC-SHA256: 600,000 iterations +* PBKDF2-HMAC-SHA512: 210,000 iterations + +Note that PBKDF2-HMAC-SHA256 is recommended by NIST. + +Iterations are also called "rounds" depending on the library used. + diff --git a/rules/S5344/common/fix/scrypt-parameters.adoc b/rules/S5344/common/fix/scrypt-parameters.adoc new file mode 100644 index 0000000000..6fc268b1fb --- /dev/null +++ b/rules/S5344/common/fix/scrypt-parameters.adoc @@ -0,0 +1,32 @@ +==== Select the correct Scrypt parameters + +If scrypt must be used, the default values of scrypt are considered secure. + +Like Argon2id, scrypt has three different parameters that can be configured. N is the CPU/memory cost parameter and must be a power of two. r is the block size and p is the parallelization factor. + +All three parameters affect the memory and CPU usage of the algorithm. +Higher values of N, r and p result in safer hashes, but come at the cost of higher resource usage. + +For scrypt, OWASP recommends to have a hash length of at least 64 bytes, and to set N, p and r to the values of one of the following rows: + +[options="header",cols="a,a,a"] +|=== +|N (cost parameter) |p (parallelization factor) |r (block size) +|2^17^ (`1 << 17`) +|1 +|8 +|2^16^ (`1 << 16`) +|2 +|8 +|2^15^ (`1 << 15`) +|3 +|8 +|2^14^ (`1 << 14`) +|5 +|8 +|2^13^ (`1 << 13`) +|10 +|8 +|=== + +Every row provides the same level of defense. They only differ in the amount of CPU and RAM used: the top row has low CPU usage and high memory usage, while the bottom row has high CPU usage and low memory usage. \ No newline at end of file diff --git a/rules/S5344/common/pitfalls/pre-hashing.adoc b/rules/S5344/common/pitfalls/pre-hashing.adoc new file mode 100644 index 0000000000..fcdb1b8ac0 --- /dev/null +++ b/rules/S5344/common/pitfalls/pre-hashing.adoc @@ -0,0 +1,13 @@ +==== Pre-hashing passwords + +As bcrypt has a maximum length input length of 72 bytes for most +implementations, some developers may be tempted to pre-hash the password with a +stronger algorithm before hashing it with bcrypt. + +Pre-hashing passwords with bcrypt is not recommended as it can lead to +a specific range of issues. Using a strong salt and a high number of rounds is +enough to protect the password. + +More information about this can be found here: +https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pre-hashing-passwords-with-bcrypt[Pre-hashing Passwords with Bcrypt]. + diff --git a/rules/S5344/java/rule.adoc b/rules/S5344/java/rule.adoc index 51bb92e132..cd62d8fad2 100644 --- a/rules/S5344/java/rule.adoc +++ b/rules/S5344/java/rule.adoc @@ -50,6 +50,11 @@ adaptative, and automatically implements a salt. include::../common/fix/plaintext-password.adoc[] +=== Pitfalls + +include::../common/pitfalls/pre-hashing.adoc[] + + == Resources === Documentation diff --git a/rules/S5344/python/highlighting.adoc b/rules/S5344/python/highlighting.adoc new file mode 100644 index 0000000000..ead711d952 --- /dev/null +++ b/rules/S5344/python/highlighting.adoc @@ -0,0 +1,9 @@ +=== Highlighting + +For hashlib: + +* Primary highlight on the incorrect hashing parameter(s) + +For Django: + +* Primary highlight on the hashing algorithm that caused the issue diff --git a/rules/S5344/python/how-to-fix-it/argon2.adoc b/rules/S5344/python/how-to-fix-it/argon2.adoc new file mode 100644 index 0000000000..32374c391f --- /dev/null +++ b/rules/S5344/python/how-to-fix-it/argon2.adoc @@ -0,0 +1,62 @@ +== How to fix it in Argon2-cffi + +=== Code examples + +==== Noncompliant code example + +[source,python,diff-id=200,diff-type=noncompliant] +---- +from argon2 import PasswordHasher, profiles + +def hash_password(password): + ph = PasswordHasher.from_parameters(profiles.CHEAPEST) # Noncompliant + return ph.hash(password) +---- + +==== Compliant solution + +[source,python,diff-id=200,diff-type=compliant] +---- +from argon2 import PasswordHasher + +def hash_password(password): + ph = PasswordHasher() + return ph.hash(password) +---- + +=== How does this work? + +include::../../common/fix/argon-parameters.adoc[] + +To use values recommended by the Argon2 authors, you can use the following objects: + +* https://argon2-cffi.readthedocs.io/en/stable/api.html#argon2.profiles.RFC_9106_HIGH_MEMORY[argon2.profiles.RFC_9106_HIGH_MEMORY] +* https://argon2-cffi.readthedocs.io/en/stable/api.html#argon2.profiles.RFC_9106_LOW_MEMORY[argon2.profiles.RFC_9106_LOW_MEMORY] + +To use values recommended by the OWASP you can craft objects as follows: + +[source, python] +---- +from argon2 import Parameters +from argon2.low_level import ARGON2_VERSION, Type + +OWASP_1 = argon2.Parameters( + type=Type.ID, + version=ARGON2_VERSION, + salt_len=16, + hash_len=32, + time_cost=1, + memory_cost=47104, # 46 MiB + parallelism=1) + +def hash_password(password): + ph = PasswordHasher.from_parameters(OWASP_1) + return ph.hash(password) +---- + +=== Going the extra mile + +include::../../common/extra-mile/argon-cli.adoc[] + +include::../../common/extra-mile/peppering.adoc[] + diff --git a/rules/S5344/python/how-to-fix-it/bcrypt.adoc b/rules/S5344/python/how-to-fix-it/bcrypt.adoc new file mode 100644 index 0000000000..262be4205a --- /dev/null +++ b/rules/S5344/python/how-to-fix-it/bcrypt.adoc @@ -0,0 +1,73 @@ +== How to fix it in Bcrypt + +=== Code examples + +==== Noncompliant code example + +For password hashing: +[source,python,diff-id=201,diff-type=noncompliant] +---- +import bcrypt + +def hash_password(password): + return bcrypt.hashpw(password, bcrypt.gensalt(2)) # Noncompliant +---- + +For key derivation: +[source,python,diff-id=291,diff-type=noncompliant] +---- +import bcrypt + +def kdf(password, salt): + return bcrypt.kdf( + password=password, + salt=salt, + desired_key_bytes=32, + rounds=12, # Noncompliant + ignore_few_rounds=True) +---- + +==== Compliant solution + +For password hashing: + +[source,python,diff-id=201,diff-type=compliant] +---- +import bcrypt + +def hash_password(password): + return bcrypt.hashpw(password, bcrypt.gensalt()) +---- + +For key derivation: +[source,python,diff-id=291,diff-type=compliant] +---- +import bcrypt + +def kdf(password, salt): + return bcrypt.kdf( + password=password, + salt=salt, + desired_key_bytes=32, + rounds=4096) +---- + +=== How does this work? + +include::../../common/fix/password-hashing.adoc[] + +include::../../common/fix/bcrypt-parameters.adoc[] + +In the python bcrypt library, the default number of rounds is 12, which is +a good default value. + +For the `bcrypt.kdf` function, at least 50 rounds should be set, and the +`ignore_few_rounds` parameter should be avoided, as it allows fewer rounds. + +=== Pitfalls + +include::../../common/pitfalls/pre-hashing.adoc[] + +=== Going the extra mile + +include::../../common/extra-mile/peppering.adoc[] + diff --git a/rules/S5344/python/how-to-fix-it/django.adoc b/rules/S5344/python/how-to-fix-it/django.adoc new file mode 100644 index 0000000000..2ddf8cb45d --- /dev/null +++ b/rules/S5344/python/how-to-fix-it/django.adoc @@ -0,0 +1,73 @@ +== How to fix it in Django + +=== Code examples + +==== Noncompliant code example + +Django uses the first item in the `PASSWORD_HASHERS` list to store new passwords. +In this example, SHA-1 is used, which is too fast to store passwords. + +[source,python,diff-id=203,diff-type=noncompliant] +---- +# settings.py +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.SHA1PasswordHasher', # Noncompliant + 'django.contrib.auth.hashers.CryptPasswordHasher', + 'django.contrib.auth.hashers.Argon2PasswordHasher', + 'django.contrib.auth.hashers.ScryptPasswordHasher', +] +---- + +==== Compliant solution + +This example requires `argon2-cffi` to be installed, which can be done using `pip install django[argon2]`. + +[source,python,diff-id=203,diff-type=compliant] +---- +# settings.py +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.Argon2PasswordHasher', + 'django.contrib.auth.hashers.ScryptPasswordHasher', + 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', +] +---- + +=== How does this work? + +include::../../common/fix/password-hashing.adoc[] + +In the previous example, Argon2 is used as the default password hashing function +by Django. Use the `PASSWORD_HASHERS` variable carefuly. If there is a need to +upgrade, use +https://docs.djangoproject.com/en/5.0/topics/auth/passwords/#password-upgrading[Django's password upgrade documentation]. + +=== Going the extra mile + +==== Tweaking password hashing parameters + +It is possible to change the parameters of the password hashing algorithm to +make it more secure. For example, you can increase the number of iterations or +the length of the salt. + +https://docs.djangoproject.com/en/5.0/topics/auth/passwords/[The Django documentation contains more details about these parameters]. + +==== Preventing user enumeration attacks + +Django uses the first item in `PASSWORD_HASHERS` to store passwords, but uses every hashing algorithm in the `PASSWORD_HASHERS` +list to check passwords during user login. If a user password was not hashed using the first algorithm, then Django upgrades +the hashed password after a user logs in. + +This process is convenient to keep users up to date, but is also vulnerable to enumeration. If an +attacker wants to know whether an account exists, they can attempt a login with that account. By +tracking how long it took to get a response, they can know if an older hashing algorithm was used +(so the account exists) or the new hashing algorithm was used (the default is an account does not +exist.) + +To fix this, https://docs.djangoproject.com/en/5.0/topics/auth/passwords/#password-upgrading-without-requiring-a-login[the Django documentation] +defines how to upgrade passwords without needing to log in. In this case, a custom hasher has to +be created that wraps the old hash. + + +include::../../common/extra-mile/peppering.adoc[] + diff --git a/rules/S5344/python/how-to-fix-it/flask.adoc b/rules/S5344/python/how-to-fix-it/flask.adoc new file mode 100644 index 0000000000..314dc7304b --- /dev/null +++ b/rules/S5344/python/how-to-fix-it/flask.adoc @@ -0,0 +1,99 @@ +== How to fix it in Flask + +=== Code examples + +==== Noncompliant code example + +[source,python,diff-id=204,diff-type=noncompliant] +---- +from flask import Flask, request +from flask_bcrypt import Bcrypt + +app = Flask(__name__) +bcrypt = Bcrypt(app) + +@app.get("/") +def hash(): + password = request.args.get('password', '') + hashed_password = bcrypt.generate_password_hash(password, rounds=2) # Noncompliant + + return f"
{hashed_password.decode('utf-8')}
" +---- + +==== Compliant solution + +[source,python,diff-id=204,diff-type=compliant] +---- +from flask import Flask, request +from flask_bcrypt import Bcrypt + +app = Flask(__name__) +bcrypt = Bcrypt(app) + +@app.get("/") +def hash(): + password = request.args.get('password', '') + hashed_password = bcrypt.generate_password_hash(password) + + return f"{hashed_password.Decode('utf-8')}
" +---- + +=== How does this work? + +include::../../common/fix/password-hashing.adoc[] + +include::../../common/fix/bcrypt-parameters.adoc[] + +include::../../common/fix/argon-parameters.adoc[] + +To use values recommended by the Argon2 authors, you can use the two following objects: + +* https://argon2-cffi.readthedocs.io/en/stable/api.html#argon2.profiles.RFC_9106_HIGH_MEMORY[argon2.profiles.RFC_9106_HIGH_MEMORY] +* https://argon2-cffi.readthedocs.io/en/stable/api.html#argon2.profiles.RFC_9106_LOW_MEMORY[argon2.profiles.RFC_9106_LOW_MEMORY] + +To use values recommended by the OWASP, you can craft objects as follows: + +[source, python] +---- +import argon2 +from argon2.low_level import ARGON2_VERSION, Type + +OWASP_1 = argon2.Parameters( + type=Type.ID, + version=ARGON2_VERSION, + salt_len=16, + hash_len=32, + time_cost=1, + memory_cost=47104, # 46 MiB + parallelism=1) + +# To apply the parameters to the Flask app: +def set_flask_argon2_parameters(app, parameters: argon2.Parameters): + app.config['ARGON2_SALT_LENGTH'] = parameters.salt_len + app.config['ARGON2_HASH_LENGTH'] = parameters.hash_len + app.config['ARGON2_TIME_COST'] = parameters.time_cost + app.config['ARGON2_MEMORY_COST'] = parameters.memory_cost + app.config['ARGON2_PARALLELISM'] = parameters.parallelism + +# ---- +# Or the unofficial way: +from flask import Flask +from flask_argon2 import Argon2 + +app = Flask(__name__) +argon2 = Argon2(app) +argon2.ph = OWASP_1 + +set_flask_argon2_parameters(app, OWASP_1) +---- + +=== Pitfalls + +include::../../common/pitfalls/pre-hashing.adoc[] + +=== Going the extra mile + +include::../../common/extra-mile/argon-cli.adoc[] + +include::../../common/extra-mile/peppering.adoc[] + diff --git a/rules/S5344/python/how-to-fix-it/hashlib.adoc b/rules/S5344/python/how-to-fix-it/hashlib.adoc new file mode 100644 index 0000000000..08447cae9b --- /dev/null +++ b/rules/S5344/python/how-to-fix-it/hashlib.adoc @@ -0,0 +1,91 @@ +== How to fix it in Python Standard Library + +=== Code examples + +==== Noncompliant code example + +Code targeting scrypt: + +[source,python,diff-id=206,diff-type=noncompliant] +---- +from hashlib import scrypt + +def hash_password(password, salt): + return scrypt( + password, + salt, + n=1 << 10, # Noncompliant: N is too low + r=8, + p=2, + dklen=64 + ) +---- + +Code targeting PBKDF2: + +[source,python,diff-id=266,diff-type=noncompliant] +---- +from hashlib import pbkdf2_hmac + +def hash_password(password, salt): + return pbkdf2_hmac( + 'sha1', + password, + salt, + 500_000 # Noncompliant: not enough iterations for SHA-1 + ) +---- + + +==== Compliant solution + +Code targeting scrypt: + +[source,python,diff-id=206,diff-type=compliant] +---- +from hashlib import scrypt + +def hash_password(password, salt): + return scrypt( + password, + salt, + n=1 << 14, + r=8, + p=5, + dklen=64, + maxmem=85_000_000 # Needs ~85MB of memory + ) +---- + +Code targeting PBKDF2: + +[source,python,diff-id=266,diff-type=compliant] +---- +from hashlib import pbkdf2_hmac + +def hash_password(password, salt): + return pbkdf2_hmac( + 'sha256', + password, + salt, + 600_000 + ) +---- + + +=== How does this work? +The following sections provide guidance on the usage of these secure +password-hashing algorithms as provided by hashlib. + +include::../../common/fix/scrypt-parameters.adoc[] + +include::../../common/fix/pbkdf2-parameters.adoc[] + +=== Pitfalls + +include::../../common/pitfalls/pre-hashing.adoc[] + +=== Going the extra mile + +include::../../common/extra-mile/peppering.adoc[] + diff --git a/rules/S5344/python/how-to-fix-it/pyca.adoc b/rules/S5344/python/how-to-fix-it/pyca.adoc new file mode 100644 index 0000000000..5450ce2eb5 --- /dev/null +++ b/rules/S5344/python/how-to-fix-it/pyca.adoc @@ -0,0 +1,114 @@ +== How to fix it in pyca + +=== Code examples + +==== Noncompliant code example + +Code targeting scrypt: + +[source,python,diff-id=207,diff-type=noncompliant] +---- +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.scrypt import Scrypt + +def hash_password(password, salt): + scrypt = Scrypt( + salt=salt, + length=32, + n=1 << 10, + r=8, + p=1) # Noncompliant + + return scrypt.derive(password) +---- + +Code targeting PBKDF2: + +[source,python,diff-id=277,diff-type=noncompliant] +---- +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +def hash_password(password, salt): + pbkdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=480000) # Noncompliant + + return pbkdf.derive(password) +---- + + +==== Compliant solution + +Code targeting scrypt: + +[source,python,diff-id=207,diff-type=compliant] +---- +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.scrypt import Scrypt + +def hash_password(password, salt): + scrypt = Scrypt( + salt=salt, + length=64, + n=1 << 17, + r=8, + p=1) + + return scrypt.derive(password) +---- + +Code targeting PBKDF2: + +[source,python,diff-id=277,diff-type=compliant] +---- +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +def hash_password(password, salt): + pbkdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=600_000) # Noncompliant + + return pbkdf.derive(password) +---- + + +=== How does this work? + +include::../../common/fix/password-hashing.adoc[] + +The following sections provide guidance on the usage of these secure +password-hashing algorithms as provided by pyca/cryptography. + +include::../../common/fix/scrypt-parameters.adoc[] + +To use values recommended by OWASP, you can use an object crafted as follows: + +[source,python] +---- +OWASP_1 = { + "n": 1 << 17, + "r": 8, + "p": 1, + "length": 64, +} + +# To use this example, you can use the dictionary as a ``**kwargs`` variable: +scrypt(password, salt, **OWASP_1) +---- + +include::../../common/fix/pbkdf2-parameters.adoc[] + +=== Pitfalls + +include::../../common/pitfalls/pre-hashing.adoc[] + +=== Going the extra mile + +include::../../common/extra-mile/peppering.adoc[] + diff --git a/rules/S5344/python/message.adoc b/rules/S5344/python/message.adoc new file mode 100644 index 0000000000..8ad6c8a991 --- /dev/null +++ b/rules/S5344/python/message.adoc @@ -0,0 +1,11 @@ +=== Message + +For hashlib: + +* For scrypt: "Use strong scrypt parameters" +* For pbkdf2_hmac: "Use at least ``+{min_iterations}+`` PBKDF2 iterations" +** If `hash_name` is `"sha1"`, then min_iterations is 1300000 +** If `hash_name` is `"sha256"`, then min_iterations is 600000 +** If `hash_name` is `"sha512"`, then min_iterations is 210000 + +For Django: "Use a secure hashing algorithm to store passwords" diff --git a/rules/S5344/python/metadata.json b/rules/S5344/python/metadata.json new file mode 100644 index 0000000000..7a73a41bfd --- /dev/null +++ b/rules/S5344/python/metadata.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/rules/S5344/python/rule.adoc b/rules/S5344/python/rule.adoc new file mode 100644 index 0000000000..cbccac6584 --- /dev/null +++ b/rules/S5344/python/rule.adoc @@ -0,0 +1,43 @@ +include::../summary.adoc[] + +== Why is this an issue? + +include::../rationale.adoc[] + +include::../impact.adoc[] + +// How to fix it section + +include::how-to-fix-it/argon2.adoc[] + +include::how-to-fix-it/bcrypt.adoc[] + +include::how-to-fix-it/hashlib.adoc[] + +include::how-to-fix-it/pyca.adoc[] + +include::how-to-fix-it/django.adoc[] + +include::how-to-fix-it/flask.adoc[] + +== Resources + +=== Documentation + +* OWASP CheatSheet - https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html[Password Storage Cheat Sheet] + +include::../common/resources/standards.adoc[] + + +ifdef::env-github,rspecator-view[] + +''' +== Implementation Specification +(visible only on this page) + +include::message.adoc[] + +include::highlighting.adoc[] + +endif::env-github,rspecator-view[] +