VirtualMailManager/password.py
branchv0.6.x
changeset 292 619dadc0fd25
parent 291 7ef3f117a230
child 316 31d8931dc535
equal deleted inserted replaced
291:7ef3f117a230 292:619dadc0fd25
    32 COMPAT = hasattr(hashlib, 'compat')
    32 COMPAT = hasattr(hashlib, 'compat')
    33 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    33 SALTCHARS = './0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    34 PASSWDCHARS = '._-+#*23456789abcdefghikmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
    34 PASSWDCHARS = '._-+#*23456789abcdefghikmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
    35 DEFAULT_B64 = (None, 'B64', 'BASE64')
    35 DEFAULT_B64 = (None, 'B64', 'BASE64')
    36 DEFAULT_HEX = (None, 'HEX')
    36 DEFAULT_HEX = (None, 'HEX')
       
    37 CRYPT_ID_MD5 = 1
       
    38 CRYPT_ID_BLF = '2a'
       
    39 CRYPT_ID_SHA256 = 5
       
    40 CRYPT_ID_SHA512 = 6
       
    41 CRYPT_SALT_LEN = 2
       
    42 CRYPT_BLF_ROUNDS_MIN = 4
       
    43 CRYPT_BLF_ROUNDS_MAX = 31
       
    44 CRYPT_BLF_SALT_LEN = 22
       
    45 CRYPT_MD5_SALT_LEN = 8
       
    46 CRYPT_SHA2_ROUNDS_DEFAULT = 5000
       
    47 CRYPT_SHA2_ROUNDS_MIN = 1000
       
    48 CRYPT_SHA2_ROUNDS_MAX = 999999999
       
    49 CRYPT_SHA2_SALT_LEN = 16
       
    50 SALTED_ALGO_SALT_LEN = 4
       
    51 
    37 
    52 
    38 _ = lambda msg: msg
    53 _ = lambda msg: msg
    39 cfg_dget = lambda option: None
    54 cfg_dget = lambda option: None
    40 _sys_rand = SystemRandom()
    55 _sys_rand = SystemRandom()
    41 _get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len))
    56 _choice = _sys_rand.choice
       
    57 _get_salt = lambda s_len: ''.join(_choice(SALTCHARS) for x in xrange(s_len))
    42 
    58 
    43 
    59 
    44 def _dovecotpw(password, scheme, encoding):
    60 def _dovecotpw(password, scheme, encoding):
    45     """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns
    61     """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns
    46     the hashed password: {scheme[.encoding]}hash
    62     the hashed password: {scheme[.encoding]}hash
   114 
   130 
   115 
   131 
   116 def _get_crypt_blowfish_salt():
   132 def _get_crypt_blowfish_salt():
   117     """Generates a salt for Blowfish crypt."""
   133     """Generates a salt for Blowfish crypt."""
   118     rounds = cfg_dget('misc.crypt_blowfish_rounds')
   134     rounds = cfg_dget('misc.crypt_blowfish_rounds')
   119     if rounds < 4:
   135     if rounds < CRYPT_BLF_ROUNDS_MIN:
   120         rounds = 4
   136         rounds = CRYPT_BLF_ROUNDS_MIN
   121     elif rounds > 31:
   137     elif rounds > CRYPT_BLF_ROUNDS_MAX:
   122         rounds = 31
   138         rounds = CRYPT_BLF_ROUNDS_MAX
   123     return '$2a$%02d$%s' % (rounds, _get_salt(22))
   139     return '$%s$%02d$%s' % (CRYPT_ID_BLF, rounds,
       
   140                             _get_salt(CRYPT_BLF_SALT_LEN))
   124 
   141 
   125 
   142 
   126 def _get_crypt_sha2_salt(crypt_id):
   143 def _get_crypt_sha2_salt(crypt_id):
   127     """Generates a salt for crypt using the SHA-256 or SHA-512 encryption
   144     """Generates a salt for crypt using the SHA-256 or SHA-512 encryption
   128     method.
   145     method.
   129     *crypt_id* must be either `5` (SHA-256) or `6` (SHA1-512).
   146     *crypt_id* must be either `5` (SHA-256) or `6` (SHA-512).
   130     """
   147     """
   131     assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id
   148     assert crypt_id in (CRYPT_ID_SHA256, CRYPT_ID_SHA512), 'invalid crypt ' \
   132     if crypt_id is 6:
   149            'id: %r' % crypt_id
       
   150     if crypt_id is CRYPT_ID_SHA512:
   133         rounds = cfg_dget('misc.crypt_sha512_rounds')
   151         rounds = cfg_dget('misc.crypt_sha512_rounds')
   134     else:
   152     else:
   135         rounds = cfg_dget('misc.crypt_sha256_rounds')
   153         rounds = cfg_dget('misc.crypt_sha256_rounds')
   136     if rounds < 1000:
   154     if rounds < CRYPT_SHA2_ROUNDS_MIN:
   137         rounds = 1000
   155         rounds = CRYPT_SHA2_ROUNDS_MIN
   138     elif rounds > 999999999:
   156     elif rounds > CRYPT_SHA2_ROUNDS_MAX:
   139         rounds = 999999999
   157         rounds = CRYPT_SHA2_ROUNDS_MAX
   140     if rounds == 5000:
   158     if rounds == CRYPT_SHA2_ROUNDS_DEFAULT:
   141         return '$%d$%s' % (crypt_id, _get_salt(16))
   159         return '$%d$%s' % (crypt_id, _get_salt(CRYPT_SHA2_SALT_LEN))
   142     return '$%d$rounds=%d$%s' % (crypt_id, rounds, _get_salt(16))
   160     return '$%d$rounds=%d$%s' % (crypt_id, rounds,
       
   161                                  _get_salt(CRYPT_SHA2_SALT_LEN))
   143 
   162 
   144 
   163 
   145 def _crypt_hash(password, scheme, encoding):
   164 def _crypt_hash(password, scheme, encoding):
   146     """Generates (encoded) CRYPT/MD5/{BLF,MD5,SHA{256,512}}-CRYPT hashes."""
   165     """Generates (encoded) CRYPT/MD5/{BLF,MD5,SHA{256,512}}-CRYPT hashes."""
   147     if scheme == 'CRYPT':
   166     if scheme == 'CRYPT':
   148         salt = _get_salt(2)
   167         salt = _get_salt(CRYPT_SALT_LEN)
   149     elif scheme == 'BLF-CRYPT':
   168     elif scheme == 'BLF-CRYPT':
   150         salt = _get_crypt_blowfish_salt()
   169         salt = _get_crypt_blowfish_salt()
   151     elif scheme in ('MD5-CRYPT', 'MD5'):
   170     elif scheme in ('MD5-CRYPT', 'MD5'):
   152         salt = '$1$%s' % _get_salt(8)
   171         salt = '$%d$%s' % (CRYPT_ID_MD5, _get_salt(CRYPT_MD5_SALT_LEN))
   153     elif scheme == 'SHA256-CRYPT':
   172     elif scheme == 'SHA256-CRYPT':
   154         salt = _get_crypt_sha2_salt(5)
   173         salt = _get_crypt_sha2_salt(CRYPT_ID_SHA256)
   155     else:
   174     else:
   156         salt = _get_crypt_sha2_salt(6)
   175         salt = _get_crypt_sha2_salt(CRYPT_ID_SHA512)
   157     encrypted = crypt(password, salt)
   176     encrypted = crypt(password, salt)
   158     if encoding:
   177     if encoding:
   159         if encoding == 'HEX':
   178         if encoding == 'HEX':
   160             encrypted = encrypted.encode('hex')
   179             encrypted = encrypted.encode('hex')
   161         else:
   180         else:
   251 
   270 
   252 
   271 
   253 def _smd5_hash(password, scheme, encoding):
   272 def _smd5_hash(password, scheme, encoding):
   254     """Generates SMD5 (salted PLAIN-MD5) hashes."""
   273     """Generates SMD5 (salted PLAIN-MD5) hashes."""
   255     md5 = hashlib.md5(password)
   274     md5 = hashlib.md5(password)
   256     salt = _get_salt(4)
   275     salt = _get_salt(SALTED_ALGO_SALT_LEN)
   257     md5.update(salt)
   276     md5.update(salt)
   258     if encoding in DEFAULT_B64:
   277     if encoding in DEFAULT_B64:
   259         digest = (md5.digest() + salt).encode('base64').rstrip()
   278         digest = (md5.digest() + salt).encode('base64').rstrip()
   260     else:
   279     else:
   261         digest = md5.hexdigest() + salt.encode('hex')
   280         digest = md5.hexdigest() + salt.encode('hex')
   263 
   282 
   264 
   283 
   265 def _ssha1_hash(password, scheme, encoding):
   284 def _ssha1_hash(password, scheme, encoding):
   266     """Generates SSHA (salted SHA/SHA1) hashes."""
   285     """Generates SSHA (salted SHA/SHA1) hashes."""
   267     sha1 = hashlib.sha1(password)
   286     sha1 = hashlib.sha1(password)
   268     salt = _get_salt(4)
   287     salt = _get_salt(SALTED_ALGO_SALT_LEN)
   269     sha1.update(salt)
   288     sha1.update(salt)
   270     if encoding in DEFAULT_B64:
   289     if encoding in DEFAULT_B64:
   271         digest = (sha1.digest() + salt).encode('base64').rstrip()
   290         digest = (sha1.digest() + salt).encode('base64').rstrip()
   272     else:
   291     else:
   273         digest = sha1.hexdigest() + salt.encode('hex')
   292         digest = sha1.hexdigest() + salt.encode('hex')
   276 
   295 
   277 def _ssha256_hash(password, scheme, encoding):
   296 def _ssha256_hash(password, scheme, encoding):
   278     """Generates SSHA256 (salted SHA256) hashes."""
   297     """Generates SSHA256 (salted SHA256) hashes."""
   279     sha256 = _sha256_new(password)
   298     sha256 = _sha256_new(password)
   280     if sha256:
   299     if sha256:
   281         salt = _get_salt(4)
   300         salt = _get_salt(SALTED_ALGO_SALT_LEN)
   282         sha256.update(salt)
   301         sha256.update(salt)
   283         if encoding in DEFAULT_B64:
   302         if encoding in DEFAULT_B64:
   284             digest = (sha256.digest() + salt).encode('base64').rstrip()
   303             digest = (sha256.digest() + salt).encode('base64').rstrip()
   285         else:
   304         else:
   286             digest = sha256.hexdigest() + salt.encode('hex')
   305             digest = sha256.hexdigest() + salt.encode('hex')
   289 
   308 
   290 
   309 
   291 def _ssha512_hash(password, scheme, encoding):
   310 def _ssha512_hash(password, scheme, encoding):
   292     """Generates SSHA512 (salted SHA512) hashes."""
   311     """Generates SSHA512 (salted SHA512) hashes."""
   293     if not COMPAT:
   312     if not COMPAT:
   294         salt = _get_salt(4)
   313         salt = _get_salt(SALTED_ALGO_SALT_LEN)
   295         sha512 = hashlib.sha512(password + salt)
   314         sha512 = hashlib.sha512(password + salt)
   296         if encoding in DEFAULT_B64:
   315         if encoding in DEFAULT_B64:
   297             digest = (sha512.digest() + salt).encode('base64').replace('\n',
   316             digest = (sha512.digest() + salt).encode('base64').replace('\n',
   298                                                                        '')
   317                                                                        '')
   299         else:
   318         else: