Mercurial > p > roundup > code
comparison roundup/password.py @ 5488:52cb53eedf77
reworked random number use
prefer secrets module from Python 3.6+, random.SystemRandom and finally plain random
| author | Christof Meerwald <cmeerw@cmeerw.org> |
|---|---|
| date | Sat, 04 Aug 2018 22:40:16 +0100 |
| parents | 3d0f71775e42 |
| children | 11a1afa3cba4 |
comparison
equal
deleted
inserted
replaced
| 5487:ce171c81d823 | 5488:52cb53eedf77 |
|---|---|
| 17 # | 17 # |
| 18 """Password handling (encoding, decoding). | 18 """Password handling (encoding, decoding). |
| 19 """ | 19 """ |
| 20 __docformat__ = 'restructuredtext' | 20 __docformat__ = 'restructuredtext' |
| 21 | 21 |
| 22 import re, string, random | 22 import re, string |
| 23 import os | 23 import os |
| 24 from base64 import b64encode, b64decode | 24 from base64 import b64encode, b64decode |
| 25 from hashlib import md5, sha1 | 25 from hashlib import md5, sha1 |
| 26 | 26 |
| 27 from roundup.anypy.strings import us2s, b2s, s2b | 27 from roundup.anypy.strings import us2s, b2s, s2b |
| 28 import roundup.anypy.random_ as random_ | |
| 28 | 29 |
| 29 try: | 30 try: |
| 30 import crypt | 31 import crypt |
| 31 except ImportError: | 32 except ImportError: |
| 32 crypt = None | 33 crypt = None |
| 47 # Python 2. | 48 # Python 2. |
| 48 return ord(c) | 49 return ord(c) |
| 49 else: | 50 else: |
| 50 # Python 3. Elements of bytes are integers. | 51 # Python 3. Elements of bytes are integers. |
| 51 return c | 52 return c |
| 52 | |
| 53 def getrandbytes(count): | |
| 54 return _bjoin(bchr(random.randint(0,255)) for i in range(count)) | |
| 55 | 53 |
| 56 #NOTE: PBKDF2 hash is using this variant of base64 to minimize encoding size, | 54 #NOTE: PBKDF2 hash is using this variant of base64 to minimize encoding size, |
| 57 # and have charset that's compatible w/ unix crypt variants | 55 # and have charset that's compatible w/ unix crypt variants |
| 58 def h64encode(data): | 56 def h64encode(data): |
| 59 """encode using variant of base64""" | 57 """encode using variant of base64""" |
| 165 plaintext = "" | 163 plaintext = "" |
| 166 if scheme == "PBKDF2": | 164 if scheme == "PBKDF2": |
| 167 if other: | 165 if other: |
| 168 rounds, salt, raw_salt, digest = pbkdf2_unpack(other) | 166 rounds, salt, raw_salt, digest = pbkdf2_unpack(other) |
| 169 else: | 167 else: |
| 170 raw_salt = getrandbytes(20) | 168 raw_salt = random_.token_bytes(20) |
| 171 salt = h64encode(raw_salt) | 169 salt = h64encode(raw_salt) |
| 172 if config: | 170 if config: |
| 173 rounds = config.PASSWORD_PBKDF2_DEFAULT_ROUNDS | 171 rounds = config.PASSWORD_PBKDF2_DEFAULT_ROUNDS |
| 174 else: | 172 else: |
| 175 rounds = 10000 | 173 rounds = 10000 |
| 182 raw_other = b64decode(other) | 180 raw_other = b64decode(other) |
| 183 salt = raw_other[20:] | 181 salt = raw_other[20:] |
| 184 else: | 182 else: |
| 185 #new password | 183 #new password |
| 186 # variable salt length | 184 # variable salt length |
| 187 salt_len = random.randrange(36, 52) | 185 salt_len = random_.randbelow(52-36) + 36 |
| 188 salt = os.urandom(salt_len) | 186 salt = random_.token_bytes(salt_len) |
| 189 s = ssha(s2b(plaintext), salt) | 187 s = ssha(s2b(plaintext), salt) |
| 190 elif scheme == 'SHA': | 188 elif scheme == 'SHA': |
| 191 s = sha1(s2b(plaintext)).hexdigest() | 189 s = sha1(s2b(plaintext)).hexdigest() |
| 192 elif scheme == 'MD5': | 190 elif scheme == 'MD5': |
| 193 s = md5(s2b(plaintext)).hexdigest() | 191 s = md5(s2b(plaintext)).hexdigest() |
| 194 elif scheme == 'crypt' and crypt is not None: | 192 elif scheme == 'crypt' and crypt is not None: |
| 195 if other is not None: | 193 if other is not None: |
| 196 salt = other | 194 salt = other |
| 197 else: | 195 else: |
| 198 saltchars = './0123456789'+string.ascii_letters | 196 saltchars = './0123456789'+string.ascii_letters |
| 199 salt = random.choice(saltchars) + random.choice(saltchars) | 197 salt = random_.choice(saltchars) + random_.choice(saltchars) |
| 200 s = crypt.crypt(plaintext, salt) | 198 s = crypt.crypt(plaintext, salt) |
| 201 elif scheme == 'plaintext': | 199 elif scheme == 'plaintext': |
| 202 s = plaintext | 200 s = plaintext |
| 203 else: | 201 else: |
| 204 raise PasswordValueError('Unknown encryption scheme %r'%scheme) | 202 raise PasswordValueError('Unknown encryption scheme %r'%scheme) |
| 205 return s | 203 return s |
| 206 | 204 |
| 207 def generatePassword(length=12): | 205 def generatePassword(length=12): |
| 208 chars = string.ascii_letters+string.digits | 206 chars = string.ascii_letters+string.digits |
| 209 password = [random.choice(chars) for x in range(length)] | 207 password = [random_.choice(chars) for x in range(length - 1)] |
| 210 # make sure there is at least one digit | 208 # make sure there is at least one digit |
| 211 password[0] = random.choice(string.digits) | 209 digitidx = random_.randbelow(length) |
| 212 random.shuffle(password) | 210 password[digitidx:digitidx] = [random_.choice(string.digits)] |
| 213 return ''.join(password) | 211 return ''.join(password) |
| 214 | 212 |
| 215 class JournalPassword: | 213 class JournalPassword: |
| 216 """ Password dummy instance intended for journal operation. | 214 """ Password dummy instance intended for journal operation. |
| 217 We do not store passwords in the journal any longer. The dummy | 215 We do not store passwords in the journal any longer. The dummy |
