comparison roundup/password.py @ 4486:693c75d56ebe

Add new config-option 'password_pbkdf2_default_rounds'... ...in 'main' section to configure the default parameter for new password generation. Set this to a higher value on faster systems which want more security. Thanks to Eli Collins for implementing this (see issue2550688). This now passes a config object (default None in which case we fall back to hard-coded parameters) into the password generation routine. This way we can add further parameters for password generation in the future. Also added a small regression test for this new feature.
author Ralf Schlatterbeck <schlatterbeck@users.sourceforge.net>
date Fri, 15 Apr 2011 08:09:59 +0000
parents 95aace124a8e
children 357c6079c73b
comparison
equal deleted inserted replaced
4485:95aace124a8e 4486:693c75d56ebe
133 except ValueError: 133 except ValueError:
134 raise PasswordValueError, "invalid PBKDF2 hash (invalid rounds)" 134 raise PasswordValueError, "invalid PBKDF2 hash (invalid rounds)"
135 raw_salt = h64decode(salt) 135 raw_salt = h64decode(salt)
136 return rounds, salt, raw_salt, digest 136 return rounds, salt, raw_salt, digest
137 137
138 def encodePassword(plaintext, scheme, other=None): 138 def encodePassword(plaintext, scheme, other=None, config=None):
139 """Encrypt the plaintext password. 139 """Encrypt the plaintext password.
140 """ 140 """
141 if plaintext is None: 141 if plaintext is None:
142 plaintext = "" 142 plaintext = ""
143 if scheme == "PBKDF2": 143 if scheme == "PBKDF2":
144 if other: 144 if other:
145 rounds, salt, raw_salt, digest = pbkdf2_unpack(other) 145 rounds, salt, raw_salt, digest = pbkdf2_unpack(other)
146 else: 146 else:
147 raw_salt = getrandbytes(20) 147 raw_salt = getrandbytes(20)
148 salt = h64encode(raw_salt) 148 salt = h64encode(raw_salt)
149 #FIXME: find way to access config, so default rounds 149 if config:
150 # can be altered for faster/slower hosts via config.ini 150 rounds = config.PASSWORD_PBKDF2_DEFAULT_ROUNDS
151 rounds = 10000 151 else:
152 rounds = 10000
152 if rounds < 1000: 153 if rounds < 1000:
153 raise PasswordValueError, "invalid PBKDF2 hash (rounds too low)" 154 raise PasswordValueError, "invalid PBKDF2 hash (rounds too low)"
154 raw_digest = pbkdf2(plaintext, raw_salt, rounds, 20) 155 raw_digest = pbkdf2(plaintext, raw_salt, rounds, 20)
155 return "%d$%s$%s" % (rounds, salt, h64encode(raw_digest)) 156 return "%d$%s$%s" % (rounds, salt, h64encode(raw_digest))
156 elif scheme == 'SHA': 157 elif scheme == 'SHA':
241 #TODO: code to migrate from old password schemes. 242 #TODO: code to migrate from old password schemes.
242 243
243 deprecated_schemes = ["SHA", "MD5", "crypt", "plaintext"] 244 deprecated_schemes = ["SHA", "MD5", "crypt", "plaintext"]
244 known_schemes = ["PBKDF2"] + deprecated_schemes 245 known_schemes = ["PBKDF2"] + deprecated_schemes
245 246
246 def __init__(self, plaintext=None, scheme=None, encrypted=None, strict=False): 247 def __init__(self, plaintext=None, scheme=None, encrypted=None, strict=False, config=None):
247 """Call setPassword if plaintext is not None.""" 248 """Call setPassword if plaintext is not None."""
248 if scheme is None: 249 if scheme is None:
249 scheme = self.default_scheme 250 scheme = self.default_scheme
250 if plaintext is not None: 251 if plaintext is not None:
251 self.setPassword (plaintext, scheme) 252 self.setPassword (plaintext, scheme, config=config)
252 elif encrypted is not None: 253 elif encrypted is not None:
253 self.unpack(encrypted, scheme, strict=strict) 254 self.unpack(encrypted, scheme, strict=strict, config=config)
254 else: 255 else:
255 self.scheme = self.default_scheme 256 self.scheme = self.default_scheme
256 self.password = None 257 self.password = None
257 self.plaintext = None 258 self.plaintext = None
258 259
265 rounds, salt, raw_salt, digest = pbkdf2_unpack(self.password) 266 rounds, salt, raw_salt, digest = pbkdf2_unpack(self.password)
266 if rounds < 1000: 267 if rounds < 1000:
267 return True 268 return True
268 return False 269 return False
269 270
270 def unpack(self, encrypted, scheme=None, strict=False): 271 def unpack(self, encrypted, scheme=None, strict=False, config=None):
271 """Set the password info from the scheme:<encryted info> string 272 """Set the password info from the scheme:<encryted info> string
272 (the inverse of __str__) 273 (the inverse of __str__)
273 """ 274 """
274 m = self.pwre.match(encrypted) 275 m = self.pwre.match(encrypted)
275 if m: 276 if m:
276 self.scheme = m.group(1) 277 self.scheme = m.group(1)
277 self.password = m.group(2) 278 self.password = m.group(2)
278 self.plaintext = None 279 self.plaintext = None
279 else: 280 else:
280 # currently plaintext - encrypt 281 # currently plaintext - encrypt
281 self.setPassword(encrypted, scheme) 282 self.setPassword(encrypted, scheme, config=config)
282 if strict and self.scheme not in self.known_schemes: 283 if strict and self.scheme not in self.known_schemes:
283 raise PasswordValueError, "unknown encryption scheme: %r" % (self.scheme,) 284 raise PasswordValueError, "unknown encryption scheme: %r" % (self.scheme,)
284 285
285 def setPassword(self, plaintext, scheme=None): 286 def setPassword(self, plaintext, scheme=None, config=None):
286 """Sets encrypts plaintext.""" 287 """Sets encrypts plaintext."""
287 if scheme is None: 288 if scheme is None:
288 scheme = self.default_scheme 289 scheme = self.default_scheme
289 self.scheme = scheme 290 self.scheme = scheme
290 self.password = encodePassword(plaintext, scheme) 291 self.password = encodePassword(plaintext, scheme, config=config)
291 self.plaintext = plaintext 292 self.plaintext = plaintext
292 293
293 def __str__(self): 294 def __str__(self):
294 """Stringify the encrypted password for database storage.""" 295 """Stringify the encrypted password for database storage."""
295 if self.password is None: 296 if self.password is None:

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