Breaking the Affine Cipher
Learn how to break the Affine cipher by brute-forcing its entire 312-key keyspace and scoring each decryption with a common-word heuristic. It's the same strategy that cracks Caesar, just applied to a slightly bigger lock.
Interactive Affine Cipher Breaker
🔐 Affine Cipher Breaker
Breaking the Affine Cipher: A Slightly Bigger Lock, the Same Master Key
Introduction
The Affine cipher is often introduced as an upgrade over Caesar: instead of one shift key, it uses two numbers, a and b, combined as E(x) = (a·x + b) mod 26. That looks like it should be considerably harder to crack: two unknowns instead of one, after all. In practice, it barely matters. The constraint that a must be coprime with 26 collapses the keyspace down to just 312 possible key pairs. That’s small enough that a computer can try every single one in a fraction of a second and let simple English-word matching pick out the right answer. That’s exactly the same brute-force-plus-heuristic strategy that breaks the Caesar cipher.
Table of Contents
- Why Affine’s Keyspace Is So Small
- The Attack Strategy
- A Worked Example
- Python Implementation
- Interactive Visualizer
- Affine vs. Caesar: What Two Keys Actually Buy You
- Limitations of This Attack
- Modern Relevance
- Conclusion
- FAQ
- References
Why Affine’s Keyspace Is So Small
An affine map E(x) = (a·x + b) mod 26 is only reversible, only decryptable, when a has a modular inverse mod 26. That’s only true when gcd(a, 26) = 1, i.e. when a shares no common factor with 26. Since 26 = 2 × 13, that rules out every even number and every multiple of 13, leaving exactly 12 valid values of a: 1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25.
b has no such restriction. All 26 values from 0 to 25 are valid. So the full keyspace is:
12 valid values of a × 26 possible values of b = 312 total keys
312 is still small enough to exhaust completely. For comparison, Caesar’s keyspace is 25, or 26 if the identity shift is counted as a valid key. That 25-key space is exactly Affine’s a=1 slice: set a=1 and E(x) = (x + b) mod 26 is pure Caesar. Affine’s full keyspace is about 12.5× larger, but 312 combinations is nothing for a computer. The visualizer above works through all of them in a few seconds, the same way the Caesar breaker works through 25.
The Attack Strategy
The attack is a direct extension of Caesar’s brute-force approach:
- Enumerate every valid key. For each of the 12 valid
avalues, precompute its modular inversea⁻¹ mod 26(needed for decryption), then pair it with each of the 26 possiblebvalues. That’s 312 candidate keys in total. - Decrypt with each key. Affine decryption uses
D(y) = a⁻¹·(y − b) mod 26. Apply this to every letter of the ciphertext for each candidate key, preserving case and passing through non-letters unchanged. - Score each result. Count occurrences of common English words (“the”, “and”, “of”, “to”, and similar) in each decryption. This is the same heuristic used by the Caesar breaker.
- Pick the winner. The key pair producing the highest word-match score is almost certainly correct. Readable English stands out sharply against the other 311 attempts, which look like uniform gibberish.
Word-matching isn’t the only viable scorer here. Classical frequency analysis (comparing single-letter frequencies against English’s known distribution) works too, since Affine, like Caesar, is monoalphabetic and preserves those frequencies untouched. This breaker uses word-matching instead because 312 keys is small enough that it doesn’t need frequency analysis’s extra statistical machinery. Both attacks are exploiting the exact same underlying weakness.
This is a ciphertext-only attack: no crib, no known plaintext, nothing beyond the ciphertext itself is required. It works precisely because 312 is small enough to try exhaustively and English is structured enough that only the correct key produces recognizable words.
A Worked Example
Take the ciphertext "Zrwu wu i zcuz ah zrc ihhwvc swfrcp ivx wz wu vaz ripx za npcig", the visualizer’s default. Trying key a=5, b=8:
- Decryption formula:
D(y) = a⁻¹·(y − b) mod 26, wherea⁻¹ = 21(since5 × 21 = 105 ≡ 1 mod 26). Z(y=25):21 × (25 − 8) mod 26 = 21 × 17 mod 26 = 357 mod 26 = 19→Tr(y=17):21 × (17 − 8) mod 26 = 21 × 9 mod 26 = 189 mod 26 = 7→h- The same
a⁻¹ = 21handles every letter in the message; the key never changes mid-decryption, only the letter being fed into it does. - Working through the rest of the message the same way recovers
"This is a test of the affine cipher and it is not hard to break".
That decryption contains seven common-word matches (“a”, “of”, “the”, “and”, “it”, “not”, “to”), far ahead of every other candidate, most of which score 0 or 1 purely by chance. The gap between the correct key’s score and everything else is exactly what makes the winner unambiguous. With 312 candidates to sift through, a ciphertext whose correct decryption only contains one or two common words risks a coincidental tie with a wrong key. So a longer sample with a stronger word signal makes the heuristic reliable.
Python Implementation
The interactive visualizer above runs this exact attack in JavaScript. Here’s the same brute-force-plus-word-matching approach in Python: enumerate all 312 valid keys, decrypt with each, score against a small list of common English words, and keep the winner.
Key Features
- Exhaustive, not probabilistic: every one of the 312 valid
(a, b)pairs is tried. There’s no early-exit heuristic or randomness, so the result is deterministic and guaranteed to find the highest-scoring key. - Modular inverse computed directly:
mod_inversebrute-forces the inverse ofamod 26 by trial, exactly like the visualizer’s ownmodInverse, rather than relying onpow(a, -1, 26). Spelling the arithmetic out is more instructive for a piece explicitly about small-keyspace brute force. - Same scoring heuristic as the Caesar breaker: no dictionary, no NLP, just counting common short words, which is enough signal once the keyspace is small enough to exhaust.
Code
# affine_breaker.py
#
# Breaks an Affine cipher by brute-forcing every valid (a, b) key pair --
# only 312 of them exist, since a must be coprime with 26 -- and scoring
# each decryption by how many common English words it contains, the same
# heuristic used to break the Caesar cipher.
import re
COMMON_WORDS = ['the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'it', 'for', 'not', 'on', 'with']
# Only values coprime with 26 make E(x) = (a*x + b) mod 26 reversible.
VALID_A_VALUES = [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]
def mod_inverse(a, m=26):
for x in range(1, m):
if (a * x) % m == 1:
return x
raise ValueError(f"{a} has no inverse mod {m}")
def affine_encrypt(text, a, b):
result = []
for ch in text:
if ch.isalpha():
base = ord('A') if ch.isupper() else ord('a')
x = ord(ch) - base
result.append(chr((a * x + b) % 26 + base))
else:
result.append(ch)
return ''.join(result)
def affine_decrypt(text, a_inv, b):
result = []
for ch in text:
if ch.isalpha():
base = ord('A') if ch.isupper() else ord('a')
y = ord(ch) - base
result.append(chr((a_inv * (y - b)) % 26 + base))
else:
result.append(ch)
return ''.join(result)
def score_text(text):
lower = text.lower()
return sum(len(re.findall(rf'\b{word}\b', lower)) for word in COMMON_WORDS)
def break_affine(ciphertext):
best = {'score': -1, 'a': None, 'b': None, 'plaintext': ''}
for a in VALID_A_VALUES:
a_inv = mod_inverse(a)
for b in range(26):
candidate = affine_decrypt(ciphertext, a_inv, b)
score = score_text(candidate)
if score > best['score']:
best = {'score': score, 'a': a, 'b': b, 'plaintext': candidate}
return best
if __name__ == '__main__':
plaintext = "This is a test of the affine cipher and it is not hard to break"
a, b = 5, 8
ciphertext = affine_encrypt(plaintext, a, b)
print('Ciphertext:', ciphertext)
print()
result = break_affine(ciphertext)
print(f"Cracked! Key = (a={result['a']}, b={result['b']}) -- {result['score']} common word matches")
print('Recovered plaintext:', result['plaintext'])
Running this against the same default ciphertext used by the visualizer and the worked example above produces:
Ciphertext: Zrwu wu i zcuz ah zrc ihhwvc swfrcp ivx wz wu vaz ripx za npcig
Cracked! Key = (a=5, b=8) -- 7 common word matches
Recovered plaintext: This is a test of the affine cipher and it is not hard to break
This matches the visualizer’s result exactly, key and plaintext both.
For Fun: The Same Thing in ~15 Lines
In the same spirit as the Caesar Cipher Breaker’s 5-line one-liner (not as something to learn the algorithm from, that’s what the version above is for), here’s the whole attack compressed down to about 15 lines. The one trick worth pointing out: affine() handles both encryption and decryption with the same formula, by transforming the key when decrypt=True (a, b = a⁻¹, (−a⁻¹·b) mod 26) rather than writing a second function. Verified to produce byte-for-byte identical output to the readable version above: same ciphertext, same recovered key, same plaintext.
import re
COMMON_WORDS = ['the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'it', 'for', 'not', 'on', 'with']
VALID_A_VALUES = [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]
inv = lambda a, m=26: next(x for x in range(1, m) if (a * x) % m == 1)
def affine(text, a, b, decrypt=False):
if decrypt:
a, b = inv(a), (-inv(a) * b) % 26
return ''.join(chr((a * (ord(ch) - ord('A' if ch.isupper() else 'a')) + b) % 26 + ord('A' if ch.isupper() else 'a')) if ch.isalpha() else ch for ch in text)
score = lambda text: sum(len(re.findall(rf'\b{w}\b', text.lower())) for w in COMMON_WORDS)
def break_affine(ciphertext):
return max(({'score': score(p), 'a': a, 'b': b, 'plaintext': p}
for a in VALID_A_VALUES
for b in range(26)
for p in [affine(ciphertext, a, b, True)]),
key=lambda d: d['score'])
if __name__ == '__main__':
ciphertext = affine("This is a test of the affine cipher and it is not hard to break", 5, 8)
result = break_affine(ciphertext)
print(f"Ciphertext: {ciphertext}\n")
print(f"Cracked! Key = (a={result['a']}, b={result['b']}) -- {result['score']} common word matches")
print(f"Recovered plaintext: {result['plaintext']}")
That break_affine function is doing the entire brute-force search (312 candidate keys, decrypt, score, and pick the max) inside a single generator expression passed straight to max(). It’s a compact demonstration of how far Python’s generator and lambda syntax can go, and a decent argument for why the version above uses actual loops and variable names instead.
For Fun, Round Two: Even Tighter
Same attack, squeezed down further: the whole thing in 6 lines instead of 15. The trick that makes this one tighter: affine is a single lambda that handles both directions by calling an inner lambda with either (a, b) for encryption or the transformed (a⁻¹, −a⁻¹·b mod 26) for decryption. There’s no if decrypt: branch mutating variables, just one expression, called with different arguments. Verified to produce identical output to both versions above.
import re; W=['the','be','to','of','and','a','in','that','have','it','for','not','on','with']; A=[1,3,5,7,9,11,15,17,19,21,23,25]; inv=lambda a,m=26:next(x for x in range(1,m) if a*x%m==1)
affine=lambda t,a,b,d=False: (lambda A,B: ''.join(chr((A*(ord(c)-(65 if c.isupper() else 97))+B)%26+(65 if c.isupper() else 97)) if c.isalpha() else c for c in t))(*((inv(a),(-inv(a)*b)%26) if d else (a,b)))
score=lambda t:sum(len(re.findall(rf'\b{w}\b',t.lower())) for w in W)
def break_affine(ct):return max(({'score':score(p),'a':a,'b':b,'plaintext':p} for a in A for b in range(26) for p in [affine(ct,a,b,True)]),key=lambda d:d['score'])
if __name__=='__main__':
ct=affine("This is a test of the affine cipher and it is not hard to break",5,8); r=break_affine(ct); print(f"Ciphertext: {ct}\n"); print(f"Cracked! Key = (a={r['a']}, b={r['b']}) -- {r['score']} common word matches"); print(f"Recovered plaintext: {r['plaintext']}")
Worth noticing: the inner lambda’s parameters are named A and B, the same names as the outer A list of valid multipliers and the break_affine loop variable b. That’s not a bug; Python’s lexical scoping keeps the inner lambda’s A/B completely separate from the outer ones, since a lambda’s parameters always shadow anything with the same name from an enclosing scope. It’s exactly the kind of thing the readable version above exists to avoid.
Interactive Visualizer
Try it above. Enter any Affine-encrypted ciphertext, or use the default, and click Break Cipher. Watch the attempts table fill in as it works through all 312 (a, b) pairs. The “Matches” column tracks how many common English words each decryption produces, and the winning row is highlighted in green once the full sweep completes.
Affine vs. Caesar: What Two Keys Actually Buy You
It’s worth being honest about how little extra protection the second key parameter provides:
| Caesar | Affine | |
|---|---|---|
| Key parameters | 1 (shift) | 2 (a, b) |
| Keyspace size | 25 | 312 |
| Brute-force time (modern computer) | Instant | Instant |
| Vulnerable to | Brute force, frequency analysis | Brute force, frequency analysis |
| Structural weakness | Fixed shift preserves letter-frequency shape | Linear map still preserves letter-frequency shape, just relabeled |
Both ciphers are monoalphabetic: each plaintext letter always maps to the same ciphertext letter within a given key. That means the underlying letter-frequency distribution of English (E is common, Q and Z are rare, and so on) survives encryption completely intact, just relabeled. A 12.5× larger keyspace doesn’t change that fundamental structural weakness at all. It only makes the brute-force loop run slightly longer, and “slightly longer” here means an imperceptible fraction of a second either way.
Limitations of This Attack
This exhaustive-search-plus-word-matching approach has real limits worth knowing:
- Short ciphertexts are unreliable. With very little text, several keys might coincidentally produce zero or one word match apiece, creating a tie the heuristic breaks arbitrarily. With 312 candidates to sift through (versus Caesar’s 25), the odds of a spurious tie are meaningfully higher. A longer ciphertext with a stronger word signal matters more here than it does for Caesar.
- Non-English or unusual text breaks the word list. The scoring function only recognizes common English words; ciphertext encoding a different language, or text with unusual vocabulary, may not score correctly even with the right key.
- This technique doesn’t generalize to larger keyspaces. Affine’s 312 keys are exhaustible; a cipher with a keyspace of, say, 26! (like unkeyed monoalphabetic substitution) is not. That requires fundamentally different techniques like hill-climbing or simulated annealing, not brute force.
- This attack is overkill if you already have a crib. Two known plaintext/ciphertext letter pairs turn
aandbinto a small linear system that can be solved directly, no search required at all. Brute force is only the tool of choice here because Affine’s keyspace is small enough to make a smarter attack unnecessary, not because one doesn’t exist.
Modern Relevance
Like Caesar, Affine offers zero real-world security today. This article exists purely for educational cryptanalysis practice. Its lasting value:
- A clean second step after Caesar in any introductory cryptanalysis curriculum, showing that “more key parameters” doesn’t automatically mean “meaningfully more secure” if the keyspace is still small and the cipher is still monoalphabetic.
- A concrete illustration of modular inverses in action: computing
a⁻¹ mod 26is a small, tangible application of number theory that shows up throughout cryptography. - A natural bridge to substitution-cipher cryptanalysis, where brute force stops working and statistical/heuristic search techniques take over instead.
Conclusion
Doubling a cipher’s key parameters sounds like it should meaningfully raise the bar for an attacker. For Affine, it raises the brute-force search space from 25 to 312, a factor that a computer doesn’t even notice. The real lesson isn’t about Affine specifically; it’s that keyspace size only matters relative to what’s searching it. 312 keys was a lot to check by hand in the pre-computer era. It’s nothing today, and the same common-word heuristic that cracks Caesar in milliseconds cracks Affine just as fast.
FAQ
How many keys does the Affine cipher have?
312: 12 valid values of a (those coprime with 26) multiplied by 26 possible values of b.
Why can’t a be any number from 0 to 25?
Decryption requires computing a⁻¹ mod 26, which only exists when gcd(a, 26) = 1. Any a sharing a factor with 26 (i.e., any even number or multiple of 13) makes the encryption non-reversible: multiple plaintext letters would map to the same ciphertext letter, destroying information.
Is brute-forcing all 312 keys practical?
Trivially. A modern computer works through all 312 candidate decryptions and scores them in a fraction of a second, exactly as demonstrated in the visualizer above.
Does a larger keyspace make Affine meaningfully more secure than Caesar?
No. Both are monoalphabetic substitution ciphers, so both preserve English letter-frequency patterns and both fall to the same brute-force-plus-word-matching attack. 312 keys versus 25 makes no practical difference against modern computing power.
What would it take to make this attack fail?
Not much would help within Affine’s design. The fix would have to be a fundamentally different cipher. Polyalphabetic ciphers like Vigenère already resist this exact attack because a single key no longer produces one fixed substitution across the whole message.
References
-
Wikipedia. “Affine cipher.” Available at: https://en.wikipedia.org/wiki/Affine_cipher
-
Practical Cryptography. “Affine Cipher.” Available at: http://practicalcryptography.com/ciphers/affine-cipher/
-
Singh, Simon. “The Code Book.” Doubleday, 1999.