Math, Randomness, and Cryptography
Math, Randomness, and Cryptography
After reading this chapter, you will master Python’s numerical precision tools (math, decimal.Decimal, fractions.Fraction), contrast pseudo-random generators (random) with cryptographically secure generators (secrets), execute cryptographic hashing with hashlib, and verify message authentication codes safely with hmac.compare_digest to eliminate timing attack vulnerabilities.
Mental model
Python provides two completely different engines for randomness and number processing:
Randomness Engines:
random module (PRNG - Pseudo-Random)
└── Mersenne Twister algorithm (MT19937)
├── Period: 2^19937 - 1
├── Predictable: Observing 624 outputs reveals the internal state!
└── Purpose: Simulations, Monte Carlo algorithms, games, shuffling
secrets module (CSPRNG - Cryptographically Secure)
└── Direct kernel entropy pool (/dev/urandom, Windows BCrypt)
├── Unpredictable: Suitable for security secrets
└── Purpose: API keys, session tokens, passwords, CSRF tokens
For financial ledgers and billing calculations, IEEE 754 binary floats (float) introduce rounding errors (\(0.1 + 0.2 \neq 0.3\)). The decimal.Decimal module performs exact base-10 arithmetic.
Minimal example
Save as crypto_math_showcase.py:
# crypto_math_showcase.py
from decimal import Decimal, ROUND_HALF_UP
import hashlib
import hmac
import secrets
def main() -> None:
# 1. Exact financial arithmetic with Decimal
price = Decimal("19.99")
tax_rate = Decimal("0.0825") # 8.25%
tax = (price * tax_rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
total = price + tax
print(f"Subtotal: ${price} | Tax: ${tax} | Total: ${total}")
# 2. Secure Token Generation with secrets (CSPRNG)
api_token = secrets.token_urlsafe(32)
hex_salt = secrets.token_hex(16)
print(f"\nGenerated secure API token : {api_token}")
print(f"Generated cryptographic salt: {hex_salt}")
# 3. SHA-256 Hashing with hashlib
payload = b"GET /api/v1/clusters HTTP/1.1"
digest = hashlib.sha256(payload).hexdigest()
print(f"\nSHA-256 Digest : {digest}")
# 4. Timing-Safe HMAC verification
secret_key = b"super-secret-cluster-signing-key"
signature = hmac.new(secret_key, payload, hashlib.sha256).hexdigest()
# hmac.compare_digest protects against side-channel timing attacks
is_valid = hmac.compare_digest(signature, signature)
print(f"HMAC Signature : {signature}")
print(f"Timing-safe signature valid?: {is_valid}")
if __name__ == "__main__":
main()Run via uv run python crypto_math_showcase.py:
Subtotal: $19.99 | Tax: $1.65 | Total: $21.64
Generated secure API token : ...
Generated cryptographic salt: ...
SHA-256 Digest : 2d1478546b595bb97970d4f6c44933a39e830e2f5f190eecfa519c5c7784da31
HMAC Signature : ...
Timing-safe signature valid?: True
Worked examples
Case 1: Timing Attack Defense with hmac.compare_digest
When checking whether an API secret or webhook token matches an expected value, using Python’s standard == operator is dangerous. The == operator performs an early exit as soon as the first non-matching byte is found, leaking timing information that allows attackers to recover the secret byte by byte:
# timing_attack_demo.py
import hmac
import time
def naive_verify(provided: str, expected: str) -> bool:
# VULNERABILITY: Early return leaks timing measurements
return provided == expected
def secure_verify(provided: str, expected: str) -> bool:
# Constant-time comparison: takes the same duration regardless of matching bytes
return hmac.compare_digest(provided, expected)
if __name__ == "__main__":
correct_token = "tok_live_998877665544332211"
attacker_candidate = "tok_live_000000000000000000"
print("Timing-safe comparison result:", secure_verify(attacker_candidate, correct_token))Run:
uv run python timing_attack_demo.pyOutput:
Timing-safe comparison result: False
Case 2: Exact Rational Fractions with fractions.Fraction
When modeling rates, probabilities, or unit proportions that must avoid any loss of precision, fractions.Fraction performs exact numerator/denominator math:
# exact_fractions.py
from fractions import Fraction
def calculate_quorum() -> None:
# Quorum requiring 2/3 majority
f1 = Fraction(1, 3)
f2 = Fraction(1, 6)
combined = f1 + f2
print(f"1/3 + 1/6 = {combined} (Numerator: {combined.numerator}, Denominator: {combined.denominator})")
# Reconstruct from float with exact limit
approx = Fraction("0.75")
print(f"Fraction for 0.75: {approx}")
if __name__ == "__main__":
calculate_quorum()Run:
uv run python exact_fractions.pyOutput:
1/3 + 1/6 = 1/2 (Numerator: 1, Denominator: 2)
Fraction for 0.75: 3/4
Pitfalls
Pitfall 1: Using random for Security Tokens
The random module uses the Mersenne Twister algorithm. It is completely deterministic and non-cryptographic:
# DANGEROUS: Predictable token!
import random
token = f"{random.randint(100000, 999999)}" # NEVER DO THIS FOR AUTH OR PASSWORDS!
# SECURE:
import secrets
token = secrets.token_urlsafe(32)Pitfall 2: Initializing Decimal from a float
Passing a raw float into Decimal() captures the IEEE 754 binary floating-point representation errors:
from decimal import Decimal
# THE BUG:
d = Decimal(0.1)
print(d) # Decimal('0.1000000000000000055511151231257827021181583404541015625')
# THE FIX: Always pass strings!
d = Decimal("0.1")
print(d) # Decimal('0.1')Exercises
- Write a billing calculator using
Decimalthat computes sales tax and applies a percentage discount, rounding to 2 decimal places usingROUND_HALF_EVEN. - Generate a secure 16-character random alphanumeric password using
secrets.choiceacross uppercase, lowercase, and digit character sets. - Compute the SHA-512 hash of a text string and format the result as an uppercase hexadecimal digest.
- Implement a message signature verification function using
hmacwith SHA-256 and verify it against test vectors.
Further reading
- PEP 506: Adding A Secrets Module To The Standard Library.
- Python Standard Library:
decimal,fractions,secrets,hashlib,hmac. - RFC 2104: HMAC: Keyed-Hashing for Message Authentication.