|$ curl https://forge-ai.dev/api/markdown?path=docs/python/hashlib-secrets
$cat docs/hashlib-&-secrets.md
updated Today·18-24 min read·published
hashlib & secrets
Introduction
hashlib provides cryptographic hashes; hmac provides keyed message authentication; secrets provides secure random tokens. For password hashing, use specialized KDFs (bcrypt/argon2), not raw SHA.
hashlib
hashlib_demo.py
Python
| 1 | import hashlib |
| 2 | from pathlib import Path |
| 3 | |
| 4 | print(hashlib.sha256(b"payload").hexdigest()) |
| 5 | print(hashlib.sha512(b"payload").hexdigest()) |
| 6 | print(hashlib.blake2b(b"payload", digest_size=32).hexdigest()) |
| 7 | |
| 8 | # streaming large files |
| 9 | h = hashlib.sha256() |
| 10 | data = b"x" * 10_000 |
| 11 | for i in range(0, len(data), 4096): |
| 12 | h.update(data[i:i+4096]) |
| 13 | print(h.hexdigest()) |
| 14 | |
| 15 | def file_sha256(path: Path) -> str: |
| 16 | h = hashlib.sha256() |
| 17 | with path.open("rb") as f: |
| 18 | for chunk in iter(lambda: f.read(1 << 16), b""): |
| 19 | h.update(chunk) |
| 20 | return h.hexdigest() |
| Algorithm | Use |
|---|---|
| sha256 / sha512 | Integrity checksums |
| blake2b / blake2s | Fast secure hashing |
| sha3_256 | SHA-3 family |
| md5 / sha1 | Legacy only — not security |
HMAC
hmac_demo.py
Python
| 1 | import hashlib |
| 2 | import hmac |
| 3 | import secrets |
| 4 | |
| 5 | key = secrets.token_bytes(32) |
| 6 | msg = b"body" |
| 7 | sig = hmac.new(key, msg, hashlib.sha256).hexdigest() |
| 8 | |
| 9 | def verify(key: bytes, msg: bytes, sig_hex: str) -> bool: |
| 10 | expected = hmac.new(key, msg, hashlib.sha256).hexdigest() |
| 11 | return hmac.compare_digest(expected, sig_hex) |
| 12 | |
| 13 | assert verify(key, msg, sig) |
| 14 | assert not verify(key, msg, "00" * 32) |
✕
danger
Always compare secrets with hmac.compare_digest — never == on digests.
secrets
secrets_demo.py
Python
| 1 | import secrets |
| 2 | import string |
| 3 | |
| 4 | print(secrets.token_bytes(16)) |
| 5 | print(secrets.token_hex(16)) |
| 6 | print(secrets.token_urlsafe(16)) |
| 7 | |
| 8 | alphabet = string.ascii_letters + string.digits |
| 9 | password = "".join(secrets.choice(alphabet) for _ in range(20)) |
| 10 | print(password) |
| 11 | |
| 12 | # secrets.compare_digest for str secrets too |
| 13 | a = secrets.token_urlsafe(8) |
| 14 | assert secrets.compare_digest(a, a) |
⚠
warning
Never use random.random / random.choices for security tokens.
Password Hashing Overview
Password storage needs slow, memory-hard KDFs with unique salts. Prefer argon2 or bcrypt libraries — not SHA256(password).
passwords.py
Python
| 1 | # pip install bcrypt |
| 2 | import bcrypt |
| 3 | |
| 4 | password = b"correct horse battery staple" |
| 5 | hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12)) |
| 6 | assert bcrypt.checkpw(password, hashed) |
| 7 | |
| 8 | # pip install argon2-cffi |
| 9 | # from argon2 import PasswordHasher |
| 10 | # ph = PasswordHasher() |
| 11 | # h = ph.hash("correct horse battery staple") |
| 12 | # ph.verify(h, "correct horse battery staple") |
| Scheme | Notes |
|---|---|
| argon2id | Modern default recommendation |
| bcrypt | Widely supported; 72-byte limit |
| scrypt | Available in hashlib.scrypt |
| PBKDF2 | hashlib.pbkdf2_hmac — OK if tuned |
| Plain SHA | Never for passwords |
stdlib KDFs
kdf.py
Python
| 1 | import hashlib |
| 2 | import secrets |
| 3 | |
| 4 | password = b"secret" |
| 5 | salt = secrets.token_bytes(16) |
| 6 | dk = hashlib.scrypt(password, salt=salt, n=2**14, r=8, p=1, dklen=32) |
| 7 | print(dk.hex()) |
| 8 | |
| 9 | dk2 = hashlib.pbkdf2_hmac("sha256", password, salt, 200_000, dklen=32) |
| 10 | print(dk2.hex()) |
API Signing Pattern
sign.py
Python
| 1 | import hashlib |
| 2 | import hmac |
| 3 | import secrets |
| 4 | import time |
| 5 | |
| 6 | def sign(secret: bytes, body: bytes, ts: int | None = None) -> str: |
| 7 | ts = ts or int(time.time()) |
| 8 | msg = f"{ts}.".encode() + body |
| 9 | sig = hmac.new(secret, msg, hashlib.sha256).hexdigest() |
| 10 | return f"t={ts},v1={sig}" |
| 11 | |
| 12 | def check(secret: bytes, body: bytes, header: str, skew: int = 300) -> bool: |
| 13 | parts = dict(p.split("=", 1) for p in header.split(",")) |
| 14 | ts = int(parts["t"]) |
| 15 | if abs(int(time.time()) - ts) > skew: |
| 16 | return False |
| 17 | expected = sign(secret, body, ts) |
| 18 | return secrets.compare_digest(expected, f"t={ts},v1={parts['v1']}") |
Security Checklist
- secrets for tokens; hmac.compare_digest for compares
- SHA-256+ for integrity; never MD5 for security
- argon2id/bcrypt for passwords
- Unique salt per password
- Do not log secrets, tokens, or raw password hashes
File Integrity Manifest
manifest.py
Python
| 1 | import hashlib |
| 2 | import json |
| 3 | from pathlib import Path |
| 4 | |
| 5 | def digest_tree(root: Path) -> dict[str, str]: |
| 6 | out: dict[str, str] = {} |
| 7 | for p in sorted(root.rglob("*")): |
| 8 | if p.is_file(): |
| 9 | h = hashlib.sha256(p.read_bytes()).hexdigest() |
| 10 | out[str(p.relative_to(root))] = h |
| 11 | return out |
| 12 | |
| 13 | # Path("manifest.json").write_text(json.dumps(digest_tree(Path(".")), indent=2)) |
URL-safe Tokens for Sessions
tokens.py
Python
| 1 | import secrets |
| 2 | from dataclasses import dataclass |
| 3 | from datetime import datetime, timedelta, timezone |
| 4 | |
| 5 | @dataclass |
| 6 | class Session: |
| 7 | token: str |
| 8 | expires_at: datetime |
| 9 | |
| 10 | def new_session(ttl_hours: int = 24) -> Session: |
| 11 | return Session( |
| 12 | token=secrets.token_urlsafe(32), |
| 13 | expires_at=datetime.now(timezone.utc) + timedelta(hours=ttl_hours), |
| 14 | ) |
| 15 | |
| 16 | s = new_session() |
| 17 | print(s.token, s.expires_at.isoformat()) |
Timing Attacks
String comparison with == short-circuits; attackers measure time. Use hmac.compare_digest / secrets.compare_digest.
timing.py
Python
| 1 | import secrets |
| 2 | user_supplied = "abc" |
| 3 | expected = "abc" |
| 4 | print(secrets.compare_digest(user_supplied, expected)) |
Do Not
- Roll your own crypto protocols
- Use MD5/SHA1 for security
- Hash passwords with single SHA256
- Put API keys in URLs/query strings if avoidable
- Log Authorization headers
algorithms_available / guaranteed
algos.py
Python
| 1 | import hashlib |
| 2 | print("sha256" in hashlib.algorithms_guaranteed) |
| 3 | print(sorted(hashlib.algorithms_available)[:10]) |
FAQ
| Question | Answer |
|---|---|
| Checksum a download? | SHA-256 + compare_digest |
| Session id? | secrets.token_urlsafe |
| Password at rest? | argon2id or bcrypt |
| API request auth? | HMAC with shared secret |
| UUID for secrets? | OK-ish; token_urlsafe is fine |
Summary
Hash for integrity, HMAC for authenticity, secrets for tokens, and argon2/bcrypt for passwords. Compare digests in constant time.
$Blueprint — Engineering Documentation·Section ID: PYTHON-HASH-SECRETS·Revision: 1.0
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.