diff --git a/roundup/password.py b/roundup/password.py
index fcf8386bdc131afab8d096bd3d5fe6aff5d583a6..92ada54a1fa8756e2d62ad6e9d5580efcbcad3ad 100644 (file)
--- a/roundup/password.py
+++ b/roundup/password.py
""" The password value is not valid """
pass
+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):
"""Encrypt the plaintext password.
"""
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)
chars = string.letters+string.digits
return ''.join([random.choice(chars) for x in range(length)])
-class Password:
+class JournalPassword:
+ """ Password dummy instance intended for journal operation.
+ We do not store passwords in the journal any longer. The dummy
+ version only reads the encryption scheme from the given
+ encrypted password.
+ """
+ default_scheme = 'PBKDF2' # new encryptions use this scheme
+ pwre = re.compile(r'{(\w+)}(.+)')
+
+ def __init__ (self, encrypted=''):
+ if isinstance(encrypted, self.__class__):
+ self.scheme = encrypted.scheme or self.default_scheme
+ else:
+ m = self.pwre.match(encrypted)
+ if m:
+ self.scheme = m.group(1)
+ else:
+ self.scheme = self.default_scheme
+ self.password = ''
+
+ def dummystr(self):
+ """ return dummy string to store in journal
+ - reports scheme, but nothing else
+ """
+ return "{%s}*encrypted*" % (self.scheme,)
+
+ __str__ = dummystr
+
+ def __cmp__(self, other):
+ """Compare this password against another password."""
+ # check to see if we're comparing instances
+ if isinstance(other, self.__class__):
+ if self.scheme != other.scheme:
+ return cmp(self.scheme, other.scheme)
+ return cmp(self.password, other.password)
+
+ # assume password is plaintext
+ if self.password is None:
+ raise ValueError, 'Password not set'
+ return cmp(self.password, encodePassword(other, self.scheme,
+ self.password or None))
+
+class Password(JournalPassword):
"""The class encapsulates a Password property type value in the database.
The encoding of the password is one if None, 'SHA', 'MD5' or 'plaintext'.
"""
#TODO: code to migrate from old password schemes.
- default_scheme = 'PBKDF2' # new encryptions use this scheme
known_schemes = [ "PBKDF2", "SHA", "MD5", "crypt", "plaintext" ]
- pwre = re.compile(r'{(\w+)}(.+)')
def __init__(self, plaintext=None, scheme=None, encrypted=None, strict=False):
"""Call setPassword if plaintext is not None."""
self.password = None
self.plaintext = None
+ def needs_migration(self):
+ """ Password has insecure scheme or other insecure parameters
+ and needs migration to new password scheme
+ """
+ if self.scheme != 'PBKDF2':
+ 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):
"""Set the password info from the scheme:<encryted info> string
(the inverse of __str__)
self.password = encodePassword(plaintext, scheme)
self.plaintext = plaintext
- def __cmp__(self, other):
- """Compare this password against another password."""
- # check to see if we're comparing instances
- if isinstance(other, Password):
- if self.scheme != other.scheme:
- return cmp(self.scheme, other.scheme)
- return cmp(self.password, other.password)
-
- # assume password is plaintext
- if self.password is None:
- raise ValueError, 'Password not set'
- return cmp(self.password, encodePassword(other, self.scheme,
- self.password))
-
def __str__(self):
"""Stringify the encrypted password for database storage."""
if self.password is None: