From 6568be91ff8864d0bc55c158e978cc441e17aa0a Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Thu, 3 Sep 2020 19:07:02 +0100 Subject: [PATCH] Constant time password hash match --- auth.py | 35 ++++++++++++++++++++++++++++------- tests.py | 1 - 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/auth.py b/auth.py index 7ac45cecf..07cf91af2 100644 --- a/auth.py +++ b/auth.py @@ -10,7 +10,6 @@ import base64 import hashlib import binascii import os -import secrets def hashPassword(password: str) -> str: @@ -24,17 +23,39 @@ def hashPassword(password: str) -> str: return (salt + pwdhash).decode('ascii') -def verifyPassword(storedPassword: str, providedPassword: str) -> bool: - """Verify a stored password against one provided by user +def getPasswordHash(salt: str, providedPassword: str) -> str: + """Returns the hash of a password """ - salt = storedPassword[:64] - storedPassword = storedPassword[64:] pwdhash = hashlib.pbkdf2_hmac('sha512', providedPassword.encode('utf-8'), salt.encode('ascii'), 100000) - pwdhash = binascii.hexlify(pwdhash).decode('ascii') - return pwdhash == storedPassword + return binascii.hexlify(pwdhash).decode('ascii') + +def verifyPassword(storedPassword: str, providedPassword: str) -> bool: + """Verify a stored password against one provided by user + """ + if not storedPassword: + return False + if not providedPassword: + return False + salt = storedPassword[:64] + storedPassword = storedPassword[64:] + pwHash = getPasswordHash(salt, providedPassword) + # check that hashes are of equal length + if len(pwHash) != len(storedPassword): + return False + # Compare all of the characters before returning true or false. + # Hence the match should take a constant amount of time. + # See https://sqreen.github.io/DevelopersSecurityBestPractices/ + # timing-attack/python + ctr = 0 + matched = True + for ch in pwHash: + if ch != storedPassword[ctr]: + matched = False + ctr += 1 + return matched def createBasicAuthHeader(nickname: str, password: str) -> str: diff --git a/tests.py b/tests.py index 127d0c798..7c5c2865b 100644 --- a/tests.py +++ b/tests.py @@ -2084,7 +2084,6 @@ def testTranslations(): print(englishStr + ' is missing from ' + lang + '.json') assert langJson.get(englishStr) - def runAllTests(): print('Running tests...') testTranslations()