diff roundup/password.py @ 5053:9792b18e0b19

issue 2550880: Ability to choose password store scheme and SSHA support. Discussion on list is tending in favor of this patch. Embedded test works, my manual test with a SSHA password assigned to a user allowed the user to log in. Ran the test suite and the tests that were not skipped passed.
author John Rouillard <rouilj@ieee.org>
date Sat, 16 Apr 2016 22:49:38 -0400
parents 9ba03348f923
children 66a17c80e035
line wrap: on
line diff
--- a/roundup/password.py	Sat Apr 09 01:15:22 2016 -0400
+++ b/roundup/password.py	Sat Apr 16 22:49:38 2016 -0400
@@ -20,6 +20,7 @@
 __docformat__ = 'restructuredtext'
 
 import re, string, random
+import os
 from base64 import b64encode, b64decode
 from hashlib import md5, sha1
 
@@ -81,6 +82,16 @@
             out += block
         return out[:keylen]
 
+def ssha(password, salt):
+    ''' Make ssha digest from password and salt.
+    Based on code of Roberto Aguilar <roberto@baremetal.io>
+    https://gist.github.com/rca/7217540
+    '''
+    shaval = sha1(password)
+    shaval.update( salt )
+    ssha_digest = b64encode( '{}{}'.format(shaval.digest(), salt) ).strip()
+    return ssha_digest
+
 def pbkdf2(password, salt, rounds, keylen):
     """pkcs#5 password-based key derivation v2.0
 
@@ -149,6 +160,16 @@
             raise PasswordValueError, "invalid PBKDF2 hash (rounds too low)"
         raw_digest = pbkdf2(plaintext, raw_salt, rounds, 20)
         return "%d$%s$%s" % (rounds, salt, h64encode(raw_digest))
+    elif scheme == 'SSHA':
+        if other:
+            raw_other = b64decode(other)
+            salt = raw_other[20:]
+        else:
+            #new password
+            # variable salt length
+            salt_len = random.randrange(36, 52)
+            salt = os.urandom(salt_len)
+        s = ssha(plaintext, salt)
     elif scheme == 'SHA':
         s = sha1(plaintext).hexdigest()
     elif scheme == 'MD5':
@@ -241,7 +262,7 @@
     #TODO: code to migrate from old password schemes.
 
     deprecated_schemes = ["SHA", "MD5", "crypt", "plaintext"]
-    known_schemes = ["PBKDF2"] + deprecated_schemes
+    known_schemes = ["PBKDF2", "SSHA"] + deprecated_schemes
 
     def __init__(self, plaintext=None, scheme=None, encrypted=None, strict=False, config=None):
         """Call setPassword if plaintext is not None."""
@@ -319,6 +340,13 @@
         assert 'sekrit' == p
         assert 'not sekrit' != p
 
+    # SSHA
+    p = Password('sekrit', 'SSHA')
+    assert p == 'sekrit'
+    assert p != 'not sekrit'
+    assert 'sekrit' == p
+    assert 'not sekrit' != p
+
     # PBKDF2 - low level function
     from binascii import unhexlify
     k = pbkdf2("password", "ATHENA.MIT.EDUraeburn", 1200, 32)

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