How to Verify Passwords in Constant Time in Python
Use hmac.compare_digest to verify passwords in constant time, preventing timing attacks that could reveal password length or character positions.
Python code
29 linesimport hmac
import time
# Mock of a constant-time password comparison (prevents timing attacks)
def verify_password(stored_password: str, supplied_password: str) -> bool:
# hmac.compare_digest runs in constant time (for a given length)
return hmac.compare_digest(stored_password.encode(), supplied_password.encode())
if __name__ == "__main__":
correct_password = "super_secret_123"
wrong_password = "super_secret_124"
start = time.perf_counter()
result1 = verify_password(correct_password, correct_password)
end = time.perf_counter()
print(f"Correct match: {result1} (took {end - start:.10f}s)")
start = time.perf_counter()
result2 = verify_password(correct_password, wrong_password)
end = time.perf_counter()
print(f"Wrong match: {result2} (took {end - start:.10f}s)")
# Timing attack example: time differences should be negligible
# due to constant-time comparison regardless of where mismatch occurs
short_wrong = "super_secret_1"
start = time.perf_counter()
verify_password(correct_password, short_wrong)
end = time.perf_counter()
print(f"Short wrong: (took {end - start:.10f}s)")
Output
Correct match: True (took 0.0000034000s)
Wrong match: False (took 0.0000035000s)
Short wrong: (took 0.0000031000s)
How it works
The hmac.compare_digest function performs a constant-time comparison of two byte strings, meaning the time taken does not depend on where the first mismatch occurs. This prevents attackers from using timing information to guess password characters incrementally. Both arguments are encoded to bytes because compare_digest requires bytes-like objects. The function returns a boolean indicating whether the passwords match. By timing comparisons, you can observe that correct, wrong, and short passwords take roughly the same time to verify, highlighting the security benefit.
Common mistakes
- Using `==` for password comparison, which short-circuits on the first mismatch and leaks timing information
- Forgetting to encode strings before calling `hmac.compare_digest`
- Storing plaintext passwords instead of hashes — constant-time comparison alone is not sufficient for security
Variations
- Use `secrets.compare_digest` as an alternative to `hmac.compare_digest` for the same constant-time guarantee
- Compare hashed passwords (e.g., SHA-256) using `hmac.compare_digest` to avoid timing leaks while storing only hashes
Real-world use cases
- Authenticating users in a login endpoint where timing side-channels could leak password information.
- Verifying API tokens or session secrets before granting access to protected resources.
- Checking HMAC signatures in webhook receivers to ensure requests are authentic without exposing timing details.
Sponsored
More from Auth & security at scale
- ACME LetsEncrypt Mock Challenge Server in Python medium
- AES GCM encryption and decryption in Python medium
- Build a Mock OIDC Userinfo Endpoint in Python with Flask easy
- ChaCha20-Poly1305 mock in Python medium
- ECDH key agreement in Python with cryptography medium
- Enforce TLS 1.2 Minimum in Python easy
Keep learning
Related tutorials and quizzes for this topic.