Skip to content

Instantly share code, notes, and snippets.

@84adam
Last active May 7, 2024 14:24
Show Gist options
  • Save 84adam/51b30ef4b8edd420f6f1d6ab6eea40fe to your computer and use it in GitHub Desktop.
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
# 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")
@84adam
Copy link
Author

84adam commented May 2, 2024

python3 bcrypt-hash-test.py ; example output on a 4-core machine:

Hashing with bcrypt work factor of 10 took 0.049 seconds.
Max single CPU core usage: 4.20%
Average usage all cores during test: 2.10%

Hashing with bcrypt work factor of 11 took 0.092 seconds.
Max single CPU core usage: 3.10%
Average usage all cores during test: 1.83%

Hashing with bcrypt work factor of 12 took 0.177 seconds.
Max single CPU core usage: 3.60%
Average usage all cores during test: 1.68%

Hashing with bcrypt work factor of 13 took 0.364 seconds.
Max single CPU core usage: 15.80%
Average usage all cores during test: 3.95%

Hashing with bcrypt work factor of 14 took 0.729 seconds.
Max single CPU core usage: 57.10%
Average usage all cores during test: 15.58%

Hashing with bcrypt work factor of 15 took 1.446 seconds.
Max single CPU core usage: 97.90%
Average usage all cores during test: 15.30%

Hashing with bcrypt work factor of 16 took 2.880 seconds.
Max single CPU core usage: 100.00%
Average usage all cores during test: 23.09%

NOTE: Max single core usage results may vary; run multiple times to get a more complete sense of CPU utilization.

SUGGESTION: Target a work factor that requires at least 250 ms to compute.

SEE 'Optimal bcrypt work factor' for details; particularly this comment: https://stackoverflow.com/a/61304956

@84adam
Copy link
Author

84adam commented May 3, 2024

argon2 example output, 4 core machine, between 10 and 30 iterations:

Hashing with argon2 work factor of 10 took 0.105 seconds.
Max single CPU core usage: 7.40%
Average usage all cores during test: 4.83%

Hashing with argon2 work factor of 11 took 0.134 seconds.
Max single CPU core usage: 6.50%
Average usage all cores during test: 5.45%

Hashing with argon2 work factor of 12 took 0.153 seconds.
Max single CPU core usage: 6.50%
Average usage all cores during test: 5.78%

Hashing with argon2 work factor of 13 took 0.137 seconds.
Max single CPU core usage: 7.60%
Average usage all cores during test: 6.00%

Hashing with argon2 work factor of 14 took 0.157 seconds.
Max single CPU core usage: 7.80%
Average usage all cores during test: 5.60%

Hashing with argon2 work factor of 15 took 0.160 seconds.
Max single CPU core usage: 8.90%
Average usage all cores during test: 6.72%

Hashing with argon2 work factor of 16 took 0.164 seconds.
Max single CPU core usage: 9.00%
Average usage all cores during test: 6.70%

Hashing with argon2 work factor of 17 took 0.180 seconds.
Max single CPU core usage: 8.80%
Average usage all cores during test: 7.00%

Hashing with argon2 work factor of 18 took 0.186 seconds.
Max single CPU core usage: 12.10%
Average usage all cores during test: 8.43%

Hashing with argon2 work factor of 19 took 0.196 seconds.
Max single CPU core usage: 10.20%
Average usage all cores during test: 9.28%

Hashing with argon2 work factor of 20 took 0.214 seconds.
Max single CPU core usage: 10.20%
Average usage all cores during test: 9.20%

Hashing with argon2 work factor of 21 took 0.212 seconds.
Max single CPU core usage: 9.30%
Average usage all cores during test: 7.90%

Hashing with argon2 work factor of 22 took 0.228 seconds.
Max single CPU core usage: 11.60%
Average usage all cores during test: 9.30%

Hashing with argon2 work factor of 23 took 0.233 seconds.
Max single CPU core usage: 11.50%
Average usage all cores during test: 9.85%

Hashing with argon2 work factor of 24 took 0.242 seconds.
Max single CPU core usage: 14.80%
Average usage all cores during test: 10.70%

Hashing with argon2 work factor of 25 took 0.260 seconds.
Max single CPU core usage: 12.90%
Average usage all cores during test: 10.62%

Hashing with argon2 work factor of 26 took 0.265 seconds.
Max single CPU core usage: 11.90%
Average usage all cores during test: 9.55%

Hashing with argon2 work factor of 27 took 0.268 seconds.
Max single CPU core usage: 14.30%
Average usage all cores during test: 11.60%

Hashing with argon2 work factor of 28 took 0.292 seconds.
Max single CPU core usage: 14.10%
Average usage all cores during test: 12.65%

Hashing with argon2 work factor of 29 took 0.286 seconds.
Max single CPU core usage: 13.10%
Average usage all cores during test: 11.95%

Hashing with argon2 work factor of 30 took 0.304 seconds.
Max single CPU core usage: 16.90%
Average usage all cores during test: 13.08%

@84adam
Copy link
Author

84adam commented May 6, 2024

argon2 with 5-30 work factor, using 256 MiB of memory on a 4-core machine:

Hashing with argon2 work factor of 5 and memory cost of 262144 took 0.256 seconds.
Max single CPU core usage: 9.80%
Average usage all cores during test: 6.88%

Hashing with argon2 work factor of 6 and memory cost of 262144 took 0.264 seconds.
Max single CPU core usage: 8.60%
Average usage all cores during test: 7.00%

Hashing with argon2 work factor of 7 and memory cost of 262144 took 0.297 seconds.
Max single CPU core usage: 11.70%
Average usage all cores during test: 7.70%

Hashing with argon2 work factor of 8 and memory cost of 262144 took 0.334 seconds.
Max single CPU core usage: 11.80%
Average usage all cores during test: 9.57%

Hashing with argon2 work factor of 9 and memory cost of 262144 took 0.361 seconds.
Max single CPU core usage: 11.00%
Average usage all cores during test: 9.23%

Hashing with argon2 work factor of 10 and memory cost of 262144 took 0.390 seconds.
Max single CPU core usage: 14.10%
Average usage all cores during test: 11.03%

Hashing with argon2 work factor of 11 and memory cost of 262144 took 0.429 seconds.
Max single CPU core usage: 14.70%
Average usage all cores during test: 12.25%

Hashing with argon2 work factor of 12 and memory cost of 262144 took 0.467 seconds.
Max single CPU core usage: 16.40%
Average usage all cores during test: 14.52%

Hashing with argon2 work factor of 13 and memory cost of 262144 took 0.494 seconds.
Max single CPU core usage: 18.00%
Average usage all cores during test: 15.12%

Hashing with argon2 work factor of 14 and memory cost of 262144 took 0.526 seconds.
Max single CPU core usage: 20.30%
Average usage all cores during test: 17.93%

Hashing with argon2 work factor of 15 and memory cost of 262144 took 0.573 seconds.
Max single CPU core usage: 23.60%
Average usage all cores during test: 18.50%

Hashing with argon2 work factor of 16 and memory cost of 262144 took 0.610 seconds.
Max single CPU core usage: 25.00%
Average usage all cores during test: 20.23%

Hashing with argon2 work factor of 17 and memory cost of 262144 took 0.645 seconds.
Max single CPU core usage: 26.40%
Average usage all cores during test: 20.80%

Hashing with argon2 work factor of 18 and memory cost of 262144 took 0.681 seconds.
Max single CPU core usage: 28.60%
Average usage all cores during test: 23.25%

Hashing with argon2 work factor of 19 and memory cost of 262144 took 0.724 seconds.
Max single CPU core usage: 29.20%
Average usage all cores during test: 26.48%

Hashing with argon2 work factor of 20 and memory cost of 262144 took 0.745 seconds.
Max single CPU core usage: 31.80%
Average usage all cores during test: 28.65%

Hashing with argon2 work factor of 21 and memory cost of 262144 took 0.790 seconds.
Max single CPU core usage: 38.50%
Average usage all cores during test: 34.32%

Hashing with argon2 work factor of 22 and memory cost of 262144 took 0.826 seconds.
Max single CPU core usage: 41.00%
Average usage all cores during test: 38.52%

