This document collects practical guidance for building and reviewing encryption and decryption protocols. It consolidates best practices for symmetric and public-key protocols, hybrid encryption, authenticated encryption (AEAD), key exchange and key derivation, nonce/IV management, authentication and replay protection, error handling, and operational controls. The goal is to help engineers design secure, interoperable systems and avoid common pitfalls.
- Confidentiality: prevent unauthorized parties from reading plaintext.
- Integrity: detect accidental or malicious modification of ciphertext.
- Authenticity: ensure the sender is who they claim to be (or that the key used is authorized).
- Freshness / replay protection: prevent old messages from being replayed.
- Forward secrecy: compromise of long-term keys should not expose past session keys.
Design protocols with these goals in mind and prefer standardized constructions (TLS, SSH, IPsec) over home-grown designs.
Use AEAD primitives (e.g., AES-GCM, AES-CCM, ChaCha20-Poly1305) whenever possible. AEAD combines confidentiality and integrity in a single primitive and avoids many composition mistakes.
If AEAD is unavailable and you must combine primitives:
- Use Encrypt-then-MAC (EtM): compute ciphertext = Enc_K1(plaintext) then tag = MAC_K2(ciphertext || associated_data); verify MAC before decryption.
- Avoid MAC-then-Encrypt and plain Encrypt-then-Decrypt without authentication.
Include associated data (A) in the authenticated input when context (headers, sequence numbers, protocol metadata) must be bound to the ciphertext.
Public-key schemes (RSA, ECIES) are typically used to protect symmetric keys rather than bulk data. Typical pattern:
- Generate a fresh symmetric key K_s (and optionally a nonce/IV) per message or session.
- Encrypt bulk data with an AEAD scheme using K_s.
- Encrypt K_s with the recipient's public key using a secure KEM/DEM or RSA-OAEP.
- Send (encrypted_K_s, nonce, ciphertext, tag, associated_data).
This hybrid approach limits expensive public-key operations and leverages AEAD for integrity.
Use standardized KEMs (e.g., ECIES variants, KEMs in TLS 1.3) and prefer ephemeral keys for forward secrecy.
- Use authenticated Diffie–Hellman (Ephemeral DH or ECDHE) for session key agreement and forward secrecy.
- Authenticate DH exchanges with signatures or an authenticated channel (certificates, pre-shared keys) to avoid man-in-the-middle attacks.
- Prefer TLS 1.3 or modern authenticated key exchange protocols (Noise, Signal's X3DH/Double Ratchet) for robust forward secrecy and key management.
Correct nonce/IV handling is crucial:
- AEAD modes (CTR/ChaCha20/etc.): nonces must never repeat under the same key. Reuse can catastrophically break confidentiality/integrity.
- AES-GCM: reuse of nonce+key can allow tag forgery and plaintext recovery; follow library RFCs (e.g., RFC 5288/5116) for nonce construction.
- CTR: nonce+counter pair must be unique; prefer deterministic counters or random nonces with sufficient length to make collisions negligible.
- CBC: IV should be unpredictable (random) or derived from a secure nonce scheme; do not reuse IVs with the same key for different messages if the mode requires randomness.
Practical patterns:
- Per-session counter: derive a session key and then use a block counter per packet (safe if combined with a unique session nonce).
- Random nonces: use CSPRNG-generated nonces of full length where allowed by the mode; include the nonce with the ciphertext.
- Deterministic IVs: for disk encryption, consider deterministic schemes like SIV (synthetic IV) that provide misuse-resistance.
Document the nonce lifecycle and enforce checks to prevent accidental reuse.
Always authenticate protocol metadata that affects interpretation: sequence numbers, frame headers, version numbers, sender/recipient IDs, and message types. AEAD associated_data is the usual place to include this.
Do not assume implicit binding. Explicitly include and check associated data during decryption.
Common defenses against replays:
- Sequence numbers: include monotonic sequence numbers in associated data and reject out-of-order or previously-seen values.
- Timestamps with bounded skew: include and validate timestamps, but require secure time and check for clock drift.
- Nonce-based single-use tokens: store recent nonces or use stateless techniques like SIV that guarantee uniqueness for given inputs.
Choose the mechanism appropriate for protocol constraints (stateless vs stateful receivers) and document windowing/retention policies.
- Be conservative with error messages. Do not reveal whether decryption failed due to authentication failure versus padding/format errors—uniform error signals prevent oracles.
- Use constant-time code paths for secret-dependent branches where feasible.
- Treat timing and response differences as potential oracles (Bleichenbacher-style attacks rely on subtle differences in error behavior).
- Never derive multiple independent keys by reusing the same raw key material without a proper KDF. Use HKDF (HMAC-based KDF) or other vetted KDFs to derive separate keys for encryption, MAC, and IV/nonce derivation.
- Define clear key lifetimes and rotation policies. Rotate keys before they approach the risk of compromise (e.g., after fixed time or data volume thresholds).
- Use separate keys for different purposes (encryption vs authentication) — derive them via KDF from a single master secret.
Example HKDF usage: derive (K_enc, K_mac, base_nonce) = HKDF(master_secret, info=protocol_context).
- For certificate-based systems, perform proper certificate validation: chain validation, expiration, revocation checking (OCSP/CRLs or stapling), and hostname checks.
- Avoid trusting self-signed certificates unless explicitly provisioned and pinned.
- For signed messages, use modern signature schemes with appropriate padding: RSASSA-PSS or Ed25519/Ed448 rather than PKCS#1 v1.5 signatures.
- Always verify integrity/authenticity before acting on decrypted data.
- When using CRT optimizations or hardware accelerators, validate results (e.g., re-encrypt or check MAC) to detect faults or tampering.
- Log decryption failures and suspicious events, but avoid logging sensitive plaintext or keys.
- Maintain an incident response plan for key compromise: revoke keys/certificates, rotate keys, and audit recent activity.
- AEAD with hardware acceleration (AES-NI, ARMv8 SHA/ASM) is fast; ChaCha20-Poly1305 may be faster on systems without AES hardware.
- Measure CPU vs memory vs latency trade-offs for your workload; streaming large payloads favors chunked AEAD with incremental associated data.
- TLS 1.3: a modern, well-vetted protocol that combines authenticated key exchange (ECDHE), AEAD ciphers, and forward secrecy by default — use it rather than building your own transport security.
- Noise protocol framework: a simple, modern toolbox for building secure channel protocols with clear patterns for key exchange, forward secrecy, and ratcheting.
- RFCs: RFC 5116 (AEAD), RFC 5869 (HKDF), RFC 8017 (PKCS#1) and the TLS RFCs for concrete parameter guidance.
- Use AEAD (AES-GCM, ChaCha20-Poly1305) whenever possible.
- For public-key transport, use hybrid encryption (KEM + AEAD) or authenticated DH (ECDHE) with ephemeral keys for forward secrecy.
- Ensure nonces/IVs are unique for each key; document and enforce lifecycle.
- Authenticate associated protocol context explicitly (headers, sequence numbers) via AEAD associated data.
- Use HKDF to derive separate keys and nonces from a master secret.
- Validate certificates and use modern signature schemes (PSS, EdDSA).
- Return uniform errors for decryption/authentication failures and mitigate timing oracles.
- Plan for key rotation, logging, and compromise recovery.