diff -r ea6d052de24a -r ec1966828246 VirtualMailManager/password.py --- a/VirtualMailManager/password.py Fri Apr 30 08:02:03 2010 +0000 +++ b/VirtualMailManager/password.py Mon May 03 08:25:26 2010 +0000 @@ -13,7 +13,7 @@ """ from crypt import crypt -from random import choice, shuffle +from random import SystemRandom from subprocess import Popen, PIPE try: @@ -35,7 +35,28 @@ _ = lambda msg: msg cfg_dget = lambda option: None -_get_salt = lambda s_len: ''.join(choice(SALTCHARS) for x in xrange(s_len)) +_sys_rand = SystemRandom() +_get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len)) + + +def _test_crypt_algorithms(): + """Check for Blowfish/SHA-256/SHA-512 support in crypt.crypt().""" + blowfish_ = sha256_ = sha512_ = False + _blowfish = '$2a$04$0123456789abcdefABCDE.N.drYX5yIAL1LkTaaZotW3yI0hQhZru' + _sha256 = '$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt9McEgrbFMKi\ +9qrb1jehe7hn4' + _sha512 = '$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTqvh\ +JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1' + + if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish: + blowfish_ = True + if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256: + sha256_ = True + if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512: + sha512_ = True + return blowfish_, sha256_, sha512_ + +CRYPT_BLOWFISH, CRYPT_SHA256, CRYPT_SHA512 = _test_crypt_algorithms() def _dovecotpw(password, scheme, encoding): @@ -110,10 +131,44 @@ return get_unicode('{%s}%s' % (scheme, password)) +def _get_crypt_blowfish_salt(): + """Generates a salt for Blowfish crypt.""" + rounds = cfg_dget('misc.crypt_blowfish_rounds') + if rounds < 4: + rounds = 4 + elif rounds > 31: + rounds = 31 + return '$2a$%02d$%s$' % (rounds, _get_salt(22)) + + +def _get_crypt_shaxxx_salt(crypt_id): + """Generates a salt for crypt using the SHA-256 or SHA-512 encryption + method. + *crypt_id* must be either `5` (SHA-256) or `6` (SHA1-512). + """ + assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id + if crypt_id is 6: + rounds = cfg_dget('misc.crypt_sha512_rounds') + else: + rounds = cfg_dget('misc.crypt_sha256_rounds') + if rounds < 1000: + rounds = 1000 + elif rounds > 999999999: + rounds = 999999999 + return '$%d$rounds=%d$%s$' % (crypt_id, rounds, _get_salt(16)) + + def _crypt_hash(password, scheme, encoding): """Generates (encoded) CRYPT/MD5/MD5-CRYPT hashes.""" if scheme == 'CRYPT': - salt = _get_salt(2) + if CRYPT_BLOWFISH and cfg_dget('misc.crypt_blowfish_rounds'): + salt = _get_crypt_blowfish_salt() + elif CRYPT_SHA512 and cfg_dget('misc.crypt_sha512_rounds'): + salt = _get_crypt_shaxxx_salt(6) + elif CRYPT_SHA256 and cfg_dget('misc.crypt_sha256_rounds'): + salt = _get_crypt_shaxxx_salt(5) + else: + salt = _get_salt(2) else: salt = '$1$%s$' % _get_salt(8) encrypted = crypt(password, salt) @@ -121,7 +176,7 @@ if encoding == 'HEX': encrypted = encrypted.encode('hex') else: - encrypted = encrypted.encode('base64').rstrip() + encrypted = encrypted.encode('base64').replace('\n', '') return _format_digest(encrypted, scheme, encoding) @@ -335,11 +390,9 @@ The length of the password can be configured in the ``vmm.cfg`` (account.password_length). """ - pw_chars = list(PASSWDCHARS) - shuffle(pw_chars) pw_len = cfg_dget('account.password_length') if pw_len < 8: pw_len = 8 - return ''.join(choice(pw_chars) for x in xrange(pw_len)) + return ''.join(_sys_rand.sample(PASSWDCHARS, pw_len)) -del _, cfg_dget +del _, cfg_dget, _test_crypt_algorithms