diff --git a/re2o/login.py b/re2o/login.py index b867e836..4f4150e2 100644 --- a/re2o/login.py +++ b/re2o/login.py @@ -33,8 +33,10 @@ import binascii import os from base64 import encodestring from base64 import decodestring +from base64 import b64encode +from base64 import b64decode from collections import OrderedDict - +import crypt from django.contrib.auth import hashers @@ -72,6 +74,117 @@ def checkPassword(challenge_password, password): return valid_password +def hash_password_salt(hashed_password): + """ Extract the salt from a given hashed password """ + if hashed_password.upper().startswith('{CRYPT}'): + hashed_password = hashed_password[7:] + if hashed_password.startswith('$'): + return '$'.join(hashed_password.split('$')[:-1]) + else: + return hashed_password[:2] + elif hashed_password.upper().startswith('{SSHA}'): + try: + digest = b64decode(hashed_password[6:]) + except TypeError as error: + raise ValueError("b64 error for `hashed_password` : %s" % error) + if len(digest) < 20: + raise ValueError("`hashed_password` too short") + return digest[20:] + elif hashed_password.upper().startswith('{SMD5}'): + try: + digest = b64decode(hashed_password[7:]) + except TypeError as error: + raise ValueError("b64 error for `hashed_password` : %s" % error) + if len(digest) < 16: + raise ValueError("`hashed_password` too short") + return digest[16:] + else: + raise ValueError("`hashed_password` should start with '{SSHA}' or '{CRYPT}' or '{SMD5}'") + + + +class CryptPasswordHasher(hashers.BasePasswordHasher): + """ + Crypt password hashing to allow for LDAP auth compatibility + We do not encode, this should bot be used ! + """ + + algorithm = "{crypt}" + + def encode(self, password, salt): + pass + + def verify(self, password, encoded): + """ + Check password against encoded using SSHA algorithm + """ + assert encoded.startswith(self.algorithm) + salt = hash_password_salt(challenge_password) + return crypt.crypt(password.encode(), salt) == challenge.encode() + + def safe_summary(self, encoded): + """ + Provides a safe summary of the password + """ + assert encoded.startswith(self.algorithm) + hash_str = encoded[7:] + hash_str = binascii.hexlify(decodestring(hash_str.encode())).decode() + return OrderedDict([ + ('algorithm', self.algorithm), + ('iterations', 0), + ('salt', hashers.mask_hash(hash_str[2*DIGEST_LEN:], show=2)), + ('hash', hashers.mask_hash(hash_str[:2*DIGEST_LEN])), + ]) + + def harden_runtime(self, password, encoded): + """ + Method implemented to shut up BasePasswordHasher warning + + As we are not using multiple iterations the method is pretty useless + """ + pass + +class MD5PasswordHasher(hashers.BasePasswordHasher): + """ + MD5 password hashing to allow for LDAP auth compatibility + We do not encode, this should bot be used ! + """ + + algorithm = "{SMD5}" + + def encode(self, password, salt): + pass + + def verify(self, password, encoded): + """ + Check password against encoded using SSHA algorithm + """ + assert encoded.startswith(self.algorithm) + salt = hash_password_salt(encoded) + return b64encode(hashlib.md5(password.encode() + salt).digest() + salt) == encoded.encode() + + def safe_summary(self, encoded): + """ + Provides a safe summary of the password + """ + assert encoded.startswith(self.algorithm) + hash_str = encoded[7:] + hash_str = binascii.hexlify(decodestring(hash_str.encode())).decode() + return OrderedDict([ + ('algorithm', self.algorithm), + ('iterations', 0), + ('salt', hashers.mask_hash(hash_str[2*DIGEST_LEN:], show=2)), + ('hash', hashers.mask_hash(hash_str[:2*DIGEST_LEN])), + ]) + + def harden_runtime(self, password, encoded): + """ + Method implemented to shut up BasePasswordHasher warning + + As we are not using multiple iterations the method is pretty useless + """ + pass + class SSHAPasswordHasher(hashers.BasePasswordHasher): """ SSHA password hashing to allow for LDAP auth compatibility diff --git a/re2o/settings.py b/re2o/settings.py index 71bd266f..b68e4997 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -46,6 +46,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Auth definition PASSWORD_HASHERS = ( 're2o.login.SSHAPasswordHasher', + 're2o.login.MD5PasswordHasher', + 're2o.login.CryptPasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', ) AUTH_USER_MODEL = 'users.User' # The class to use for authentication