# HG changeset patch # User Pascal Volk # Date 1273542890 0 # Node ID 619dadc0fd2540925662293f5e9d4f6f8290c619 # Parent 7ef3f117a2304146bb977bf4556e8f1a188e44ff VMM/password: added some CRYPT_* constants. Reverted modification (284:ec1966828246) in _get_salt(). diff -r 7ef3f117a230 -r 619dadc0fd25 TODO --- a/TODO Mon May 10 09:06:57 2010 +0000 +++ b/TODO Tue May 11 01:54:50 2010 +0000 @@ -19,9 +19,9 @@ + alias domains Database: - public.users.passwd: increase to "character varying(250)" - why? len(VirtualMailManager.password.pwhash('1', 'crypt.hex')) -> 249 - if VirtualMailManager.password.CRYPT_SHA512 is True and - misc.crypt_sha512_rounds > 0 + public.users.passwd: increase to "character varying(264)" + why? len(VirtualMailManager.password.pwhash('1', 'sha512-crypt.hex')) -> 264 + if Dovecot >= v2.0.beta6 || changeset >= 11278:2ead7574bb08 and + misc.crypt_sha512_rounds >= 100000000 public.users.digestmd5: add "character varying(48)" Outlook will love it. (`doveadm pw -s DIGEST-MD5.hex -p 1 -u 0`) diff -r 7ef3f117a230 -r 619dadc0fd25 VirtualMailManager/password.py --- a/VirtualMailManager/password.py Mon May 10 09:06:57 2010 +0000 +++ b/VirtualMailManager/password.py Tue May 11 01:54:50 2010 +0000 @@ -34,11 +34,27 @@ PASSWDCHARS = '._-+#*23456789abcdefghikmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ' DEFAULT_B64 = (None, 'B64', 'BASE64') DEFAULT_HEX = (None, 'HEX') +CRYPT_ID_MD5 = 1 +CRYPT_ID_BLF = '2a' +CRYPT_ID_SHA256 = 5 +CRYPT_ID_SHA512 = 6 +CRYPT_SALT_LEN = 2 +CRYPT_BLF_ROUNDS_MIN = 4 +CRYPT_BLF_ROUNDS_MAX = 31 +CRYPT_BLF_SALT_LEN = 22 +CRYPT_MD5_SALT_LEN = 8 +CRYPT_SHA2_ROUNDS_DEFAULT = 5000 +CRYPT_SHA2_ROUNDS_MIN = 1000 +CRYPT_SHA2_ROUNDS_MAX = 999999999 +CRYPT_SHA2_SALT_LEN = 16 +SALTED_ALGO_SALT_LEN = 4 + _ = lambda msg: msg cfg_dget = lambda option: None _sys_rand = SystemRandom() -_get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len)) +_choice = _sys_rand.choice +_get_salt = lambda s_len: ''.join(_choice(SALTCHARS) for x in xrange(s_len)) def _dovecotpw(password, scheme, encoding): @@ -116,44 +132,47 @@ 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)) + if rounds < CRYPT_BLF_ROUNDS_MIN: + rounds = CRYPT_BLF_ROUNDS_MIN + elif rounds > CRYPT_BLF_ROUNDS_MAX: + rounds = CRYPT_BLF_ROUNDS_MAX + return '$%s$%02d$%s' % (CRYPT_ID_BLF, rounds, + _get_salt(CRYPT_BLF_SALT_LEN)) def _get_crypt_sha2_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). + *crypt_id* must be either `5` (SHA-256) or `6` (SHA-512). """ - assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id - if crypt_id is 6: + assert crypt_id in (CRYPT_ID_SHA256, CRYPT_ID_SHA512), 'invalid crypt ' \ + 'id: %r' % crypt_id + if crypt_id is CRYPT_ID_SHA512: 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 - if rounds == 5000: - return '$%d$%s' % (crypt_id, _get_salt(16)) - return '$%d$rounds=%d$%s' % (crypt_id, rounds, _get_salt(16)) + if rounds < CRYPT_SHA2_ROUNDS_MIN: + rounds = CRYPT_SHA2_ROUNDS_MIN + elif rounds > CRYPT_SHA2_ROUNDS_MAX: + rounds = CRYPT_SHA2_ROUNDS_MAX + if rounds == CRYPT_SHA2_ROUNDS_DEFAULT: + return '$%d$%s' % (crypt_id, _get_salt(CRYPT_SHA2_SALT_LEN)) + return '$%d$rounds=%d$%s' % (crypt_id, rounds, + _get_salt(CRYPT_SHA2_SALT_LEN)) def _crypt_hash(password, scheme, encoding): """Generates (encoded) CRYPT/MD5/{BLF,MD5,SHA{256,512}}-CRYPT hashes.""" if scheme == 'CRYPT': - salt = _get_salt(2) + salt = _get_salt(CRYPT_SALT_LEN) elif scheme == 'BLF-CRYPT': salt = _get_crypt_blowfish_salt() elif scheme in ('MD5-CRYPT', 'MD5'): - salt = '$1$%s' % _get_salt(8) + salt = '$%d$%s' % (CRYPT_ID_MD5, _get_salt(CRYPT_MD5_SALT_LEN)) elif scheme == 'SHA256-CRYPT': - salt = _get_crypt_sha2_salt(5) + salt = _get_crypt_sha2_salt(CRYPT_ID_SHA256) else: - salt = _get_crypt_sha2_salt(6) + salt = _get_crypt_sha2_salt(CRYPT_ID_SHA512) encrypted = crypt(password, salt) if encoding: if encoding == 'HEX': @@ -253,7 +272,7 @@ def _smd5_hash(password, scheme, encoding): """Generates SMD5 (salted PLAIN-MD5) hashes.""" md5 = hashlib.md5(password) - salt = _get_salt(4) + salt = _get_salt(SALTED_ALGO_SALT_LEN) md5.update(salt) if encoding in DEFAULT_B64: digest = (md5.digest() + salt).encode('base64').rstrip() @@ -265,7 +284,7 @@ def _ssha1_hash(password, scheme, encoding): """Generates SSHA (salted SHA/SHA1) hashes.""" sha1 = hashlib.sha1(password) - salt = _get_salt(4) + salt = _get_salt(SALTED_ALGO_SALT_LEN) sha1.update(salt) if encoding in DEFAULT_B64: digest = (sha1.digest() + salt).encode('base64').rstrip() @@ -278,7 +297,7 @@ """Generates SSHA256 (salted SHA256) hashes.""" sha256 = _sha256_new(password) if sha256: - salt = _get_salt(4) + salt = _get_salt(SALTED_ALGO_SALT_LEN) sha256.update(salt) if encoding in DEFAULT_B64: digest = (sha256.digest() + salt).encode('base64').rstrip() @@ -291,7 +310,7 @@ def _ssha512_hash(password, scheme, encoding): """Generates SSHA512 (salted SHA512) hashes.""" if not COMPAT: - salt = _get_salt(4) + salt = _get_salt(SALTED_ALGO_SALT_LEN) sha512 = hashlib.sha512(password + salt) if encoding in DEFAULT_B64: digest = (sha512.digest() + salt).encode('base64').replace('\n',