-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Swift: detect hash functions with low # of iterations #10947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
QHelp previews: swift/ql/src/queries/Security/CWE-916/InsufficientHashIterations.qhelpInsufficient hash iterationsStoring cryptographic hashes of passwords is standard security practice, but it is equally important to select the right hashing scheme. If an attacker obtains the hashed passwords of an application, the password hashing scheme should still prevent the attacker from easily obtaining the original cleartext passwords. A good password hashing scheme requires a computation that cannot be done efficiently. Hashing schemes with low number of iterations are efficiently computable, and are therefore not suitable for password hashing. RecommendationUse the OWASP recommendation for sufficient number of iterations (currently, that is greater than or equal to 120,000) for password hashing schemes. ExampleThe following example shows a few cases where a password hashing scheme is instantiated. In the 'BAD' cases, the scheme is initialized with insufficient iterations, making it susceptible to password cracking attacks. In the 'GOOD' cases, the scheme is initialized with at least 120,000 iterations, which protects the hashed data against recovery.
References
|
👋 Hi from docs, I've added this PR to the team board for a writer to review. |
Hi @karimhamdanali - thanks for asking for a docs team review of your pull request. 👋🏻 Martin's followed our first responder notes and added this to our review board. Normally we expect a new query to have a technical review either complete, or nearly complete, before we start a docs review. This avoids duplicated effort because a technical review may change the focus of a query, so the user-facing content will change as a result of the review. I'm going to remove the label and remove this pull request from our review board for now. Please do re-add the label when you're confident that the technical review is nearly complete. |
Thanks @felicitymay for the clarification. I actually didn't know that! Will re-add the label once the PR is through the technical review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, a few minor suggestions...
swift/ql/src/queries/Security/CWE-916/InsufficientHashIterations.qhelp
Outdated
Show resolved
Hide resolved
swift/ql/src/queries/Security/CWE-916/InsufficientHashIterations.ql
Outdated
Show resolved
Hide resolved
swift/ql/src/queries/Security/CWE-916/InsufficientHashIterations.ql
Outdated
Show resolved
Hide resolved
d2b6ace
to
b4a37d4
Compare
plus also simplify the pattern matching of the sink classes
b4a37d4
to
408c7be
Compare
PR is now ready for docs review. |
which is at least 120,000 iterations for secure password hashing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy for this to be merged after the docs review. 👍
Hi again from docs -- Adding this PR to the team board for a writer to review ✨ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @karimhamdanali 👋🏻
Many thanks for including query help, references and an example for this new query ✨
I've added a few suggestions and comments with the intention of making the information clearer for users. I'm hopeful that my suggestions don't change the intended meaning, but am aware that I have little technical context. Very happy to discuss any suggestions or comments.
"qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
<p>Using hash functions with less than 1,000 iterations is not secure. That scheme is vulnerable to password cracking attacks due to having an insufficient level of computational effort.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This query seems as if it's fairly closely related to the JavaScript query Use of password hash with insufficient computational effort.
I think that it might be worth reusing some of the explanation of why this is important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I've updated the help text here with the relevant information from the JavaScript query. Thanks for the pointer to that query @felicitymay !
</overview> | ||
|
||
<recommendation> | ||
<p>Use sufficient number of iterations (that is, greater than or equal 120000) for generating password-based keys.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't say how we arrived at this number. Is it taken from an OWASP recommendation, or somewhere else that we could refer to? I'm assuming that recommended minimums will gradually rise, so referring to a source where the current best practice is detailed will help this information stay up to date.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I've updated the text to state that we should follow the OWASP recommendation, with a note to the current recommendation of at least 120,000 iterations.
</recommendation> | ||
|
||
<example> | ||
<p>The following example shows a few cases of instantiating a password-based key. In the 'BAD' cases, the key is initialized with insufficient iterations, making it susceptible to password cracking attacks. In the 'GOOD' cases, the key is initialized with at least 120000 iterations, which protects the encrypted data against recovery.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor text suggestion:
<p>The following example shows a few cases of instantiating a password-based key. In the 'BAD' cases, the key is initialized with insufficient iterations, making it susceptible to password cracking attacks. In the 'GOOD' cases, the key is initialized with at least 120000 iterations, which protects the encrypted data against recovery.</p> | |
<p>The following example shows a few cases where a password-based key is instantiated. In the 'BAD' cases, the key is initialized with insufficient iterations, making it susceptible to password cracking attacks. In the 'GOOD' cases, the key is initialized with at least 120,000 iterations, which protects the encrypted data against recovery.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed as suggested
func encrypt() { | ||
// ... | ||
|
||
// BAD: Using insufficient (i.e., < 120,000) hash iterations keys for encryption |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We write out "i.e." in full English for clarity:
// BAD: Using insufficient (i.e., < 120,000) hash iterations keys for encryption | |
// BAD: Using insufficient (that is, < 120,000) hash iterations keys for encryption |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed as suggested
_ = try PKCS5.PBKDF1(password: getRandomArray(), salt: getRandomArray(), iterations: 90000, keyLength: 0) | ||
_ = try PKCS5.PBKDF2(password: getRandomArray(), salt: getRandomArray(), iterations: 90000, keyLength: 0) | ||
|
||
// GOOD: Using sufficient (i.e., >= 120,000) hash iterations keys for encryption |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// GOOD: Using sufficient (i.e., >= 120,000) hash iterations keys for encryption | |
// GOOD: Using sufficient (that is, >= 120,000) hash iterations keys for encryption |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed as suggested
_ = try PKCS5.PBKDF1(password: getRandomArray(), salt: getRandomArray(), iterations: 120120, keyLength: 0) | ||
_ = try PKCS5.PBKDF2(password: getRandomArray(), salt: getRandomArray(), iterations: 120120, keyLength: 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Judging by the OWASP reference, what is an adequate number of iterations seems to vary according to the algorithm you're using. It's not entirely clear to me looking at this example, whether or not this is adequate for the algorithm because I don't see which hashing algorithm is used.
This may be entirely unimportant - I'm a writer, not a developer 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good point. We don't implement the different recommended values for different algorithms, rather we just took the lowest recommendation and check the value used is at least that. In practice this design is likely to catch the vast majority of problems and keeps the query simple and reliable - but there's no reason our examples shouldn't set a good example by being fully OWASP compliant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I've updated the test to be fully OWASP compliant :)
@@ -0,0 +1,68 @@ | |||
/** | |||
* @name Insufficient hash iterations | |||
* @description Using hash functions with less than 120,000 iterations is not secure, because that scheme leads to password cracking attacks due to having an insufficient level of computational effort. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest:
* @description Using hash functions with less than 120,000 iterations is not secure, because that scheme leads to password cracking attacks due to having an insufficient level of computational effort. | |
* @description Using hash functions with fewer than 120,000 iterations is insufficient to protect passwords because a cracking attack will require a low level of computational effort. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed as suggested
Thanks @felicitymay for the docs review! I think I've addressed all your comments so far. Please let me know if you have any further comments or suggestions before we can merge this PR. Thanks :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many thanks for the updates to the query help 💖
Awesome! Thanks @felicitymay. @geoffw0, I think this one is ready to get merged then :) Thanks all for reviewing this PR. |
Using hash functions with less than 1,000 iterations is not secure. That scheme is vulnerable to password cracking attacks due to having an insufficient level of computational effort.
The rule currently supports all ciphers that the CryptoSwift API provides, but we can always extend it further if more cophers are added.
I'd appreciate a review of the query itself, the accompanying tests, and the associated documentation.