Zerologon (AES-CFB8 Zero-State Keystream Collision)
- •
What it is
- •
AES-CFB8 encrypts one byte at a time by encrypting a 16-byte "state" (seeded from the IV) with the block cipher and using only its first output byte as keystream, then shifting the state left one byte and feeding a new byte back in. During decryption, the byte fed back in is the ciphertext byte, not the plaintext byte. If an attacker submits an all-zero ciphertext with an all-zero IV, the state starts at 16 zero bytes and — since it only ever gets zero bytes appended — stays all-zero for the entire decryption. That means every decrypted byte equals the same fixed value: the first byte of . That fixed byte is uniformly distributed for a random key, so there's exactly a 1/256 chance it's zero — and when it is, the "decrypted" output (and anything derived from it, like a password) comes out entirely as zero bytes. This is the same vulnerability class as the real-world Zerologon (CVE-2020-1472), where Windows' Netlogon protocol used AES-CFB8 with a zero IV in a way that let an attacker authenticate with roughly 1-in-256 odds per attempt.
- •
- •
When to apply
- •
A protocol uses AES-CFB8 (or another byte-feedback stream mode) with an attacker-controlled or fixed zero IV, decrypts attacker-supplied all-zero ciphertext, and derives something security-sensitive (a password, a session key) directly from the decrypted bytes.
- •
- •
Math
- •
For decrypt: . If for all ii i and , then for all , so every output byte equals — a single fixed value depending only on the key. for a uniformly random key.
- •
- •
Worked example
- •
For one random key, an all-zero CFB8 token under an all-zero IV decrypts to a repeated byte. The attack succeeds when , with probability about per independent key. After independent resets, the chance of at least one hit is ; at it is about .
- •
- •
Python
- •
def cfb8_decrypt_allzero(key, n): from Crypto.Cipher import AES cipher = AES.new(key, AES.MODE_ECB) state = b"\x00" * 16 out = b"" for _ in range(n): b = cipher.encrypt(state)[0] out += bytes([b]) state = state[1:] + b"\x00" # ciphertext byte fed back is always 0 return out
- •
- •
- •
Cards
- •
Why does the internal CFB8 state stay all-zero throughout decryption of an all-zero ciphertext?
- •
Because decryption feeds the ciphertext byte (always 0 here) back into the state, so a state that starts at all-zero never changes.
- •
- •
What's the probability a random AES key produces a zero first-output-byte when encrypting an all-zero block?
- •
1/256, since AES output bytes are effectively uniformly distributed for a random key.
- •
- •