This is an automated email from the ASF dual-hosted git repository. gstein pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/steve.git
commit f4a0f2f6c813cc3a73bb2714a8e7d7826dd4f24b Author: Greg Stein <[email protected]> AuthorDate: Fri Feb 20 19:32:44 2026 -0600 Switch from passlib to argon2-cffi. The cffi was already imported so that passlib could use it. However, we don't need the "stored password formatting" that passlib provides. We simply want the Argon2 hashing algorithm. Strip out passlib. Add a benchmarking tool to measure/tune our use of Argon2. We can perform some tuning in the future to ensure (minimum time) that brute force can't be used against our vote database. --- v3/pyproject.toml | 1 - v3/steve/crypto.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/v3/pyproject.toml b/v3/pyproject.toml index 6f7a7f7..25b79d2 100644 --- a/v3/pyproject.toml +++ b/v3/pyproject.toml @@ -12,7 +12,6 @@ dependencies = [ "asfquart>=0.1.12", "ezt>=1.1", "easydict>=1.13", - "passlib>=1.7.4,<2", "cryptography>=46.0.5,<47", "argon2-cffi>=25.1.0,<26", ] diff --git a/v3/steve/crypto.py b/v3/steve/crypto.py index 9606c81..63480b4 100644 --- a/v3/steve/crypto.py +++ b/v3/steve/crypto.py @@ -18,8 +18,9 @@ import base64 import secrets import hashlib # for blake2b +import time -import passlib.hash # note that .argon2 is proxy in this pkg +import argon2.low_level import cryptography.fernet from cryptography.hazmat.primitives import hashes @@ -87,10 +88,17 @@ def decrypt_votestring(vote_token: bytes, salt: bytes, ciphertext: bytes) -> str def _hash(data: bytes, salt: bytes) -> bytes: - "Apply our desired hashing function." - ph = passlib.hash.argon2.using(type='d', salt=salt) - h = ph.hash(data) - return base64.standard_b64decode(h.split('$')[-1] + '==') + "Use Argon2 to hash the data, with default tuning parameters." + + return argon2.low_level.hash_secret_raw( + secret=data, + salt=salt, + time_cost=2, # Passlib default + memory_cost=65536, # Passlib default + parallelism=4, # Passlib default + hash_len=32, # Standard Argon2 digest length + type=argon2.low_level.Type.D, + ) def shuffle(x): @@ -116,3 +124,46 @@ def create_id(): # Use 10 hex characters for the ID return secrets.token_hex(5) # 5 bytes + + +def benchmark_argon2(): + + # Test Data + dummy_data = b"This is a sample datum representing one of your 1000 entries." + salt = b"16_byte_salt_123" # 16 bytes + intermediate = hashlib.blake2b(dummy_data).digest() + + # Define Low/High levels for the 3 parameters + # Note: Memory is in Kibibytes (65536 = 64MB) + options = { + "rounds": [2, 4], + "memory": [65536, 131072], + "parallelism": [4, 8] + } + + print(f"{'Rounds':<8} | {'Memory (MB)':<12} | {'Threads':<8} | {'Time (s)':<10}") + print("-" * 50) + + for r in options["rounds"]: + for m in options["memory"]: + for p in options["parallelism"]: + start = time.perf_counter() + + # The actual derivation process + argon2.low_level.hash_secret_raw( + secret=intermediate, + salt=salt, + time_cost=r, + memory_cost=m, + parallelism=p, + hash_len=32, + type=argon2.low_level.Type.ID + ) + + duration = time.perf_counter() - start + print(f"{r:<8} | {m//1024:<12} | {p:<8} | {duration:.4f}s") + + +if __name__ == "__main__": + print("--- Argon2 Parameter Benchmarking ---") + benchmark_argon2()
