bcrypt-argon2-pbkdf2-hash-test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# bcrypt-argon2-pbkdf2-hash-test.py | |
import threading | |
import bcrypt | |
from pwdlib.hashers import argon2 | |
from pwdlib import PasswordHash | |
from pwdlib.hashers.argon2 import Argon2Hasher | |
from hashlib import pbkdf2_hmac | |
import time | |
import psutil | |
from statistics import median | |
def monitor_cpu(): | |
while not stop_thread: | |
global cpu_load | |
cpu_load.append(psutil.cpu_percent(interval=1, percpu=True)) | |
def hash_password(algorithm, work_factor, memory_cost=None): | |
password = b"your_secret_password" | |
start_time = time.time() | |
if algorithm == 'bcrypt': | |
_ = bcrypt.hashpw(password, bcrypt.gensalt(work_factor)) | |
elif algorithm == 'pbkdf2': | |
# Assuming salt is also bytes; normally should be securely generated | |
salt = b'some_random_salt' | |
# pbkdf2_hmac requires the number of iterations, the salt, the desired hash name, and the password | |
_ = pbkdf2_hmac('sha256', password, salt, work_factor) | |
else: | |
# assume argon2 | |
if memory_cost == None: | |
memory_cost = 65536 # 64 MiB; see: https://github.com/hynek/argon2-cffi/blob/0805dbdded04dc3c4bc8573236c80be50ff30113/src/argon2/profiles.py#L30-L38 | |
password_hash = PasswordHash(( | |
Argon2Hasher(time_cost=work_factor, memory_cost=memory_cost), | |
)) | |
_ = password_hash.hash(password) | |
end_time = time.time() | |
if algorithm == 'argon2': | |
print(f"Hashing with {algorithm} work factor of {work_factor} and memory cost of {memory_cost} took {end_time - start_time:.3f} seconds.") | |
else: | |
print(f"Hashing with {algorithm} work factor of {work_factor} took {end_time - start_time:.3f} seconds.") | |
if __name__ == "__main__": | |
# select from: 'pbkdf2', 'argon2' or 'bcrypt' | |
algorithm = 'argon2' | |
# for argon2 | |
memory_cost = 256 * 1024 # 256 MiB; see: https://github.com/sparrowwallet/drongo/blob/master/src/main/java/com/sparrowwallet/drongo/crypto/Argon2KeyDeriver.java | |
min_work = 5 | |
max_work = 30 | |
# for bcrypt | |
if algorithm == 'bcrypt': | |
if max_work > 20: | |
max_work = 20 | |
# for argon2 / bcrypt | |
work_factors = [x for x in range(min_work, max_work+1)] | |
# for pbkdf2 | |
if algorithm == 'pbkdf2': | |
min_work = 700_000 | |
max_work = 1_000_000 | |
work_factors = [x for x in range(min_work, max_work+1, 20_000)] # Increment by 20000 for noticeable differences | |
for work_factor in work_factors: | |
cpu_load = [] | |
stop_thread = False | |
thread = threading.Thread(target=monitor_cpu) | |
thread.start() | |
try: | |
if algorithm in ['bcrypt', 'pbkdf2']: | |
hash_password(algorithm, work_factor) | |
elif algorithm == 'argon2': | |
hash_password(algorithm, work_factor, memory_cost) | |
else: | |
raise Exception(f"algorithm '{algorithm}' not configured") | |
finally: | |
stop_thread = True | |
thread.join() | |
all_max = [] | |
all_avg = [] | |
for index, cpu in enumerate(cpu_load): | |
all_max.append(max(cpu)) | |
all_avg.append(sum(cpu) / len(cpu)) | |
print(f"Max single CPU core usage: {max(all_max):.2f}%") | |
print(f"Average usage all cores during test: {(sum(all_avg) / len(all_avg)):.2f}%\n") |