Skip to content

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:

AlgorithmDigest LengthSecurityUse Cases
MD5128 bit / 32 hexWeak (collisions found)File checksum (non-security scenarios)
SHA-1160 bit / 40 hexWeakLegacy systems
SHA-256256 bit / 64 hexStrongPassword storage, digital signatures
SHA-512512 bit / 128 hexVery strongHigh-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}")   # True
For storing passwords in production, use hashlib.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)}")   # False

Authentication 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)
Last updated on