Cryptography and Key Management
Cryptography can provide confidentiality, integrity, authenticity, and key agreement when used correctly.
8.1 What cryptography can and cannot do
Cryptography can provide confidentiality, integrity, authenticity, and key agreement when used correctly. It does not fix missing authorization, compromised endpoints, unsafe key storage, or flawed business logic.
8.2 Core primitives
Cryptographic hash: fixed-size digest; not encryption.
HMAC: message authentication using a shared secret key.
Digital signature: integrity and origin evidence using private/public key operations.
Symmetric encryption: same secret key is used for encryption and decryption.
Authenticated encryption (AEAD): confidentiality and integrity/authenticity, e.g. AES-GCM or ChaCha20-Poly1305.
Key derivation function (KDF): derives keys from secret material or passwords; password hashing needs a password-specific, deliberately costly function.
Key exchange: establishes shared secret material, often as part of a protocol such as TLS.
8.3 Never invent cryptography
Use reputable, maintained libraries and well-reviewed protocol implementations. Do not design your own cipher, authentication protocol, random-number generator, password hash, or key exchange.
8.4 Secure randomness
Use an operating-system-backed cryptographically secure random generator for session IDs, reset tokens, nonces, salts, and key material. Do not use ordinary simulation-oriented PRNGs for secrets.
Output (CPython 3.13.1, with three checks appended; the token itself is not printed — real code must not print or log it either):
Treat this value as a secret bearer credential. Store it securely, give it a short lifetime, bind it to one purpose and account, and make it single-use. The token should not appear in logs or analytics.
Secure versus simulation randomness Output (CPython 3.13.1):
The random module is a Mersenne Twister: excellent for simulations, and reproducible by design — which is exactly what a secret must not be. Its internal state can also be reconstructed from enough outputs. secrets uses the operating system's CSPRNG.
import secrets
# 32 random bytes, encoded as a URL-safe token.
reset_token = secrets.token_urlsafe(32)length: 43 characters
URL-safe alphabet only: True
a second call differs: Trueimport random
import secrets
random.seed(42)
first = [random.randint(0, 9) for _ in range(8)]
random.seed(42)
second = [random.randint(0, 9) for _ in range(8)]
print("random with the same seed repeats:", first == second, first)
print("secrets.token_hex(16) length:", len(secrets.token_hex(16)), "hex characters = 128 bits")
print("two secrets tokens differ:", secrets.token_hex(16) != secrets.token_hex(16))random with the same seed repeats: True [1, 0, 4, 3, 3, 2, 1, 8]
secrets.token_hex(16) length: 32 hex characters = 128 bits
two secrets tokens differ: True8.5 Authenticated encryption example
Use a maintained library's AEAD interface. The following is illustrative with Python's cryptography package; confirm the current API and key-management requirements for the version deployed.
Tests appended for the run (the Module 8 lab's tamper cases):
Output (Python 3.10.4 on Ubuntu 22.04 (WSL2), cryptography 3.4.8): Critical rules:
Keep the key outside source control and ordinary logs.
Never reuse a nonce with the same AES-GCM key.
Store the nonce and ciphertext together; the nonce is not normally secret.
Associated data is authenticated but not encrypted.
Plan rotation, backup, access policy, and recovery.
Never treat the example's in-memory key as a production key-management solution.
HMAC: integrity with a shared key Output (CPython 3.13.1):
An HMAC proves that someone holding the key produced the message and that it was not changed; it does not hide the message. hmac.compare_digest compares in time that does not depend on where the first difference is.
Try it: AES-GCM in your browser This lab uses your browser's Web Crypto API (real AES-GCM, not a simulation) to encrypt a test record and shows what happens when you tamper with the ciphertext, change the associated data, use a different key or reuse a nonce.
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12) # Unique for each encryption under this key.
plaintext = b"confidential course record"
associated_data = b"record:v1"
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
recovered = aesgcm.decrypt(nonce, ciphertext, associated_data)
assert recovered == plaintextfrom cryptography.exceptions import InvalidTag
print("round trip ok:", recovered == plaintext)
print("ciphertext:", len(ciphertext), "bytes for", len(plaintext), "bytes of plaintext (includes the 16-byte tag)")
def attempt(label, fn):
try:
fn(); print(f"{label:<24} -> decrypted (unexpected)")
except InvalidTag:
print(f"{label:<24} -> InvalidTag, rejected")
tampered = bytes([ciphertext[0] ^ 1]) + ciphertext[1:]
attempt("tampered ciphertext", lambda: aesgcm.decrypt(nonce, tampered, associated_data))
attempt("altered associated data", lambda: aesgcm.decrypt(nonce, ciphertext, b"record:v2"))
attempt("wrong key", lambda: AESGCM(AESGCM.generate_key(bit_length=256)).decrypt(nonce, ciphertext, associated_data))round trip ok: True
ciphertext: 42 bytes for 26 bytes of plaintext (includes the 16-byte tag)
tampered ciphertext -> InvalidTag, rejected
altered associated data -> InvalidTag, rejected
wrong key -> InvalidTag, rejectedimport hashlib
import hmac
import secrets
key = secrets.token_bytes(32)
message = b'{"user": 17, "action": "export"}'
tag = hmac.new(key, message, hashlib.sha256).hexdigest()
def verify(msg: bytes, received_tag: str) -> bool:
expected = hmac.new(key, msg, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received_tag)
print("tag length:", len(tag), "hex characters")
print("original message verifies:", verify(message, tag))
print("altered message verifies: ", verify(b'{"user": 18, "action": "export"}', tag))tag length: 64 hex characters
original message verifies: True
altered message verifies: False8.6 Password hashing versus encryption
Passwords should generally be verified using a salted password hash/KDF designed for password storage, not encrypted for later decryption. Data that must be recovered may require encryption with managed keys. These are different use cases.
8.7 TLS and certificates
Use current TLS configurations and disable obsolete protocols and weak cipher suites according to platform guidance.
Validate certificates and hostnames.
Keep private keys private and rotate compromised/expired credentials.
Use HTTPS throughout the trust boundary; avoid mixed content.
Understand certificate chains, trust stores, renewal, revocation limitations, and service identity.
Do not disable certificate validation to “fix” connection errors.
8.8 Key management lifecycle
Generate keys with a secure source.
Store them in an appropriate secrets manager, KMS, HSM, or protected platform facility.
Restrict access and separate duties.
Record metadata and purpose without exposing key material.
Rotate according to risk, lifetime, and compromise policy.
Support safe re-encryption or key versioning when necessary.
Revoke and respond to suspected compromise.
Secure backups and test recovery.
Retire and destroy keys when no longer needed.
Text equivalent: a key is generated from a secure source, stored in a protected facility, used only by identities that need it, rotated (with key versions so existing data stays readable), revoked when it must no longer be trusted, and finally destroyed. Backups and recovery tests run alongside every stage.
flowchart TD
G[Generate securely] --> S[Store in KMS / HSM / secrets manager]
S --> U[Use with restricted access]
U --> RO[Rotate on schedule or compromise]
RO --> RE[Re-encrypt or keep versioned keys]
RE --> RV[Revoke old version]
RV --> D[Retire and destroy]8.9 Common cryptographic failures
ECB mode for general data encryption MD5/SHA-1 for security-sensitive collision-resistant purposes Plain fast hashes for password storage
Home-grown “encryption” based on Base64 or XOR Trusting unsigned data
Comparing secret values with ordinary comparison in contexts where timing leakage matters Incorrect certificate validation Weak or predictable randomness Confusing encoding with encryption
Lab
Encrypt and decrypt a test record with a maintained AEAD library. Add tests for tampered ciphertext, wrong key, altered associated data, and nonce management policy. Never use real production secrets in the lab.
Quiz
? Which statement about hashing, encryption and signatures is accurate?
+ Hashing is one-way; encryption is reversible with a key; a signature proves origin and integrity with a private key. :: Correct — three different jobs.
- Hashing and encryption both hide data reversibly. :: A hash cannot be decrypted.
- Signatures encrypt the message. :: A signature does not provide confidentiality.
- Base64 is lightweight encryption. :: It is an encoding with no key.
? Why must an AES-GCM nonce never be reused with the same key?
- It makes decryption slower. :: Performance is not the issue.
+ Reuse leaks the XOR of the plaintexts and allows authentication tags to be forged. :: GCM's security depends on unique nonces per key.
- The nonce is the secret key. :: The nonce is not secret.
- It is only a style rule. :: It is a hard security requirement.
? Which of these are cryptographic failures listed in the course? (select all that apply)
+ Hard-coded keys :: Anyone with the source has the key.
+ ECB mode for general data :: It reveals patterns in the plaintext.
+ Disabling certificate validation to fix a connection error :: It removes protection against interception.
- Using a maintained AEAD
…