Do not use ECB. It encrypts each 16-byte block on its own, so equal plaintext blocks give equal ciphertext blocks, and it detects no tampering. Name an authenticated mode explicitly, such as AES-256-GCM with a fresh 96-bit nonce or ChaCha20-Poly1305, through a maintained library. Watch for defaults: in Java, Cipher.getInstance("AES") means AES/ECB/PKCS5Padding.
- Under one key, equal 16-byte plaintext blocks always produce equal ciphertext blocks.
- A longer key does not help: the leak comes from the mode, not the key size.
- ECB has no integrity: blocks can be swapped, deleted or replayed without any error.
- ECB usually enters code through defaults, such as Java’s
Cipher.getInstance("AES"), or explicitMODE_ECBoptions. - Replace it with an AEAD and a versioned format, then re-encrypt stored ECB data.
- Search the code and its dependencies for the ECB patterns listed above.
- Encrypt a plaintext made of two identical 16-byte blocks and confirm the two ciphertext blocks differ.
- Encrypt the same record twice and confirm the outputs differ.
- Flip one ciphertext byte and confirm decryption fails without returning plaintext.
- Applies to
Historical ECB leakage and modern migration decisions. The toy block exercise is not an encryption recipe.
- Part of
- Learning path: Cryptography without shortcuts (step 9 of 12). Workflow: Fix a finding
Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.
Learning goals and prerequisites
What you will learn
- Derive equality leakage from ECB’s per-block equation.
- Distinguish pattern evidence from guessed plaintext.
- Specify a migration and negative tests for an authenticated format.
Before you start
Developers who call encryption APIs directly, and reviewers who find ECB in code or data formats.
- Understand equal-size plaintext blocks and deterministic encryption; no cipher implementation is required.
AES transforms one 16-byte block under a key. ECB extends that primitive to a message by encrypting each block independently: Cᵢ = Eₖ(Pᵢ). Because encryption under one key is a permutation, two equal plaintext blocks produce equal ciphertext blocks, and two equal ciphertext blocks imply equal plaintext blocks under that key. An observer can therefore recover an equality map without recovering the key or the plaintext.
Consider a fixed-width payroll export whose department field always starts on a 16-byte boundary. Repeated department blocks produce repeated ciphertext blocks. Once an observer learns that one block means SALES, every equal block acquires that meaning. An image is a memorable demonstration, but database rows, repeated headers, and padded short identifiers are equally useful examples. The leakage depends on alignment and repetition, not on whether the file is visually recognizable.
Plaintext blocks: A | B | A | C | B
ECB ciphertext: x | y | x | z | y
Known mapping: B = "department=SALES"
Deduction: blocks 2 and 5 carry the same known department.The equality claim is within the same key and block boundaries. Seeing equal ciphertext under two different keys does not establish equal plaintext. A change of serialization can move a field across a block boundary and hide this particular pattern without making the construction secure. Likewise, compression may remove visible repeated blocks while introducing other information leakage; it is not a security repair.
If the attacker can request encryptions of chosen candidate values under the target key, deterministic encryption becomes a dictionary oracle. For a field with only four possible values, encrypting those four padded values identifies every corresponding ciphertext. More entropy in the AES key does nothing to enlarge that four-value plaintext dictionary. With no chosen-plaintext access, frequency and context may still reveal information, but an equality map alone does not prove a particular value.
For ordinary application records, use a supported authenticated-encryption construction with correct nonce handling; the AEAD record guide at /domains/identity-crypto/encryption/authenticated-record-design/ covers nonces and associated data. Distinct nonces make repeated plaintexts encrypt differently; the tag also detects tampering. ECB has no authenticity: replacing, deleting, or reordering complete encrypted blocks need not cause any cryptographic failure. A parser rejecting malformed records can catch some modifications but cannot authenticate the remaining valid ones.
Do not add an IV field to an ECB record and assume that fixes the mode: ECB does not consume an IV. Do not invent a random prefix scheme, because blocks after the prefix still have independent deterministic behavior. Searchable encryption and deterministic tokenization are separate designs with deliberate equality leakage; choosing one requires a documented use case and threat model rather than rebranding ECB.
Migration needs a versioned format. New writes use the new format; old reads decrypt only explicitly marked legacy records and then rewrite them. Do not let an authentication failure trigger a silent fallback to ECB. Retain old decryption capability only for the migration window and backups that still require it.
- Search code for
Cipher.getInstance("AES"),AES/ECB,MODE_ECB,modes.ECBand-ecbcipher names, including in dependencies. - Replace each with an AEAD, such as AES-256-GCM or ChaCha20-Poly1305, through a library that chooses the nonce for you where possible; the nonce lab at /labs/nonces/ shows what goes wrong otherwise.
- Add a versioned record format, decrypt legacy ECB records only on an explicit legacy path, and rewrite them.
- Add a test that encrypts two equal blocks and fails if the ciphertext blocks match.
In Java, a transformation without a mode falls back to the provider’s default, and the JDK’s default provider uses ECB. This class shows the fix: name AES/GCM/NoPadding, generate a fresh 96-bit nonce for every record, keep the 128-bit tag, and authenticate the record’s context. It was tested on JDK 21, which confirmed that Cipher.getInstance("AES") produces repeated ciphertext blocks identical to AES/ECB/PKCS5Padding, while the GCM version produces different output for the same input and rejects a changed byte or a wrong context.
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
public final class RecordCipher {
private static final SecureRandom RANDOM = new SecureRandom();
// Vulnerable: Cipher.getInstance("AES") names no mode, and the JDK's default provider
// fills in AES/ECB/PKCS5Padding, so equal 16-byte blocks give equal ciphertext blocks.
// Fixed: name AES-GCM, use a fresh 96-bit nonce and authenticate the record's context.
public static byte[] encrypt(SecretKey key, byte[] plaintext, byte[] context) throws Exception {
byte[] nonce = new byte[12];
RANDOM.nextBytes(nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, nonce));
cipher.updateAAD(context);
return ByteBuffer.allocate(12 + plaintext.length + 16).put(nonce).put(cipher.doFinal(plaintext)).array();
}
public static byte[] decrypt(SecretKey key, byte[] record, byte[] context) throws Exception {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, record, 0, 12));
cipher.updateAAD(context);
return cipher.doFinal(record, 12, record.length - 12); // AEADBadTagException on any change
}
}The exercise uses an explicitly supplied substitution table, not AES. It isolates the mode behavior so the conclusion can be checked without a library. It proves how independently deterministic blocks leak patterns; it does not measure the security of a real cipher.
For a real replacement, confirm that repeated encryption of identical plaintext under one key yields distinct nonces and ciphertexts, that decryption restores the original bytes, and that changing a ciphertext byte or record context returns failure without plaintext. A single successful round trip only proves that the two functions agree. Record the algorithm, nonce allocation, authenticated context, and legacy-read cutoff as separate review decisions.
Guided practice
An analyst receives a toy block-encrypted export and one known field. Recover only what the evidence permits.
Supplied scenario material
Toy permutation under key K: A→7, B→2, C→9, D→4.
Record 1 plaintext: A B A C
Record 2 ciphertext: 7 4 7 2
Known fact: B means SALES. A, C, D remain unknown.1. Encrypt and compare
Write Record 1 ciphertext and mark equal positions across both records.
Expected result and reasoning
Record 1 is 7 2 7 9. Positions 1 and 3 repeat within each record and across records. Their underlying value is A, but its business meaning is not supplied.
2. Separate knowledge from guessing
Identify every SALES block; then decide whether ciphertext 4 means ENGINEERING.
Expected result and reasoning
SALES appears at Record 1 position 2 and Record 2 position 4. No supplied fact identifies D or ciphertext 4 as ENGINEERING.
3. Test a partial repair
Add an ignored IV field to both records, then propose an authenticated replacement acceptance test.
Expected result and reasoning
An ignored IV changes no block and fixes nothing. A replacement must round-trip, use distinct nonces, and reject a changed tag or wrong record context before releasing bytes.
The small permutation is a teaching model. No real encryption is executed and no real plaintext is recovered.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
Reference information
- SDLC phase
- DesignDevelopment
- Role
- Developers
- ASVS 5.0.0 requirements
- v5.0.0-11.3.1 · v5.0.0-11.3.2 · v5.0.0-11.3.3