VirtualMailManager/password.py
branchv0.6.x
changeset 284 ec1966828246
parent 274 45ec5c3cfef4
child 287 1e77dd639fa3
--- 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