---
key: "crypto-ecb"
title: "ECB mode: why it leaks patterns and what to use instead"
url: "https://softwaresecurity.com/domains/identity-crypto/encryption/ecb-mode-leakage/"
api: "https://softwaresecurity.com/api/v1/articles/crypto-ecb.json"
domain: "identity-crypto"
topic: "Identity \u0026 cryptography"
type: "Reference"
identifiers: ["CWE-327","A04:2025","V11.3.1","V11.3.2","V11.3.3"]
tags: ["Encryption","Historical cryptographic attacks","Java"]
stages: ["design","build"]
roles: ["developer"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# ECB mode: why it leaks patterns and what to use instead

Derive exactly what ECB reveals and why a longer key cannot fix it, find where ECB enters real code, and replace it with an authenticated mode.

Canonical URL: https://softwaresecurity.com/domains/identity-crypto/encryption/ecb-mode-leakage/

## In short

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 explicit `MODE_ECB` options.
- Replace it with an AEAD and a versioned format, then re-encrypt stored ECB data.

## Verify it

1. Search the code and its dependencies for the ECB patterns listed above.
2. Encrypt a plaintext made of two identical 16-byte blocks and confirm the two ciphertext blocks differ.
3. Encrypt the same record twice and confirm the outputs differ.
4. Flip one ciphertext byte and confirm decryption fails without returning plaintext.

## 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.

Audience: Developers who call encryption APIs directly, and reviewers who find ECB in code or data formats.

Before you start:

- Understand equal-size plaintext blocks and deterministic encryption; no cipher implementation is required.

Time: About 18 minutes: 5 to read and 13 for the exercise

## A permutation applied independently to every block

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.

```text
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.
```

## What the observer must be able to compare

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.

## Choose the security property the record needs

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.

## What should you do today?



- Search code for `Cipher.getInstance("AES")`, `AES/ECB`, `MODE_ECB`, `modes.ECB` and `-ecb` cipher 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.

## Replace the Java default with AES-GCM

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.

```java
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
  }
}

```

## Verify equality leakage and the replacement boundary

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 material

```text
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.

Worked answer: 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.

Worked answer: 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.

Worked answer: 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.

Deliverable: An annotated equality map and three concrete replacement acceptance cases.

Scope: The small permutation is a teaching model. No real encryption is executed and no real plaintext is recovered.

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [NIST SP 800-38A: Block Cipher Modes of Operation](https://csrc.nist.gov/pubs/sp/800/38/a/final). December 2001.
- [RFC 5116: An Interface and Algorithms for Authenticated Encryption](https://www.rfc-editor.org/info/rfc5116/). January 2008.
- [OWASP Cryptographic Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html)
- [Java SE 21: javax.crypto.Cipher (provider default mode and padding)](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/javax/crypto/Cipher.html)