11 hashed_password = pwhash(password[, scheme][, user]) |
11 hashed_password = pwhash(password[, scheme][, user]) |
12 random_password = randompw() |
12 random_password = randompw() |
13 """ |
13 """ |
14 |
14 |
15 from crypt import crypt |
15 from crypt import crypt |
16 from random import choice, shuffle |
16 from random import SystemRandom |
17 from subprocess import Popen, PIPE |
17 from subprocess import Popen, PIPE |
18 |
18 |
19 try: |
19 try: |
20 import hashlib |
20 import hashlib |
21 except ImportError: |
21 except ImportError: |
33 DEFAULT_B64 = (None, 'B64', 'BASE64') |
33 DEFAULT_B64 = (None, 'B64', 'BASE64') |
34 DEFAULT_HEX = (None, 'HEX') |
34 DEFAULT_HEX = (None, 'HEX') |
35 |
35 |
36 _ = lambda msg: msg |
36 _ = lambda msg: msg |
37 cfg_dget = lambda option: None |
37 cfg_dget = lambda option: None |
38 _get_salt = lambda s_len: ''.join(choice(SALTCHARS) for x in xrange(s_len)) |
38 _sys_rand = SystemRandom() |
|
39 _get_salt = lambda salt_len: ''.join(_sys_rand.sample(SALTCHARS, salt_len)) |
|
40 |
|
41 |
|
42 def _test_crypt_algorithms(): |
|
43 """Check for Blowfish/SHA-256/SHA-512 support in crypt.crypt().""" |
|
44 blowfish_ = sha256_ = sha512_ = False |
|
45 _blowfish = '$2a$04$0123456789abcdefABCDE.N.drYX5yIAL1LkTaaZotW3yI0hQhZru' |
|
46 _sha256 = '$5$rounds=1000$0123456789abcdef$K/DksR0DT01hGc8g/kt9McEgrbFMKi\ |
|
47 9qrb1jehe7hn4' |
|
48 _sha512 = '$6$rounds=1000$0123456789abcdef$ZIAd5WqfyLkpvsVCVUU1GrvqaZTqvh\ |
|
49 JoouxdSqJO71l9Ld3tVrfOatEjarhghvEYADkq//LpDnTeO90tcbtHR1' |
|
50 |
|
51 if crypt('08/15!test~4711', '$2a$04$0123456789abcdefABCDEF$') == _blowfish: |
|
52 blowfish_ = True |
|
53 if crypt('08/15!test~4711', '$5$rounds=1000$0123456789abcdef$') == _sha256: |
|
54 sha256_ = True |
|
55 if crypt('08/15!test~4711', '$6$rounds=1000$0123456789abcdef$') == _sha512: |
|
56 sha512_ = True |
|
57 return blowfish_, sha256_, sha512_ |
|
58 |
|
59 CRYPT_BLOWFISH, CRYPT_SHA256, CRYPT_SHA512 = _test_crypt_algorithms() |
39 |
60 |
40 |
61 |
41 def _dovecotpw(password, scheme, encoding): |
62 def _dovecotpw(password, scheme, encoding): |
42 """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns |
63 """Communicates with dovecotpw (Dovecot 2.0: `doveadm pw`) and returns |
43 the hashed password: {scheme[.encoding]}hash |
64 the hashed password: {scheme[.encoding]}hash |
108 password = password.encode('base64').replace('\n', '') |
129 password = password.encode('base64').replace('\n', '') |
109 return _format_digest(password, scheme, encoding) |
130 return _format_digest(password, scheme, encoding) |
110 return get_unicode('{%s}%s' % (scheme, password)) |
131 return get_unicode('{%s}%s' % (scheme, password)) |
111 |
132 |
112 |
133 |
|
134 def _get_crypt_blowfish_salt(): |
|
135 """Generates a salt for Blowfish crypt.""" |
|
136 rounds = cfg_dget('misc.crypt_blowfish_rounds') |
|
137 if rounds < 4: |
|
138 rounds = 4 |
|
139 elif rounds > 31: |
|
140 rounds = 31 |
|
141 return '$2a$%02d$%s$' % (rounds, _get_salt(22)) |
|
142 |
|
143 |
|
144 def _get_crypt_shaxxx_salt(crypt_id): |
|
145 """Generates a salt for crypt using the SHA-256 or SHA-512 encryption |
|
146 method. |
|
147 *crypt_id* must be either `5` (SHA-256) or `6` (SHA1-512). |
|
148 """ |
|
149 assert crypt_id in (5, 6), 'invalid crypt id: %r' % crypt_id |
|
150 if crypt_id is 6: |
|
151 rounds = cfg_dget('misc.crypt_sha512_rounds') |
|
152 else: |
|
153 rounds = cfg_dget('misc.crypt_sha256_rounds') |
|
154 if rounds < 1000: |
|
155 rounds = 1000 |
|
156 elif rounds > 999999999: |
|
157 rounds = 999999999 |
|
158 return '$%d$rounds=%d$%s$' % (crypt_id, rounds, _get_salt(16)) |
|
159 |
|
160 |
113 def _crypt_hash(password, scheme, encoding): |
161 def _crypt_hash(password, scheme, encoding): |
114 """Generates (encoded) CRYPT/MD5/MD5-CRYPT hashes.""" |
162 """Generates (encoded) CRYPT/MD5/MD5-CRYPT hashes.""" |
115 if scheme == 'CRYPT': |
163 if scheme == 'CRYPT': |
116 salt = _get_salt(2) |
164 if CRYPT_BLOWFISH and cfg_dget('misc.crypt_blowfish_rounds'): |
|
165 salt = _get_crypt_blowfish_salt() |
|
166 elif CRYPT_SHA512 and cfg_dget('misc.crypt_sha512_rounds'): |
|
167 salt = _get_crypt_shaxxx_salt(6) |
|
168 elif CRYPT_SHA256 and cfg_dget('misc.crypt_sha256_rounds'): |
|
169 salt = _get_crypt_shaxxx_salt(5) |
|
170 else: |
|
171 salt = _get_salt(2) |
117 else: |
172 else: |
118 salt = '$1$%s$' % _get_salt(8) |
173 salt = '$1$%s$' % _get_salt(8) |
119 encrypted = crypt(password, salt) |
174 encrypted = crypt(password, salt) |
120 if encoding: |
175 if encoding: |
121 if encoding == 'HEX': |
176 if encoding == 'HEX': |
122 encrypted = encrypted.encode('hex') |
177 encrypted = encrypted.encode('hex') |
123 else: |
178 else: |
124 encrypted = encrypted.encode('base64').rstrip() |
179 encrypted = encrypted.encode('base64').replace('\n', '') |
125 return _format_digest(encrypted, scheme, encoding) |
180 return _format_digest(encrypted, scheme, encoding) |
126 |
181 |
127 |
182 |
128 def _md4_hash(password, scheme, encoding): |
183 def _md4_hash(password, scheme, encoding): |
129 """Generates encoded PLAIN-MD4 hashes.""" |
184 """Generates encoded PLAIN-MD4 hashes.""" |
333 """Generates a plain text random password. |
388 """Generates a plain text random password. |
334 |
389 |
335 The length of the password can be configured in the ``vmm.cfg`` |
390 The length of the password can be configured in the ``vmm.cfg`` |
336 (account.password_length). |
391 (account.password_length). |
337 """ |
392 """ |
338 pw_chars = list(PASSWDCHARS) |
|
339 shuffle(pw_chars) |
|
340 pw_len = cfg_dget('account.password_length') |
393 pw_len = cfg_dget('account.password_length') |
341 if pw_len < 8: |
394 if pw_len < 8: |
342 pw_len = 8 |
395 pw_len = 8 |
343 return ''.join(choice(pw_chars) for x in xrange(pw_len)) |
396 return ''.join(_sys_rand.sample(PASSWDCHARS, pw_len)) |
344 |
397 |
345 del _, cfg_dget |
398 del _, cfg_dget, _test_crypt_algorithms |