Skip to content

Classic modes

ECB, CBC, CFB, OFB and CTR: the older ways to run a block cipher over a long message. They hide data but don't protect it, and that gap fed years of attacks on TLS.

Status and standards: see Cipher modes and AEAD in the main table.

AES encrypts one 16-byte block; a mode says how to use it on a whole message. FIPS 81 (1980) defined ECB, CBC, CFB and OFB for DES and was withdrawn in 2005. SP 800-38A (2001) has the same four for AES, plus CTR. None of them notices a changed ciphertext. In 2023 NIST decided to revise SP 800-38A, limit ECB to uses other NIST standards name, and push authentication. For new work use an AEAD.

Mode IV Parallel One flipped ciphertext bit
ECB none both ways garbles its block
CBC unpredictable decryption only garbles its block, flips that bit in the next
CFB unpredictable decryption only flips that bit, garbles the next 16 bytes
OFB unique no (keystream can be made ahead) flips that bit
CTR unique counter blocks both ways flips that bit

ECB

How it works. Each block is encrypted alone: Cᵢ = E(K, Pᵢ).

What goes wrong. Equal plaintext blocks give equal ciphertext blocks. The famous picture is the "ECB penguin": a bitmap of Tux, the Linux mascot, encrypted in ECB, and the penguin is still plain to see. Blocks can also be cut, swapped or replayed. Zoom's client up to 4.6.9 encrypted meeting audio and video this way (CVE-2020-11500). NIST still allows ECB for one-block jobs such as the PIV card challenge in SP 800-73-4.

CBC

How it works. XOR each plaintext block with the previous ciphertext block, then encrypt: Cᵢ = E(K, Pᵢ ⊕ Cᵢ₋₁), with C₀ the IV. The IV must be unpredictable. The message is padded to whole blocks; PKCS#7 adds n bytes of value n.

What goes wrong.

  • Predictable IVs. TLS 1.0 used the last ciphertext block of one record as the next IV, and BEAST (Duong and Rizzo, 2011) used that to read cookies. TLS 1.1 already had a fresh IV per record.
  • Padding oracles. Vaudenay (EUROCRYPT 2002): if the receiver lets slip whether the padding was valid, by an error or by timing, an attacker can decrypt any block by editing the one before it, at most 256 tries per byte. TLS 1.2 sends one alert for every error and always checks the MAC, and RFC 5246 says "a small timing channel" is left but too small to use.
  • Lucky 13. AlFardan and Paterson (IEEE S&P 2013) used it: the MAC takes longer or shorter depending on how much the padding strips off. The name is for the 13 header bytes in the TLS MAC. OpenSSL's fix later opened a new padding oracle (CVE-2016-2107).
  • POODLE. Möller, Duong and Kotowicz (Google, 2014): SSL 3.0 checks only the last padding byte, and the MAC doesn't cover the padding. After forcing a fallback to SSL 3.0, an attacker learns about one cookie byte per 256 requests. RFC 7568 (2015) deprecated SSL 3.0.

CFB

How it works. The previous ciphertext goes through the cipher to make keystream: Cᵢ = Pᵢ ⊕ E(K, Cᵢ₋₁). SP 800-38A also allows s-bit segments (CFB1, CFB8, CFB128). Only the forward cipher is used, and decryption gets back in step by itself after a damaged segment. OpenPGP uses a variant (RFC 4880, Section 13.9).

What goes wrong. The first block is P₁ ⊕ E(K, IV), so a repeated IV leaks P₁ ⊕ P₁′. Zerologon (CVE-2020-1472) shows how bad a fixed IV gets: Netlogon ran AES-CFB8 with an all-zero IV. For one key in 256 the first byte of E(K, 0) is zero, and then all-zero plaintext encrypts to all zeros. An attacker sending zeros got in after about 256 tries.

OFB

How it works. Encrypt the IV, encrypt that, and so on; XOR the keystream with the plaintext. It never depends on the data, so a bit error in transit damages only that bit.

What goes wrong. The usual stream cipher trouble: a repeated IV repeats the keystream, and flipped ciphertext bits flip the same plaintext bits. The IV must also not equal an earlier output block, or the keystream restarts partway. FIPS 81 allowed feeding back fewer than 64 bits, which Davies and Parkin (1982) showed cuts the average cycle from 2⁶³ steps to about 2³². SP 800-38A keeps only full-block feedback.

CTR

How it works. Encrypt a counter block and XOR it in: Cᵢ = Pᵢ ⊕ E(K, Tᵢ), with Tᵢ going up by one each block. Diffie and Hellman suggested it in 1979, but it waited until SP 800-38A for a NIST standard. It runs in parallel, any block decrypts alone, and there is no padding. GCM and CCM are CTR plus a MAC.

What goes wrong. A counter block must never repeat under one key, across all messages. If one does, C ⊕ C′ = P ⊕ P′, and one known plaintext gives the other. With random 64-bit nonces a repeat is expected after about 2³² messages. KRACK (Vanhoef and Piessens, CCS 2017) made WPA2 devices reinstall a key and reset the packet number, so CCMP reused keystream and packets could be decrypted.

Encrypt-then-MAC or MAC-then-encrypt

These modes need a MAC, and the order matters. Three old protocols picked one order each:

  • MAC-then-encrypt: MAC the plaintext, encrypt both. SSL and TLS up to 1.2.
  • Encrypt-and-MAC: encrypt, and MAC the plaintext alongside. SSH (RFC 4253).
  • Encrypt-then-MAC: encrypt, then MAC the ciphertext. IPsec ESP.

Bellare and Namprempre (ASIACRYPT 2000) proved encrypt-then-MAC secure for any secure cipher and MAC, and the other two not secure in general; Krawczyk (CRYPTO 2001) went through the three protocols. With encrypt-then-MAC the receiver checks the tag first and drops a forgery unread, so there is no padding check to probe. RFC 7366 (2014) added it to TLS as an extension. An AEAD does the same job in one algorithm.

Why TLS 1.3 dropped CBC

From SSL 3.0 to TLS 1.2, CBC ran MAC-then-encrypt, and each fix patched the same design: fresh IVs for BEAST, one alert and a MAC check every time for padding oracles, constant-time code for Lucky 13, then a bug in that code. RFC 7457 (2015) puts the padding oracles down to MAC-then-encrypt itself. TLS 1.3 (RFC 8446, 2018) kept only AEAD ciphers: AES-GCM, ChaCha20-Poly1305 and AES-CCM.

When to use them

  • Not for new designs. Use AES-GCM or ChaCha20-Poly1305.
  • CTR inside a bigger construction, or with an encrypt-then-MAC tag.
  • CBC, CFB, OFB only to read old data or talk to old systems. ECB only for one block.

Example (Python)

Python's standard library has no AES, so a toy 16-byte block cipher, four Feistel rounds over HMAC-SHA256, stands in. It is only there so the modes have something to call. First ECB against CBC on three equal blocks, then a padding oracle attack that reads a CBC block from the server's yes or no about the padding.

import hmac, os

def xor(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

def F(k, r, x):                    # round function: HMAC-SHA256 cut to 8 bytes
    return hmac.digest(k, bytes([r]) + x, "sha256")[:8]

def E(k, b):                       # toy 16-byte block cipher in place of AES
    L, R = b[:8], b[8:]
    for r in range(4):
        L, R = R, xor(L, F(k, r, R))
    return L + R

def D(k, b):
    L, R = b[:8], b[8:]
    for r in (3, 2, 1, 0):
        L, R = xor(R, F(k, r, L)), L
    return L + R

def cbc(k, iv, p):
    c = b""
    for i in range(0, len(p), 16):
        iv = E(k, xor(p[i:i + 16], iv))
        c += iv
    return c

key = os.urandom(16)
msg = b"YELLOW SUBMARINE" * 3                         # three equal blocks
c = cbc(key, os.urandom(16), msg)
print(len({E(key, msg[i:i + 16]) for i in range(0, 48, 16)}),   # ECB: 1 distinct block
      len({c[i:i + 16] for i in range(0, 48, 16)}))             # CBC: 3

def oracle(iv, c):                 # the server: is the padding good?
    p = xor(D(key, c), iv)
    return 1 <= p[-1] <= 16 and p[-p[-1]:] == bytes([p[-1]]) * p[-1]

def attack(prev, c):               # recover block c from the oracle alone
    mid = bytearray(16)            # becomes D(key, c), last byte first
    for i in range(15, -1, -1):
        n = 16 - i                 # aim for n padding bytes of value n
        f = bytearray(m ^ n for m in mid)
        for g in range(256):
            f[i] = g
            if oracle(bytes(f), c):
                if i == 15:        # rule out a lucky ...02 02 ending
                    f[14] ^= 1
                    if not oracle(bytes(f), c):
                        continue
                break
        mid[i] = g ^ n
    return xor(mid, prev)

iv = os.urandom(16)
c = cbc(key, iv, b"PIN=4417;user=jo" + bytes([16]) * 16)   # PKCS#7: a full padding block
print(attack(iv, c[:16]))          # b'PIN=4417;user=jo'

The attacker sends the block with a fake IV. When the padding is good, the last byte came out as 01, which gives the last byte of D(key, c). Then it aims for 02 02, and so on back to the first byte. XOR with the real IV gives the plaintext.

Security notes

  • CBC and CFB need an unpredictable IV; OFB and CTR need values that never repeat under the key.
  • Change keys long before 2⁶⁴ blocks with AES. With 64-bit blocks the limit is close enough to hit: see Sweet32.
  • To decrypt CBC at all, check a MAC over the ciphertext first, and never let errors or timing depend on the padding.

See also

References

Weaker sources: Wikimedia Commons, "Tux_ecb.jpg" for the ECB penguin; Tervoort, "Zerologon" whitepaper (Secura, 2020) for the CFB8 details.