From 2b4227c5fb7bb1aa1aae7681fa03f956d4a687d4 Mon Sep 17 00:00:00 2001 From: lhark Date: Wed, 6 Jul 2016 22:44:30 +0200 Subject: [PATCH] Custom password hasher for SSHA --- re2o/login.py | 76 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 8 deletions(-) diff --git a/re2o/login.py b/re2o/login.py index 566f2873..a5ae1324 100644 --- a/re2o/login.py +++ b/re2o/login.py @@ -2,29 +2,89 @@ # Module d'authentification # David Sinquin, Gabriel Détraz, Goulven Kermarec -import hashlib, binascii + +import hashlib +import binascii import os -from base64 import urlsafe_b64encode as encode -from base64 import urlsafe_b64decode as decode +from base64 import encodestring +from base64 import decodestring +from collections import OrderedDict + +from django.contrib.auth import hashers + + +ALGO_NAME = "{SSHA}" +ALGO_LEN = len(ALGO_NAME + "$") +DIGEST_LEN = 20 + def makeSecret(password): salt = os.urandom(4) h = hashlib.sha1(password.encode()) h.update(salt) - return "{SSHA}" + encode(h.digest() + salt).decode() + return ALGO_NAME + "$" + encodestring(h.digest() + salt).decode()[:-1] + def hashNT(password): hash = hashlib.new('md4', password.encode()).digest() return binascii.hexlify(hash) + def checkPassword(challenge_password, password): - challenge_bytes = decode(challenge_password[6:]) - digest = challenge_bytes[:20] - salt = challenge_bytes[20:] + challenge_bytes = decodestring(challenge_password[ALGO_LEN:].encode()) + digest = challenge_bytes[:DIGEST_LEN] + salt = challenge_bytes[DIGEST_LEN:] hr = hashlib.sha1(password.encode()) hr.update(salt) valid_password = True - # La comparaison est volontairement en temps constant (pour éviter les timing-attacks) + # La comparaison est volontairement en temps constant + # (pour éviter les timing-attacks) for i, j in zip(digest, hr.digest()): valid_password &= i == j return valid_password + + +class SSHAPasswordHasher(hashers.BasePasswordHasher): + """ + SSHA password hashing to allow for LDAP auth compatibility + """ + + algorithm = ALGO_NAME + + def encode(self, password, salt, iterations=None): + """ + Hash and salt the given password using SSHA algorithm + + salt is overridden + """ + assert password is not None + return makeSecret(password) + + def verify(self, password, encoded): + """ + Check password against encoded using SSHA algorithm + """ + assert encoded.startswith(self.algorithm) + return checkPassword(encoded, password) + + def safe_summary(self, encoded): + """ + Provides a safe summary ofthe password + """ + assert encoded.startswith(self.algorithm) + hash = encoded[ALGO_LEN:] + hash = binascii.hexlify(decodestring(hash.encode())).decode() + return OrderedDict([ + ('algorithm', self.algorithm), + ('iterations', 0), + ('salt', hashers.mask_hash(hash[2*DIGEST_LEN:], show=2)), + ('hash', hashers.mask_hash(hash[: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