cryptography.education / stream ciphers / rc4

RC4, step by step — and the biases that killed it

A complete RC4 in plain client-side JavaScript: all 256 key-scheduling swaps, the keystream generation, and the XOR that turns plaintext into ciphertext — with the plain-English reason each step exists, the pitfalls that catch students, and a question to check yourself. Every number is computed live on this page, the keystream is compared against the published RFC 6229 test vectors, and the panel below the walkthrough measures RC4's keystream biases on your own machine, over thousands of random keys, printing the numbers it observes.

Live measurement: the keystream biases that killed RC4

This panel encrypts nothing. It runs the KSA and the PRGA on thousands of random 16-byte keys, counts what comes out, and prints the measured frequencies next to the ideal one — a uniform byte is 0 with probability exactly 1/256 = 0.00390625, so the “×256” column reads 1.000 for a perfect cipher. All of it is computed in your browser from the same core the walkthrough above uses.

random keys to generate: —

measuring…

WEP: how a 24-bit IV turned a weak cipher into a broken one

WEP needed a different keystream per packet, so it prepended a per-packet initialisation vector to the shared key and keyed RC4 with IV ‖ K. The IV travelled in the clear. That single design choice — not the cipher's arithmetic — produced the attacks that ended WEP.

stepwhat WEP doesconsequence
keyingsession key = 3-byte IV ‖ 5- to 13-byte shared key The first three key bytes are public. Every bias that leaks the first key bytes leaks the shared key.
IV space24 bits ≈ 16.7 M values, not checked for repeats By the birthday bound an access point starts reusing IVs after a few thousand packets. Same IV ⇒ same key ⇒ same keystream.
reusetwo packets, same IV, different plaintexts C1 ⊕ C2 = P1 ⊕ P2: the keystream cancels. Classic two-time-pad recovery, and no key was recovered.
FMS attackIVs of the form (3, 255, x) — a small, known set The first keystream byte then correlates with a key byte (the ≈1.39 × 1/256 excess measured above). After ~105–106 packets the shared key is recovered.
integrityCRC-32 “checksum”, not a MAC CRC is linear and RC4 is XOR, so an attacker who cannot read a message can still flip chosen bits and repair the checksum: injection and replay.

WPA's TKIP bolted on a per-packet key mix and a real MIC, which fixed the IV-collision and the bit-flipping — but not the biases. AlFardan, Bernstein, Paterson, Poettering and Schuldt (USENIX Security 2013) recovered plaintext from TLS and WPA-TKIP traffic using RC4 keystream biases alone; Vanhoef and Piessens extended it to packet injection. The countermeasures are history: RFC 7465 (2015) prohibits RC4 cipher suites in TLS, browsers stopped offering them, and CCMP/WPA2 replaced TKIP in wireless. RC4 must not be used for new work — not in a protocol, not in a file format, not as a “fallback”, and not “in addition to AES”.

1987RivestRC4 designed at RSA Security; kept as a trade secret.
1994leakSource posted anonymously to the Cypherpunks list; reimplemented everywhere as “ARC4”.
1995RoosWeak-key classes: the permutation after the KSA still carries the key's fingerprint.
2000FMSFluhrer–Mantin–Shamir key-recovery attack on WEP.
2001M–SMantin–Shamir: the second keystream byte is zero with probability 2/256 — measured below.
2013AlFardanStatistically sound plaintext recovery against TLS and WPA-TKIP.
2015RFC 7465RC4 prohibited in TLS; browsers and servers follow.

Notation

SThe state: a 256-byte array holding a permutation of 0…255. It begins as the identity permutation S[i] = i and is the only thing the algorithm carries between steps.
i, jTwo index bytes. i is a counter (0…255, then it wraps); j is “pseudo-random” — a running sum of state and key material. All arithmetic on them is modulo 256.
KThe key. A 1- to 256-byte string, used cyclically: KSA step i reads K[i mod keylen]. Here K also names the 256-byte expansion of that key.
KSAKey Scheduling Algorithm: 256 steps that turn the identity permutation into a key-dependent one.
PRGAPseudo-Random Generation Algorithm: the loop that emits one keystream byte per step.
ZrThe r-th keystream byte (the literature counts from 1). In the code on this page it is keystream[r-1].
⊕XOR: bitwise addition modulo 2 with no carries. XOR is its own inverse, so the operation that encrypts also decrypts.
0x…Hexadecimal bytes. The key and plaintext boxes accept either UTF-8 text or a hex byte string written as 0x0102030405 (spaces allowed).
drop n“RC4-dropn”: throw away the first n keystream bytes. Damage control, not a fix — see the glossary.

Glossary

The state is a permutation, and that is the whole design

RC4's only state is a 256-byte array holding every value from 0 to 255 exactly once, plus two index bytes i and j. The KSA shuffles it using the key; the PRGA keeps swapping one byte at a time and reads a byte out at a key-dependent offset. There is no S-box in ROM like AES's, no round constants like SHA-256's, no key-schedule tables and no block structure — the algorithm is about ten lines of code, which is exactly why it spread everywhere. A shuffle-based state is also hard to reason about: the proofs are combinatorial, not algebraic, and the shuffling is measurably not uniform. The panel above measures exactly that.

Stream cipher: XOR with a keystream, and why that costs you integrity

A stream cipher manufactures a keystream Z1, Z2, … and sets Cr = Pr ⊕ Zr. Decryption is the identical operation with the identical keystream, so the ciphertext is exactly as long as the plaintext: no padding, no block boundaries, and nothing in the code distinguishes “encrypt” from “decrypt”. The catch is that XOR is linear and malleable. Because Cr ⊕ Zr = Pr, flipping a bit of a ciphertext flips exactly the corresponding bit of the plaintext. An attacker who cannot read a message can still edit it in a controlled way — unless a MAC is computed over the ciphertext and checked on decryption. Confidentiality is not integrity, and a bare stream cipher supplies only the first.

Key (and IV, and nonce) reuse is fatal — the two-time pad

If the same key is used twice, the keystream is used twice. Encrypt P and Q with the same stream and C1 ⊕ C2 = P ⊕ Q: the keystream cancels completely and two “encrypted” messages become two plaintexts XORed together. Natural language is redundant enough to pull both out by hand — the classic Vigenère/crib-drag recovery. There is no key and no cipher to break; reuse is the break. That is why real stream ciphers are always driven by a fresh nonce or counter, and why WEP's 24-bit IV — which collides after a few thousand packets — was fatal by construction.

Why “drop the first 256 (or 768) bytes” is damage control, not a fix

RC4's best-known biases are worst in the first few keystream bytes, so a natural patch is to throw those bytes away: RC4-drop256, RC4-drop768, or the “RC4-offset” recommended for SSH in RFC 4345. This does help — a distinguisher that needs Z2 cannot use a stream that starts at Z768 — but it only moves the problem. The state after the KSA is still measurably non-uniform (the Roos correlation measured above is a property of the KSA, not of the first PRGA steps), and long-term biases remain at Z256, Z257, Z258, …, where “recycling” of the PRGA indices keeps producing distinguishable output. The statistical plaintext-recovery attacks against TLS and WPA-TKIP did not need the first byte; they needed several positions, not the first one. Dropping bytes buys time, not security: the only reliable answer is a cipher designed after 1994.

Where RC4 came from — “Ron's Code 4”

Ron Rivest designed RC4 at RSA Security in 1987. It was a trade secret; the name variously stands for “Rivest Cipher 4” and “Ron's Code 4”. In 1994 an anonymous post to the Cypherpunks mailing list published the source, and the algorithm spread through every library alive — SSL/TLS, WEP, WPA-TKIP, Microsoft Office, PDF, Lotus Notes and dozens of file formats. Because the trade mark forced renamings, it is often still found in code and configuration as “ARC4” — arcfour in SSH's cipher list, arc4 in OpenSSL and Python. The name is the only thing that ever changed.

The biases are structural, not a coding bug

It is tempting to think RC4 is “fine if implemented carefully”. It is not. The bias comes from the design: the KSA starts from a perfectly regular identity permutation and shuffles it only 256 times, with a j that is a running sum of key bytes. That is simply not enough shuffling to erase the key's fingerprint from the state. Roos showed in 1995 that the post-KSA permutation still carries linear combinations of the key bytes; Paul and Maitra gave the general formula this page measures; Mantin and Shamir showed the chosen output byte is redundant, which produces the second-byte zero bias. Every one of these is a property of the recipe. No fast implementation, no constant-time rewrite and no amount of discarded output removes them.

What replaced it

For protocols that need a stream, the modern choices are ChaCha20 (RFC 8439) and AES-CTR, or AES-GCM — always with a nonce, and GCM additionally supplying the authentication that bare RC4 never did. In wireless, WPA2's CCMP (AES-CCM) replaced TKIP and WPA3 replaced WPA2. The lesson generalises beyond RC4: a cipher whose output is statistically distinguishable from random is broken no matter how long the key is, and a tidy rewrite of a biased design is still biased.