Last active
May 7, 2024 14:24
-
-
Save 84adam/51b30ef4b8edd420f6f1d6ab6eea40fe to your computer and use it in GitHub Desktop.
bcrypt/argon2/pbkdf2 hash test -- target a work factor that requires at least 250 ms to compute
This file contains hidden or 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") |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
added
pbkdf2-- example output on a 4-core machine: