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

Roundup Issue Tracker: http://roundup-tracker.org/