Encryption Modules
Python’s standard library provides two encryption-related modules: hashlib (cryptographic hash digests) and hmac (message authentication codes), used for password storage, data integrity verification, and communication authentication.
hashlib — Hash Digests
Hash (digest) algorithms map data of arbitrary length to a fixed-length byte string. They have one-way (non-reversible) and avalanche effect (a tiny change in the input causes a completely different digest) properties.
Common algorithms:
| Algorithm | Digest Length | Security | Use Cases |
|---|---|---|---|
| MD5 | 128 bit / 32 hex | Weak (collisions found) | File checksum (non-security scenarios) |
| SHA-1 | 160 bit / 40 hex | Weak | Legacy systems |
| SHA-256 | 256 bit / 64 hex | Strong | Password storage, digital signatures |
| SHA-512 | 512 bit / 128 hex | Very strong | High-security requirements |
Basic Usage
import hashlib
# MD5
h = hashlib.md5()
h.update("hello world".encode("utf-8"))
print(h.hexdigest()) # 5eb63bbbe01eeed093cb22bb8f5acdc3 (32 hex chars)
# SHA-256 (recommended)
h = hashlib.sha256("hello world".encode("utf-8"))
print(h.hexdigest()) # Hex string, 64 characters
# Equivalent shorthand
digest = hashlib.sha256(b"hello world").hexdigest()
print(digest)Salting
Hashing a password directly is vulnerable to rainbow table attacks. Adding a salt improves security:
import hashlib
import os
def hash_password(password: str, salt: bytes | None = None) -> tuple[str, bytes]:
"""Hash a password with a salt, returning (hexdigest, salt)."""
if salt is None:
salt = os.urandom(32) # Generate a random 32-byte salt
h = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations=260_000)
return h.hex(), salt
def verify_password(password: str, stored_hash: str, salt: bytes) -> bool:
"""Verify whether the password is correct."""
h, _ = hash_password(password, salt)
return h == stored_hash
# Store the password
pwd_hash, salt = hash_password("my_secret_password")
print(f"Hash: {pwd_hash}")
# Verify the password
ok = verify_password("my_secret_password", pwd_hash, salt)
print(f"Verification passed: {ok}") # Truehashlib.pbkdf2_hmac (PBKDF2) or third-party libraries such as bcrypt or argon2-cffi, rather than MD5/SHA directly.File Integrity Verification
import hashlib
from pathlib import Path
def file_checksum(filepath: str | Path, algorithm: str = "sha256", chunk_size: int = 65536) -> str:
"""Compute a file's hash value (reads in chunks, suitable for large files)."""
h = hashlib.new(algorithm)
with open(filepath, "rb") as f:
while chunk := f.read(chunk_size):
h.update(chunk)
return h.hexdigest()
# Check whether two files are identical
f1 = file_checksum("file1.bin")
f2 = file_checksum("file2.bin")
print("Files are identical" if f1 == f2 else f"Files differ: {f1} vs {f2}")Available Algorithms
import hashlib
# List all available algorithms
print(hashlib.algorithms_available)
print(hashlib.algorithms_guaranteed) # Guaranteed to be available on all platforms
# Select an algorithm dynamically
h = hashlib.new("sha512", b"data")
print(h.hexdigest())hmac — Message Authentication Codes
HMAC (Hash-based Message Authentication Code) introduces a shared secret key on top of hashing, allowing simultaneous verification of both data integrity and source authenticity.
import hmac
import os
# Shared key between both parties (must be exchanged through a secure channel)
secret_key = os.urandom(32)
def sign(message: bytes, key: bytes) -> bytes:
"""Sign a message."""
return hmac.new(key, message, digestmod="sha256").digest()
def verify(message: bytes, signature: bytes, key: bytes) -> bool:
"""Verify a message signature (uses compare_digest to prevent timing attacks)."""
expected = sign(message, key)
return hmac.compare_digest(expected, signature)
# Sender signs
msg = b"transfer: $100 to Alice"
sig = sign(msg, secret_key)
print(f"Signature: {sig.hex()}")
# Receiver verifies
ok = verify(msg, sig, secret_key)
print(f"Signature valid: {ok}") # True
# When the message has been tampered with
tampered = b"transfer: $9999 to Eve"
print(f"Tampered verification: {verify(tampered, sig, secret_key)}") # FalseAuthentication in TCP Communication
Use HMAC to implement a challenge-response authentication scheme where the server authenticates the client:
import socket
import hmac
import os
SECRET_KEY = b"shared_secret_key_for_demo"
# --- Server ---
def server_auth(conn: socket.socket) -> bool:
"""Send a random challenge to the client and verify it knows the shared key."""
challenge = os.urandom(32)
conn.sendall(challenge) # Send challenge
response = conn.recv(64) # Wait for the client's HMAC response
expected = hmac.new(SECRET_KEY, challenge, digestmod="sha256").digest()
return hmac.compare_digest(response, expected)
# --- Client ---
def client_auth(conn: socket.socket) -> None:
"""Receive the challenge, compute the HMAC, and send it back to the server."""
challenge = conn.recv(32) # Receive the server's challenge
response = hmac.new(SECRET_KEY, challenge, digestmod="sha256").digest()
conn.sendall(response)