Best Practices and Pitfalls
Most real-world crypto failures are not exotic ciphers - they are standard ciphers misused. This chapter collects the recurring mistakes, what each one breaks, and the habit that prevents it. Read it as a pre-merge checklist.
The Big Pitfalls
- ECB mode: identical plaintext blocks produce identical ciphertext blocks, so structure leaks. Use GCM or ChaCha20-Poly1305.
- Nonce/IV reuse with the same key: in GCM it breaks both confidentiality and authenticity; in CTR it XORs two plaintexts together. Generate a fresh nonce per message and store it with the ciphertext.
- Unauthenticated encryption: CBC or CTR without a MAC is malleable - flipped bits flip predictably. Prefer AEAD; if you add a MAC manually, do encrypt-then-MAC and check the MAC before decrypting (that kills padding oracles).
- MD5 / SHA-1 for security: both have practical collision attacks. SHA-256 is the floor; SHA-512 if you want headroom.
- Home-rolled crypto: "AES with a custom padding step" is a new cipher nobody has analyzed. Use library primitives exactly as designed.
- Predictable randomness:
random, time-based seeds, or user input as nonces. Security values come fromsecrets/os.urandom. - Weak or missing KDF work: a bare SHA-256 password hash cracks on GPUs in seconds. Use PBKDF2, scrypt, or Argon2 with a tuned work factor.
- Keys in source code: keys belong in a secret manager, environment, or a key file with 0600 permissions - and they rotate.
- Non-constant-time comparison:
==on tokens and MACs leaks a timing signal. Usecompare_digest. - Verification disabled "temporarily":
CERT_NONEandcheck_hostname = Falsein production are an open door. Verify, or do not connect.
Pre-Merge Checklist
- Is the primitive from a reviewed library (stdlib,
cryptography) with a standard mode? - Is every key generated by a CSPRNG, stored outside the code, and sized for its algorithm?
- Is every nonce/IV fresh per message, and stored where decryption can find it?
- Is the encryption authenticated (AEAD or MAC-first), and is the tag checked before use?
- Are digests SHA-256 or better, and are comparisons constant-time?
- Can a wrong key or tampered byte be distinguished from a valid one? If yes, you have an oracle - make the error uniform.
- Is there a rotation plan (MultiFernet, versioned container headers) and a revocation story?
When in Doubt
Prefer the boring choice: Fernet for application-level secrets, AES-256-GCM for bulk data, X25519 and Ed25519 for key exchange and signatures, TLS for transport, Argon2id for passwords. Each one is a solved problem behind a library call; novelty is where vulnerabilities hide.
Summary
Run the checklist on every crypto change: standard mode, fresh nonces, authentication on, CSPRNG keys, constant-time compares, verified TLS. When in doubt, pick the boring primitive - the goal is to make correctness a property of the library, not an achievement of your code.