Keys and operations
Generate keys securely
Use cryptographically secure platform or key-management APIs with the exact size and algorithm required instead of deriving keys from memorable text.
A key is only as strong as the randomness behind it. A 256-bit key gives you 256 bits of security only if every bit came from a source the attacker cannot model. Phrases people invent and ordinary random functions do not qualify.
Math.random() is the classic mistake in JavaScript. It exists for shuffling animations, not for secrets. It is not a CSPRNG, a cryptographically secure pseudorandom number generator, and its outputs can be predicted from previous outputs. The same goes for timestamps, process IDs, and anything else an attacker can guess or observe.
Use the platform’s secure source
Let a maintained library, the operating system, an HSM, or a KMS generate your keys. In Node, node:crypto reads from the operating system’s CSPRNG:
import { randomBytes } from 'node:crypto'
const key = randomBytes(32)
That is a full-strength 256-bit key. On the command line, openssl does the same job:
openssl rand -hex 32
# 41d1e2c7a80f5b9e6d3c7f2a1b8e4d90c5a6f3e2d1b0a9c8e7f6d5c4b3a29180
Match the exact size and algorithm the construction wants. 32 bytes for AES-256. 12 bytes for a GCM nonce. Key pairs through generateKeyPairSync, never from raw bytes you shaped yourself. For random integers and identifiers use randomInt() and randomUUID() from the same module. Never Math.random().
Why keys derived from text fail
Say we derive an encryption key by running SHA-256 over a memorable deployment phrase. The output looks perfectly random: 64 hex characters. But an attacker with some ciphertext can test likely phrases offline, far faster than searching a random key space. The key space collapsed from 2^256 down to “phrases a person would pick”.
There is one case where a password must produce key material: when the user’s passphrase is the only input available, like encrypting a local vault. Then use a real password KDF with a salt and current parameters, never a bare hash. And accept that the key is only as strong as the passphrase.
Two operational rules
Never print a generated key while debugging. Logs outlive the debugging session and flow into systems with much wider access than your terminal. A key in a log line is a leaked key.
And if key generation fails, it must fail loudly. Don’t write a fallback that seeds a generator from the timestamp when the secure source is unavailable. That fallback is a silent catastrophe, because the keys look fine and are trivially guessable. Let the code throw instead.
Try this on your own project: trace the generation API and random source for every key in a small application. Record the algorithm and length for each one, without recording the bytes. Generate two keys and confirm they differ and match the library’s expected format. Then block or mock the random source and confirm generation fails instead of falling back to a timestamp or Math.random().
Lesson completed