diff --git a/roundup/password.py b/roundup/password.py
index 545baeb66690603808961c02c6617cc8ea84e03b..006c591be99cf39f6960b973bd3b48b8fa68b023 100644 (file)
--- a/roundup/password.py
+++ b/roundup/password.py
import re, string, random
from base64 import b64encode, b64decode
-from roundup.anypy.hashlib_ import md5, sha1
+from roundup.anypy.hashlib_ import md5, sha1, shamodule
try:
import crypt
except ImportError:
#no m2crypto - make our own pbkdf2 function
from struct import pack
from hmac import HMAC
- try:
- from hashlib import sha1
- except ImportError:
- from sha import new as sha1
def xor_bytes(left, right):
"perform bitwise-xor of two byte-strings"
def _pbkdf2(password, salt, rounds, keylen):
digest_size = 20 # sha1 generates 20-byte blocks
total_blocks = int((keylen+digest_size-1)/digest_size)
- hmac_template = HMAC(password, None, sha1)
+ hmac_template = HMAC(password, None, shamodule)
out = _bempty
for i in xrange(1, total_blocks+1):
hmac = hmac_template.copy()
""" The password value is not valid """
pass
-def encodePassword(plaintext, scheme, other=None):
+def pbkdf2_unpack(pbkdf2):
+ """ unpack pbkdf2 encrypted password into parts,
+ assume it has format "{rounds}${salt}${digest}
+ """
+ if isinstance(pbkdf2, unicode):
+ pbkdf2 = pbkdf2.encode("ascii")
+ try:
+ rounds, salt, digest = pbkdf2.split("$")
+ except ValueError:
+ raise PasswordValueError, "invalid PBKDF2 hash (wrong number of separators)"
+ if rounds.startswith("0"):
+ raise PasswordValueError, "invalid PBKDF2 hash (zero-padded rounds)"
+ try:
+ rounds = int(rounds)
+ except ValueError:
+ raise PasswordValueError, "invalid PBKDF2 hash (invalid rounds)"
+ raw_salt = h64decode(salt)
+ return rounds, salt, raw_salt, digest
+
+def encodePassword(plaintext, scheme, other=None, config=None):
"""Encrypt the plaintext password.
"""
if plaintext is None:
plaintext = ""
if scheme == "PBKDF2":
if other:
- #assume it has format "{rounds}${salt}${digest}"
- if isinstance(other, unicode):
- other = other.encode("ascii")
- try:
- rounds, salt, digest = other.split("$")
- except ValueError:
- raise PasswordValueError, "invalid PBKDF2 hash (wrong number of separators)"
- if rounds.startswith("0"):
- raise PasswordValueError, "invalid PBKDF2 hash (zero-padded rounds)"
- try:
- rounds = int(rounds)
- except ValueError:
- raise PasswordValueError, "invalid PBKDF2 hash (invalid rounds)"
- raw_salt = h64decode(salt)
+ rounds, salt, raw_salt, digest = pbkdf2_unpack(other)
else:
raw_salt = getrandbytes(20)
salt = h64encode(raw_salt)
- #FIXME: find way to access config, so default rounds
- # can be altered for faster/slower hosts via config.ini
- rounds = 10000
+ if config:
+ rounds = config.PASSWORD_PBKDF2_DEFAULT_ROUNDS
+ else:
+ rounds = 10000
if rounds < 1000:
raise PasswordValueError, "invalid PBKDF2 hash (rounds too low)"
raw_digest = pbkdf2(plaintext, raw_salt, rounds, 20)
"""
#TODO: code to migrate from old password schemes.
- known_schemes = [ "PBKDF2", "SHA", "MD5", "crypt", "plaintext" ]
+ deprecated_schemes = ["SHA", "MD5", "crypt", "plaintext"]
+ known_schemes = ["PBKDF2"] + deprecated_schemes
- def __init__(self, plaintext=None, scheme=None, encrypted=None, strict=False):
+ def __init__(self, plaintext=None, scheme=None, encrypted=None, strict=False, config=None):
"""Call setPassword if plaintext is not None."""
if scheme is None:
scheme = self.default_scheme
if plaintext is not None:
- self.setPassword (plaintext, scheme)
+ self.setPassword (plaintext, scheme, config=config)
elif encrypted is not None:
- self.unpack(encrypted, scheme, strict=strict)
+ self.unpack(encrypted, scheme, strict=strict, config=config)
else:
self.scheme = self.default_scheme
self.password = None
self.plaintext = None
- def unpack(self, encrypted, scheme=None, strict=False):
+ def needs_migration(self):
+ """ Password has insecure scheme or other insecure parameters
+ and needs migration to new password scheme
+ """
+ if self.scheme in self.deprecated_schemes:
+ return True
+ rounds, salt, raw_salt, digest = pbkdf2_unpack(self.password)
+ if rounds < 1000:
+ return True
+ return False
+
+ def unpack(self, encrypted, scheme=None, strict=False, config=None):
"""Set the password info from the scheme:<encryted info> string
(the inverse of __str__)
"""
self.plaintext = None
else:
# currently plaintext - encrypt
- self.setPassword(encrypted, scheme)
+ self.setPassword(encrypted, scheme, config=config)
if strict and self.scheme not in self.known_schemes:
raise PasswordValueError, "unknown encryption scheme: %r" % (self.scheme,)
- def setPassword(self, plaintext, scheme=None):
+ def setPassword(self, plaintext, scheme=None, config=None):
"""Sets encrypts plaintext."""
if scheme is None:
scheme = self.default_scheme
self.scheme = scheme
- self.password = encodePassword(plaintext, scheme)
+ self.password = encodePassword(plaintext, scheme, config=config)
self.plaintext = plaintext
def __str__(self):