Hashing with argon2 work factor of 23 and memory cost of 262144 took 0.865 seconds.
Max single CPU core usage: 52.80%
Average usage all cores during test: 46.08%

Hashing with argon2 work factor of 24 and memory cost of 262144 took 0.915 seconds.
Max single CPU core usage: 51.70%
Average usage all cores during test: 50.80%

Hashing with argon2 work factor of 25 and memory cost of 262144 took 0.924 seconds.
Max single CPU core usage: 59.30%
Average usage all cores during test: 53.25%

Hashing with argon2 work factor of 26 and memory cost of 262144 took 0.976 seconds.
Max single CPU core usage: 68.00%
Average usage all cores during test: 63.17%

Hashing with argon2 work factor of 27 and memory cost of 262144 took 1.009 seconds.
Max single CPU core usage: 72.70%
Average usage all cores during test: 34.76%

Hashing with argon2 work factor of 28 and memory cost of 262144 took 1.055 seconds.
Max single CPU core usage: 74.10%
Average usage all cores during test: 36.70%

Hashing with argon2 work factor of 29 and memory cost of 262144 took 1.083 seconds.
Max single CPU core usage: 76.00%
Average usage all cores during test: 35.48%

Hashing with argon2 work factor of 30 and memory cost of 262144 took 1.105 seconds.
Max single CPU core usage: 76.90%
Average usage all cores during test: 36.41%

@84adam
Copy link
Author

84adam commented May 6, 2024

added pbkdf2 -- example output on a 4-core machine:

Hashing with pbkdf2 work factor of 700000 took 0.256 seconds.
Max single CPU core usage: 18.70%
Average usage all cores during test: 2.59%

Hashing with pbkdf2 work factor of 720000 took 0.264 seconds.
Max single CPU core usage: 24.70%
Average usage all cores during test: 6.14%

Hashing with pbkdf2 work factor of 740000 took 0.288 seconds.
Max single CPU core usage: 20.20%
Average usage all cores during test: 7.15%

Hashing with pbkdf2 work factor of 760000 took 0.280 seconds.
Max single CPU core usage: 20.90%
Average usage all cores during test: 2.99%

Hashing with pbkdf2 work factor of 780000 took 0.289 seconds.
Max single CPU core usage: 22.80%
Average usage all cores during test: 3.23%

Hashing with pbkdf2 work factor of 800000 took 0.292 seconds.
Max single CPU core usage: 23.70%
Average usage all cores during test: 3.21%

Hashing with pbkdf2 work factor of 820000 took 0.298 seconds.
Max single CPU core usage: 23.10%
Average usage all cores during test: 3.26%

Hashing with pbkdf2 work factor of 840000 took 0.310 seconds.
Max single CPU core usage: 29.60%
Average usage all cores during test: 4.09%

Hashing with pbkdf2 work factor of 860000 took 0.316 seconds.
Max single CPU core usage: 24.20%
Average usage all cores during test: 3.27%

Hashing with pbkdf2 work factor of 880000 took 0.326 seconds.
Max single CPU core usage: 25.00%
Average usage all cores during test: 3.75%

Hashing with pbkdf2 work factor of 900000 took 0.331 seconds.
Max single CPU core usage: 23.90%
Average usage all cores during test: 3.24%

Hashing with pbkdf2 work factor of 920000 took 0.338 seconds.
Max single CPU core usage: 25.80%
Average usage all cores during test: 3.73%

Hashing with pbkdf2 work factor of 940000 took 0.345 seconds.
Max single CPU core usage: 26.70%
Average usage all cores during test: 3.73%

Hashing with pbkdf2 work factor of 960000 took 0.355 seconds.
Max single CPU core usage: 28.60%
Average usage all cores during test: 4.33%

Hashing with pbkdf2 work factor of 980000 took 0.363 seconds.
Max single CPU core usage: 31.20%
Average usage all cores during test: 4.29%

Hashing with pbkdf2 work factor of 1000000 took 0.364 seconds.
Max single CPU core usage: 27.60%
Average usage all cores during test: 4.08%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment