Lakshya

Security Engineering · Chapter 1 of 12

Cryptography for engineers who are not cryptographers

Eight decisions, one right answer each, and the rule that prevents almost every real-world failure.

7 min read1 diagramAll 12 chapters

You will almost never implement a cryptographic primitive. You will constantly choose between them, and the choices have exactly one correct answer each. This chapter is those answers, plus enough of the reasoning that you can defend them in a design review.

WHAT YOU WANT → WHAT TO USE. THERE IS EXACTLY ONE RIGHT ANSWER PER ROW. GOALUSENEVERWHY THE OLD ONE FAILED store a password argon2id, scrypt, bcrypt MD5, SHA-256, SHA-1 fast = cheap to brute-forceverify integrity HMAC-SHA256 a bare hash an attacker can recompute itencrypt data AES-GCM, ChaCha20-Poly1305 AES-CBC alone no authentication → padding oracleencrypt a large file AES-GCM with chunking one GCM call nonce/size limits per keysign a message Ed25519, ECDSA P-256 RSA PKCS#1 v1.5 padding attacks; use PSS if RSAexchange a key X25519, ECDH static RSA transport no forward secrecyrandom values the OS CSPRNG rand(), Math.random() predictable → forgeable tokenscompare secrets constant-time compare == or memcmp timing side channel And above all of it: use a maintained library at the highest level of abstraction that solves your problem — libsodium, Tink, theplatform KMS. Every row above is a mistake somebody made in production, and none of them were made by people using a well-designedhigh-level API.
Goal, primitive, and the deprecated choice with the reason it failed. Every ‘never’ in that table is a mistake made in production by a competent team, and none of them were made by people using a well-designed high-level library.

The rule that matters more than any of the details

Use the highest-level API that solves your problem

libsodium, Google Tink, your platform's KMS, your language's standard high-level interface. These make the wrong thing hard to express.

The moment you are choosing a mode of operation, generating a nonce, or writing a padding routine, you have descended a level and the failure modes multiply. Almost every catastrophic cryptographic failure in real systems was a competent engineer assembling correct primitives incorrectly — not a broken algorithm.

In an interview, “I would use AES-GCM through the platform's KMS rather than manage keys myself” is a better answer than a detailed account of GCM's internals, and it is the answer a security engineer wants to hear.

Passwords: why a fast hash is the wrong tool

SHA-256 is an excellent hash function and a terrible password hash, and the reason is that it is fast. Speed is a virtue for integrity checking and a catastrophe for password storage, because an attacker with a leaked database is running the same function billions of times per second on commodity hardware.

  • Use argon2id where available. It is memory-hard, so an attacker cannot get large speedups from GPUs or custom hardware. Tune the memory and time parameters so a single verification takes a few hundred milliseconds on your production hardware, and re-tune annually.
  • bcrypt is still acceptable and widely deployed. Note its 72-byte input limit, which silently truncates long passphrases — pre-hash if you allow them.
  • Salt is per-password and not secret. It defeats precomputed tables. Modern libraries handle it inside the hash string; if you are managing salts yourself, you are at the wrong level of abstraction.
  • A pepper is different — a secret held outside the database, mixed in before hashing. It means a database leak alone is insufficient, which is a real and cheap improvement, but only if the pepper genuinely lives somewhere else, such as a KMS.
  • Compare in constant time, and never log a password or include one in an error message. Both happen, routinely, through generic request logging.

Encryption: authenticated, or you have not encrypted anything

Encryption without authentication lets an attacker modify the ciphertext, and the ways that goes wrong are not obvious. The padding oracle attack recovers plaintext entirely without the key, using nothing but the difference between a padding error and a decryption error — which is why AES-CBC without a MAC has broken so many real systems.

Use AEAD — authenticated encryption with associated data. AES-GCM or ChaCha20-Poly1305. These encrypt and authenticate in one operation and fail closed.

The nonce rule, which is the one thing you must not get wrong. Never reuse a nonce with the same key in GCM. Reuse does not merely weaken it — it allows recovery of the authentication key, and from there arbitrary forgery. This is not a theoretical weakness; it has broken shipped products.

So either use a random 96-bit nonce and rotate the key well before the birthday bound, or use a strict counter you can guarantee never repeats, including across restarts, replicas and restores from backup. The third case is what catches people: a snapshot restored to a second instance happily reuses the counter.

If you cannot guarantee it, use a nonce-misuse-resistant mode such as AES-GCM-SIV, or hand the problem to a KMS that manages keys and nonces for you. Recognising this constraint unprompted is a strong signal.

Key management, which is the actual hard part

  • Envelope encryption is the pattern to know. A data key encrypts the data; a master key in a KMS (key management service) or HSM (hardware security module) encrypts the data key; the encrypted data key is stored beside the ciphertext. Rotating the master key then means re-encrypting only the small data keys, not petabytes of data. Every cloud KMS implements this and it is worth being able to explain.
  • A key has a lifecycle: generate, distribute, rotate, revoke, destroy. Most organisations implement the first and none of the rest, which is why key rotation is a project rather than an operation.
  • Rotation limits blast radius; it does not fix a compromise. If a key leaked six months ago, rotating today protects future data and not past data. Say that plainly — rotation is frequently proposed as if it were remediation.
  • Separate keys by blast radius: per tenant, per environment, per data classification. One key for everything means one compromise for everything.
  • Never commit a key. And when one is committed, rotating it is mandatory — removing the commit is not sufficient, because git history is distributed and the value is already in every clone and almost certainly in a scanning bot's database within minutes.

Post-quantum, in the amount worth knowing now

Standardised post-quantum algorithms exist — ML-KEM for key encapsulation, ML-DSA and SLH-DSA for signatures — and hybrid key exchange combining a classical and a post-quantum algorithm is already deployed in TLS by major browsers and CDNs.

The practically important idea is harvest now, decrypt later: an adversary can record encrypted traffic today and decrypt it when capable hardware exists. That makes the urgency proportional to how long your data must stay confidential. For a session cookie, it does not matter. For state secrets, medical records or long-lived intellectual property, it already does. That framing — urgency scaled to confidentiality lifetime rather than to a predicted date — is the mature answer.

The other thing worth saying is that crypto-agility is the real deliverable: being able to change algorithm without re-architecting. Most systems cannot, because the algorithm is assumed in a hundred places, and that inflexibility is the thing to fix now.

Check yourself

Why is SHA-256 wrong for passwords?It is fast, and speed is exactly what an attacker with a leaked database wants. Use a deliberately slow, memory-hard function — argon2id, scrypt or bcrypt.
What happens if you reuse a nonce in AES-GCM?You can leak the authentication key, which allows arbitrary forgery. It is not a gradual weakening; it is a total break, and restored backups are a common cause.
What is envelope encryption for?So master-key rotation re-encrypts small data keys rather than all your data. It also keeps the master key inside a KMS or HSM where it can never be exported.
A key was committed to git a year ago. Is deleting the commit enough?No. Rotate it. History is distributed, it is in every clone, and public repositories are scanned within minutes.
← All chaptersThe vulnerability classes that still matter →