Lesson 16 · Python standard library deep dive
Python hashlib, hmac & secrets Explained: Secure Your Code | Standard Library #16
Video sixteen of the twenty-five-part series: hashing, message authentication, and cryptographically secure randomness. hashlib for one-way hashes, hmac for…
- CoursePython standard library deep dive
- Lesson16 of 24
- Video17 min
- FormatJupyter notebook · 11 code cells
What you'll learn
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbPython Standard Library Deep-Dive, Video 16: hashlib, hmac, secrets#
- Video sixteen of the twenty-five-part series: hashing, message authentication, and cryptographically secure randomness.
- hashlib for one-way hashes, hmac for authenticated hashes, secrets for genuinely secure tokens and passwords.
- Let's get into it.
Part 1: Why Hashing#
import hashlib
print(hashlib.sha256(b'hello').hexdigest())
print(hashlib.sha256(b'hello').hexdigest())
print(hashlib.sha256(b'Hello').hexdigest())
Part 2: hashlib Basics#
data = b'The quick brown fox'
print(hashlib.md5(data).hexdigest())
print(hashlib.sha1(data).hexdigest())
print(hashlib.sha256(data).hexdigest())
print(hashlib.sha512(data).hexdigest())
print(len(hashlib.sha256(data).digest()))
print(len(hashlib.sha256(data).hexdigest()))
h = hashlib.sha256()
h.update(b'The quick ')
h.update(b'brown fox')
print(h.hexdigest())
print(h.hexdigest() == hashlib.sha256(b'The quick brown fox').hexdigest())
Part 3: Hashing Files#
with open('demo_hash_file.txt', 'w') as f:
f.write('Sample content for hashing.\n' * 100)
def hash_file(path, algorithm='sha256', chunk_size=8192):
hasher = hashlib.new(algorithm)
with open(path, 'rb') as f:
while chunk := f.read(chunk_size):
hasher.update(chunk)
return hasher.hexdigest()
print(hash_file('demo_hash_file.txt'))
Part 4: hashlib.new() and Available Algorithms#
print(sorted(hashlib.algorithms_guaranteed)[:8])
print('sha3_256' in hashlib.algorithms_guaranteed)
print('blake2b' in hashlib.algorithms_guaranteed)
h = hashlib.new('sha3_256', b'quantum-resistant family')
print(h.hexdigest())
Part 5: hmac - Message Authentication Codes#
import hmac
secret_key = b'shared-secret-key'
message = b'transfer $100 to account 42'
signature = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
print(signature)
wrong_key = b'wrong-key'
wrong_signature = hmac.new(wrong_key, message, hashlib.sha256).hexdigest()
print(signature == wrong_signature)
Part 6: hmac.compare_digest - Timing-Safe Comparison#
received_signature = signature
is_valid = hmac.compare_digest(signature, received_signature)
print(is_valid)
is_valid_tampered = hmac.compare_digest(signature, wrong_signature)
print(is_valid_tampered)
Part 7: secrets - Secure Tokens#
import secrets
token1 = secrets.token_bytes(16)
print(len(token1))
token2 = secrets.token_hex(16)
print(token2)
print(len(token2))
token3 = secrets.token_urlsafe(16)
print(token3)
Part 8: secrets - Secure Choice and Password Generation#
import string
alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for _ in range(12))
print(password)
print(len(password))
roll = secrets.randbelow(6) + 1
print(1 <= roll <= 6)
Part 9: Password Hashing Correctly#
password = 'correct horse battery staple'
salt = secrets.token_bytes(16)
hashed = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 200_000)
print(len(hashed))
def verify_password(password, salt, expected_hash):
candidate = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 200_000)
return hmac.compare_digest(candidate, expected_hash)
print(verify_password('correct horse battery staple', salt, hashed))
print(verify_password('wrong guess', salt, hashed))
Part 10: Putting It Together - Webhook Signature Verification#
webhook_secret = secrets.token_bytes(32)
def sign_payload(payload_bytes, secret):
return hmac.new(secret, payload_bytes, hashlib.sha256).hexdigest()
def verify_webhook(payload_bytes, received_signature, secret):
expected = sign_payload(payload_bytes, secret)
return hmac.compare_digest(expected, received_signature)
payload = b'{"event": "payment.success", "amount": 4999}'
signature = sign_payload(payload, webhook_secret)
print(verify_webhook(payload, signature, webhook_secret))
tampered_payload = b'{"event": "payment.success", "amount": 999999}'
print(verify_webhook(tampered_payload, signature, webhook_secret))
Wrap-Up: What You Learned#
- hashlib produces deterministic, one-way, fixed-size digests; sha256/sha512 are the modern recommended choices, md5/sha1 are broken.
- Hash objects can be fed incrementally with update(), enabling memory-safe chunked file hashing.
- hashlib.new(name) builds a hasher dynamically; algorithms_guaranteed lists every universally available algorithm.
- hmac combines a secret key with a message, proving authenticity, not just integrity.
- hmac.compare_digest performs constant-time comparison, avoiding timing side-channel attacks.
- secrets uses the OS's cryptographically secure random source; never use random for tokens, passwords, or keys.
- secrets.token_bytes/token_hex/token_urlsafe generate secure tokens; secrets.choice/randbelow drive secure random selection.
- Real password storage needs a random salt plus a deliberately slow algorithm like pbkdf2_hmac, never a single fast hash pass.
- A realistic combined pattern: hmac-signing and compare_digest-verifying webhook payloads.
- That wraps up hashlib, hmac, and secrets. Next up: contextlib, for building and combining context managers.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



