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: |