2.8 MORE HASH, MORE TIME
EXERCISE 2.8 MORE HASH, MORE TIME
Choosing a complex-to-invert password is the responsibility of the user, but the systems storing the passwords can also slow down attackers by using a more complicated hashing function. Repeat any of the preceding exercies that use MD5, but now use SHA-1 and SHA-256 instead. Record how much longer it takes to get through the brute-force operations. Finally, try out bruteforce using scrypt. You might not get very far!
I will use exercise 2.5 by changing the hash function from MD5 to SHA-1, SHA-256, and Scrypt.
# ex2_8.py
import hashlib
import secrets
import timeit
from string import ascii_lowercase
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidKey
def h_sha1(x: str) -> str:
'''returns the sha1 digest of the string x.'''
return hashlib.sha1(x.encode('utf-8')).hexdigest()
def h_sha256(x: str) -> str:
'''returns the sha256 digest of the string x.'''
return hashlib.sha256(x.encode('utf-8')).hexdigest()
def h_scrypt(x: str) -> dict[str,bytes]:
'''returns the Scrypt digest of the string x.'''
= secrets.token_bytes(16)
salt = Scrypt(salt=salt, length=32, n=2**14, r=8, p=1, backend=default_backend())
kdf = kdf.derive(x.encode('utf-8'))
key return {
"salt": salt,
"key": key,
}
def h_scrypt_verify(x: str, salt: bytes, key: bytes):
= Scrypt(salt=salt, length=32, n=2**14, r=8, p=1, backend=default_backend())
kdf 'utf-8'), key)
kdf.verify(x.encode(
= {
hashing_functions "sha1": h_sha1,
"sha256": h_sha256,
"scrypt": h_scrypt,
}
def single_run(hashing_function_name: str):
= hashing_functions[hashing_function_name]
h
= secrets.choice(ascii_lowercase)
preimage_seed dict[str,bytes] | str = h(preimage_seed)
test_hash :
for single_letter in ascii_lowercase:
if hashing_function_name in ['sha1', 'sha256']:
if h(single_letter) == test_hash:
# found a match
break
elif hashing_function_name == "scrypt":
try:
# we know that test_hash is a dict[str, bytes].
**test_hash)
h_scrypt_verify(single_letter, break
except InvalidKey:
pass
else:
# Executes when the above loop terminates through exhaustion of the iterable.
# But not when the loop is terminated by a break statement.
raise AssertionError("the above loop should always break out!!")
return
if __name__ == '__main__':
= 10
COUNTER for hashing_function_name in hashing_functions.keys():
= timeit.timeit(
total_execution_time =f"single_run('{hashing_function_name}')",
stmt="from __main__ import single_run",
setup=COUNTER,
number
)print(f"Called single_run('{hashing_function_name}') {COUNTER} times. Total execution time (in seconds): {total_execution_time} sec")
print(f"Average execution time: {total_execution_time/COUNTER} sec")
print("\n--------------------------------\n")
When COUNTER
\(= 10\):
When COUNTER
\(= 30\):