-
Notifications
You must be signed in to change notification settings - Fork 23.7k
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
unbreak password_hash('blowfish') #25347
Conversation
The used salt buffer was of incorrect length (16 instead of 22). Also the passlib documentation recommends against generating your own salt. So unless the salt was provided, let passlib generate it. Also set the default bcrypto algorithm to '2b' which is the recommended format.
@@ -271,13 +271,17 @@ def get_encrypted_password(password, hashtype='sha512', salt=None): | |||
# TODO: find a way to construct dynamically from system | |||
cryptmethod = { | |||
'md5': '1', | |||
'blowfish': '2a', | |||
'blowfish': '2b', |
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 doesn't match documentation. Why is it being changed?
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.
2a | Blowfish (not in mainline glibc; added in some
| Linux distributions)
from the glibc man page for crypt.
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.
Okay, I see where the passlib documentation specifies a 2b. Since cryptmethod is only used in the crypt.crypt() path, I don't think we should change this code here.
# below contains incorrectly set padding bits. Also the length used to | ||
# be incorrect (16 instead of 22). Besides, Passlib recommends NOT | ||
# generating a salt string manually. | ||
if salt is None and hashtype is not 'blowfish': |
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.
Making this change here doesn't work because we aren't guaranteed to have passlib installed. The crypt.crypt() method will require that we have a salt set. You could move salt generation into the conditional for not HAS_PASSLIB
.
I also noticed that the passlib import check we're doing is no longer sufficient... apparently there are now separate backend packages and passlib needs at least one of those installed to function. Perhaps we need a try: except passlib.exc.MissingBackendError: in the passlib code and fallback to crypt if that fails as well. |
Something like this perhaps: encrypted = None
if HAS_PASSLIB:
try:
# passlib based code
except passlib.exc.MissingBackendError:
pass
if encrypted is None:
if sys.platform.startswith('darwin'):
raise errors.AnsibleFilterError('|password_hash requires the passlib python module to generate password hashes on Mac OS X/Darwin')
saltstring = "$%s$%s" % (cryptmethod[hashtype], salt)
encrypted = crypt.crypt(password, saltstring) |
Note: bcoca recommends a different error message if a passlib backend is missing rather than the same message as missing passlib altogether. |
Passlib author here - I just incidentally ran across this thread, and wanted to point out that So if passlib throws MissingBackendError, it's reasonably certain that [1] For example, in cases like linux/glibc, |
Hmm... On my linux/glibc box, crypt.crypt does not fallback. It returns None (Would be better to raise an exception but a sentinel like None can also be programmed against). |
For reference, I'm using Linux Mint 18.1, python 3.5.2; but I'm not at all surprised you have different behavior. That's one of the things that started me coding passlib -- crypt()'s error/fallback behavior is hugely inconsistent at the C level, and python's wrapper just reflects that. Given the same input of Passlib's internal safe_crypt() helper is an example of the number of edge cases I've had to normalize for :( |
…_hash. * vars_prompt with encrypt does not require passlib for the algorithms supported by crypt. * Additional checks ensure that there is always a result. This works around issues in the crypt.crypt python function that returns None for algorithms it does not know. Some modules (like user module) interprets None as no password at all, which is misleading. * The password_hash filter supports all parameters of passlib. This allows users to provide a rounds parameter, fixing ansible#15326. * password_hash is not restricted to the subset provided by crypt.crypt, fixing one half of ansible#17266. * Updated documentation fixes other half of ansible#17266. * password_hash does not hard-code the salt-length, which fixes bcrypt in connection with passlib. bcrypt requires a salt with length 22, which fixes ansible#25347 * Salts are only generated by ansible when using crypt.crypt. Otherwise passlib generates them. * Avoids deprecated functionality of passlib with newer library versions. * When no rounds are specified for sha256/sha256_crypt and sha512/sha512_crypt always uses the default values used by crypt, i.e. 5000 rounds. Before when installed passlibs' defaults were used. passlib changes its defaults with newer library versions, leading to non idempotent behavior. NOTE: This will lead to the recalculation of existing hashes generated with passlib and without a rounds parameter. Yet henceforth the hashes will remain the same. No matter the installed passlib version. Making these hashes idempotent. Fixes ansible#15326 Fixes ansible#17266 Fixes ansible#25347 except bcrypt still uses 2a, instead of the suggested 2b.
#21215 has been merged which implements all of this except for the switch to 2b iff we know we're using passllib instead of crypt. If you'd like to make a PR for that aspect, feel free to create a new one which does that. Thanks! |
…_hash (#21215) * Share the implementation of hashing for both vars_prompt and password_hash. * vars_prompt with encrypt does not require passlib for the algorithms supported by crypt. * Additional checks ensure that there is always a result. This works around issues in the crypt.crypt python function that returns None for algorithms it does not know. Some modules (like user module) interprets None as no password at all, which is misleading. * The password_hash filter supports all parameters of passlib. This allows users to provide a rounds parameter, fixing #15326. * password_hash is not restricted to the subset provided by crypt.crypt, fixing one half of #17266. * Updated documentation fixes other half of #17266. * password_hash does not hard-code the salt-length, which fixes bcrypt in connection with passlib. bcrypt requires a salt with length 22, which fixes #25347 * Salts are only generated by ansible when using crypt.crypt. Otherwise passlib generates them. * Avoids deprecated functionality of passlib with newer library versions. * When no rounds are specified for sha256/sha256_crypt and sha512/sha512_crypt always uses the default values used by crypt, i.e. 5000 rounds. Before when installed passlibs' defaults were used. passlib changes its defaults with newer library versions, leading to non idempotent behavior. NOTE: This will lead to the recalculation of existing hashes generated with passlib and without a rounds parameter. Yet henceforth the hashes will remain the same. No matter the installed passlib version. Making these hashes idempotent. Fixes #15326 Fixes #17266 Fixes #25347 except bcrypt still uses 2a, instead of the suggested 2b. * random_salt is solely handled by encrypt.py. There is no _random_salt function there anymore. Also the test moved to test_encrypt.py. * Uses pytest.skip when passlib is not available, instead of a silent return. * More checks are executed when passlib is not available. * Moves tests that require passlib into their own test-function. * Uses the six library to reraise the exception. * Fixes integration test. When no rounds are provided the defaults of crypt are used. In that case the rounds are not part of the resulting MCF output.
SUMMARY
The used salt buffer was of incorrect length (16 instead of 22). Also the
passlib documentation recommends against generating your own salt. So unless
the salt was provided, let passlib generate it.
Also set the default bcrypto algorithm to '2b' which is the recommended format.
ISSUE TYPE
COMPONENT NAME
password_hash
filterANSIBLE VERSION
ADDITIONAL INFORMATION
The upstream documentation for passlib can be found here.
About salt it writes:
and about the algorithm:
Before:
After:
More informatively I've ran a
debug
task (debug: msg="{{ 'emiel' | password_hash('blowfish') }}"
) that used to return:and now returns: