Cryptographic goals

Never invent cryptography

Use maintained high-level libraries and established constructions instead of designing algorithms, modes, padding, or message formats yourself.

Cryptographic code can look correct and fail catastrophically. It compiles, it round-trips your test data, and it is still broken in ways you cannot see from the output.

This is the one field where “it works on my machine” means nothing. The attacks that matter exploit structure, timing, and error behavior, not bugs that throw exceptions.

What going wrong looks like

A developer encrypts with AES-CBC and adds a custom checksum. The design looks reasonable, but it accepts modified ciphertext before the checksum is checked and leaks useful error differences. That error difference is the basis of padding-oracle attacks, which recover plaintext without ever touching the key.

Another classic: picking ECB mode because it is the shortest name in the list.

// never do this
import { createCipheriv } from 'node:crypto'
const cipher = createCipheriv('aes-256-ecb', key, null)

ECB encrypts every 16-byte block independently. Identical plaintext blocks produce identical ciphertext blocks, so patterns in your data survive encryption and stay visible to anyone reading the ciphertext.

Neither mistake is a coding error. Both are design errors, made by choosing primitives instead of a construction.

What to use instead

Prefer APIs that choose secure algorithms, generate nonces, authenticate ciphertext, and define a versioned format. In practice that means an AEAD construction such as AES-GCM or ChaCha20-Poly1305 exposed through a maintained library, not a cipher mode you assembled yourself.

Follow the library’s current documentation and run its published test vectors, so you know your integration produces the exact bytes the specification expects:

node --test crypto-wrapper.test.js
# ✔ matches NIST GCM test vector (0.8ms)

Keep the cryptographic boundary small enough to review and replace. One module in your codebase should own encryption and decryption. Everything else calls it. When guidance changes, you have one file to update.

One more habit: a high-level construction may create a format migration later, so store an explicit version with every ciphertext from the start. A single byte prefix is enough.

Practice

Inventory every direct cryptographic call in one project and map each call to its security goal. Replace or wrap one low-level composition with a maintained high-level API, then save passing published test vectors. Corrupt the ciphertext, nonce, and tag separately and prove every case fails without returning plaintext.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →