Cheatsheet

Encryption Algorithms Cheatsheet

This cheatsheet lists the encryption algorithms a developer actually chooses between, with exact key sizes, nonce sizes, tag lengths and current status. It is for anyone picking a cipher, reading a library error such as "Invalid key length", or checking whether DES or RC4 is still acceptable. The common confusion it clears up is that a cipher (AES) and a mode (GCM, CBC) are separate choices, and the mode decides whether tampering is detected.

Quick reference

Symmetric ciphers

Algorithm Key size Block or stream Status
AES-128, AES-192, AES-256 128, 192, 256 bits 128-bit block Current standard (FIPS 197)
ChaCha20-Poly1305 256 bits Stream, 64-byte keystream blocks Current, standardized in RFC 8439
3DES (Triple DES) 168 bits nominal 64-bit block NIST disallowed encryption with it after Dec 31, 2023
DES 56 bits 64-bit block Broken, brute-forceable
RC4 Variable Stream TLS clients and servers must never negotiate it (RFC 7465)

Modes of operation

Mode Nonce or IV Tamper detection Notes
GCM 96 bits (12 bytes) recommended Yes, 128-bit tag Authenticated. Never reuse a nonce under one key
CBC 128 bits (16 bytes), unpredictable No Needs padding. Output length is a multiple of 16
CTR 128-bit counter block No Stream-like, no padding, no integrity
ECB None No Equal plaintext blocks give equal ciphertext blocks. Do not use
ChaCha20-Poly1305 96 bits (12 bytes) Yes, 128-bit tag Authenticated, fast without AES hardware

Asymmetric algorithms

Algorithm Purpose Sizes Notes
RSA-2048 Encrypt small data, sign 2048-bit modulus, 256-byte output About 112 bits of security
RSA-3072 Encrypt small data, sign 3072-bit modulus, 384-byte output About 128 bits of security
Ed25519 Sign only 32-byte keys, 64-byte signature Not an encryption algorithm
X25519 Key agreement 32-byte keys, 32-byte shared secret Derive a symmetric key from the secret
ML-KEM-512, 768, 1024 Post-quantum key encapsulation Three parameter sets NIST FIPS 203, August 2024

Which one to pick

For data at rest or in an API payload, use AES-256-GCM, or ChaCha20-Poly1305 where AES hardware is missing. For sending a secret to someone who has only your public key, use RSA-OAEP or an X25519 exchange. For proving who wrote something, sign with Ed25519 or RSA. Password storage is a different job entirely and uses a slow hash, not encryption.

Common patterns

Encrypt with AES-256-GCM and detect tampering

const c = require('crypto');
const key = Buffer.alloc(32, 1), iv = Buffer.alloc(12, 2);
const x = c.createCipheriv('aes-256-gcm', key, iv);
const ct = Buffer.concat([x.update('attack at dawn'), x.final()]);
const tag = x.getAuthTag();
console.log(ct.length, 'bytes ciphertext,', tag.length, 'bytes tag');
const bad = Buffer.from(ct); bad[0] ^= 1;
try {
  const d = c.createDecipheriv('aes-256-gcm', key, iv);
  d.setAuthTag(tag); d.update(bad); d.final();
} catch (e) { console.log('tampered:', e.message); }
14 bytes ciphertext, 16 bytes tag
tampered: Unsupported state or unable to authenticate data

Ciphertext is as long as the plaintext, and the 16-byte tag travels with it. Flipping one bit makes decryption fail instead of returning garbage. The key and IV above are fixed only so the run is repeatable; use random bytes in real code.

Generate a key and nonce of the right size

const c = require('crypto');
const k = c.randomBytes(32);
console.log(k.length, 'random key bytes;', c.randomBytes(12).length, 'random nonce bytes');
try { c.createCipheriv('aes-256-gcm', Buffer.alloc(16), Buffer.alloc(12)); }
catch (e) { console.log(e.code, e.message); }
32 random key bytes; 12 random nonce bytes
ERR_CRYPTO_INVALID_KEYLEN Invalid key length

AES-256 needs exactly 32 bytes. A 16-byte key is valid for AES-128 only, so the algorithm name and key length must match.

Predict CBC ciphertext length

const c = require('crypto');
const key = Buffer.alloc(32, 1), iv = Buffer.alloc(16, 3);
for (const n of [1, 15, 16, 17]) {
  const x = c.createCipheriv('aes-256-cbc', key, iv);
  console.log('plaintext', n, '-> ciphertext', Buffer.concat([x.update(Buffer.alloc(n)), x.final()]).length);
}
plaintext 1 -> ciphertext 16
plaintext 15 -> ciphertext 16
plaintext 16 -> ciphertext 32
plaintext 17 -> ciphertext 32

PKCS#7 padding always adds at least one byte, so an exact multiple of 16 grows by a full block.

Encrypt with RSA-OAEP and respect the size limit

const c = require('crypto');
const { publicKey } = c.generateKeyPairSync('rsa', { modulusLength: 2048 });
const enc = c.publicEncrypt({ key: publicKey, oaepHash: 'sha256' }, Buffer.from('attack at dawn'));
console.log('ciphertext', enc.length, 'bytes; max plaintext', 2048 / 8 - 2 * 32 - 2);
try { c.publicEncrypt({ key: publicKey, oaepHash: 'sha256' }, Buffer.alloc(191)); }
catch (e) { console.log(e.code); }
ciphertext 256 bytes; max plaintext 190
ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE

RSA-OAEP with SHA-256 on a 2048-bit key carries at most 190 bytes. For anything larger, encrypt the data with AES-GCM and encrypt only the AES key with RSA.

Sign with Ed25519 and agree on a key with X25519

const c = require('crypto');
const ed = c.generateKeyPairSync('ed25519');
const sig = c.sign(null, Buffer.from('attack at dawn'), ed.privateKey);
console.log('signature', sig.length, 'bytes');
const a = c.generateKeyPairSync('x25519'), b = c.generateKeyPairSync('x25519');
const s1 = c.diffieHellman({ privateKey: a.privateKey, publicKey: b.publicKey });
const s2 = c.diffieHellman({ privateKey: b.privateKey, publicKey: a.publicKey });
console.log('shared secret', s1.length, 'bytes, equal:', s1.equals(s2));
signature 64 bytes
shared secret 32 bytes, equal: true

Both sides compute the same 32-byte secret without ever sending it. Run it through a key derivation function before using it as an AES key.

Encrypt a file with the openssl CLI

echo -n 'attack at dawn' | openssl enc -aes-256-cbc -pbkdf2 -pass pass:hunter2 \
  -S 0102030405060708 -iv 000102030405060708090A0B0C0D0E0F -base64
BD8RUnPqe2H9z5JoCpXg4Q==

The -pbkdf2 flag derives the key from the password. This mode is CBC, so it has no tamper detection; the salt and IV are fixed here only to make the output repeatable.

Pitfalls

  • Reusing a GCM nonce: with the same key and nonce, XOR of two ciphertexts equals XOR of the two plaintexts, which leaks content. In a test run this printed ct1^ct2 == pt1^pt2: true. Generate a fresh random 12-byte nonce per message, or use a counter you never repeat.
  • Choosing ECB because it needs no IV: identical 16-byte plaintext blocks encrypt to identical ciphertext blocks, so patterns survive. A test of two equal blocks printed ecb block1==block2: true.
  • Using CBC or CTR without authentication: an attacker can flip bits and the decrypt call still succeeds. Use GCM or ChaCha20-Poly1305, or add an HMAC and verify it before decrypting.
  • Typing a password as the key: AES takes raw bytes of exactly 16, 24 or 32. Derive the key with PBKDF2, scrypt or Argon2. OWASP advises at least 600,000 PBKDF2-HMAC-SHA256 iterations when FIPS-140 is required.
  • Calling a hash "encryption": SHA-256 and MD5 are one-way and cannot be decrypted. Hash Generator is for integrity checks, not for protecting data you need back.
  • Encrypting large data with RSA: RSA-2048 handles 190 bytes with OAEP and SHA-256. Use hybrid encryption.
  • Expecting Ed25519 to encrypt: it only signs and verifies. Pair X25519 with an AEAD for confidentiality.
  • Keeping 3DES, DES or RC4 for compatibility: NIST stopped approving 3DES for encryption after December 31, 2023, and TLS forbids RC4. Migrate old data to AES-256-GCM.
  • Treating base64 as protection: it is an encoding. Base64 Encode / Decode converts ciphertext bytes to text for transport, and nothing more.

Related ZipKit tools

  • AES Encrypt / Decrypt — encrypts and decrypts text with AES-256 in CBC or GCM mode, entirely on your device
  • Hash Generator — MD5, SHA-1, SHA-256 and SHA-512 hashes of text, for integrity checks only
  • Base64 Encode / Decode — turns ciphertext bytes into text and back

Related cheatsheets