Unit test for constant time string check

main
Bob Mottram 2020-09-03 19:48:32 +01:00
parent 7e87bbe2aa
commit e584076aca
2 changed files with 52 additions and 18 deletions

41
auth.py
View File

@ -34,6 +34,28 @@ def getPasswordHash(salt: str, providedPassword: str) -> str:
return binascii.hexlify(pwdhash).decode('ascii') return binascii.hexlify(pwdhash).decode('ascii')
def constantTimeStringCheck(string1: str, string2: str) -> bool:
"""Compares two string and returns if they are the same
using a constant amount of time
See https://sqreen.github.io/DevelopersSecurityBestPractices/
timing-attack/python
"""
# strings must be of equal length
if len(string1) != len(string2):
return False
ctr = 0
matched = True
for ch in string1:
if ch != string2[ctr]:
matched = False
else:
# this is to make the timing more even
# and not provide clues
matched = matched
ctr += 1
return matched
def verifyPassword(storedPassword: str, providedPassword: str) -> bool: def verifyPassword(storedPassword: str, providedPassword: str) -> bool:
"""Verify a stored password against one provided by user """Verify a stored password against one provided by user
""" """
@ -44,24 +66,7 @@ def verifyPassword(storedPassword: str, providedPassword: str) -> bool:
salt = storedPassword[:64] salt = storedPassword[:64]
storedPassword = storedPassword[64:] storedPassword = storedPassword[64:]
pwHash = getPasswordHash(salt, providedPassword) pwHash = getPasswordHash(salt, providedPassword)
# check that hashes are of equal length return constantTimeStringCheck(pwHash, storedPassword)
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
else:
# this is to make the timing more even
# and not provide clues
matched = matched
ctr += 1
return matched
def createBasicAuthHeader(nickname: str, password: str) -> str: def createBasicAuthHeader(nickname: str, password: str) -> str:

View File

@ -54,6 +54,7 @@ from person import setBio
from skills import setSkillLevel from skills import setSkillLevel
from roles import setRole from roles import setRole
from roles import outboxDelegate from roles import outboxDelegate
from auth import constantTimeStringCheck
from auth import createBasicAuthHeader from auth import createBasicAuthHeader
from auth import authorizeBasic from auth import authorizeBasic
from auth import storeBasicCredentials from auth import storeBasicCredentials
@ -2085,8 +2086,36 @@ def testTranslations():
assert langJson.get(englishStr) assert langJson.get(englishStr)
def testConstantTimeStringCheck():
print('testConstantTimeStringCheck')
assert constantTimeStringCheck('testing', 'testing')
assert not constantTimeStringCheck('testing', '1234')
assert not constantTimeStringCheck('testing', '1234567')
itterations = 256
start = time.time()
for timingTest in range(itterations):
constantTimeStringCheck('nnjfbefefbsnjsdnvbcueftqfeuqfbqefnjeniwufgy',
'nnjfbefefbsnjsdnvbcueftqfeuqfbqefnjeniwufgy')
end = time.time()
avTime1 = ((end - start) * 1000000 / itterations)
# change characters and observe timing difference
start = time.time()
for timingTest in range(itterations):
constantTimeStringCheck('nnjfbefefbsnjsdnvbcueftqfeuqfbqefnjeniwufgy',
'nnjfbefefbsnjsdnvbcueftqfeuqfbqeznjeniwufgy')
end = time.time()
avTime2 = ((end - start) * 1000000 / itterations)
timeDiffMicroseconds = abs(avTime2 - avTime1)
# time difference should be less than 10uS
assert timeDiffMicroseconds < 10
def runAllTests(): def runAllTests():
print('Running tests...') print('Running tests...')
testConstantTimeStringCheck()
testTranslations() testTranslations()
testValidContentWarning() testValidContentWarning()
testRemoveIdEnding() testRemoveIdEnding()