Why encrypt two letters at a time?
The Caesar cipher swaps one letter for another, so the letter counts of English survive and give the key away. Playfair encrypts pairs of letters (digraphs). There are 26 single letters but 600 different pairs, so the most common pair, TH, is far less obvious than the most common letter, E.
Charles Wheatstone invented it in 1854 and his friend Lord Playfair promoted it, which is how it got the wrong name. The British army used it in the Boer War and the First World War because it was fast to do by hand and needed nothing but a pencil.
Step 1: build the key square
- Write the letters of the key word into a 5 × 5 grid, row by row, skipping any letter that is already there.
- Fill the remaining cells with the rest of the alphabet, in order.
- 26 letters do not fit in 25 cells, so I and J share one cell.
With the key MONARCHY:
| col 0 | col 1 | col 2 | col 3 | col 4 | |
|---|---|---|---|---|---|
| row 0 | M | O | N | A | R |
| row 1 | C | H | Y | B | D |
| row 2 | E | F | G | I/J | K |
| row 3 | L | P | Q | S | T |
| row 4 | U | V | W | X | Z |
Step 2: split the message into pairs
- Write J as I and drop spaces and punctuation.
- Split into pairs from the left.
- If a pair would contain two equal letters, put an X between them. BALLOON becomes BA LX LO ON.
- If one letter is left over at the end, give it an X partner. (If the letter itself is X, use Q instead.)
Step 3: encrypt each pair
Find both letters in the square. Exactly one of three rules applies:
| Where the two letters are | Encrypt | Decrypt |
|---|---|---|
| Same row | Take the letter to the right of each (wrap to the start of the row) | Take the letter to the left |
| Same column | Take the letter below each (wrap to the top) | Take the letter above |
| Different row and column | They are corners of a rectangle: each letter is replaced by the letter in its own row and the other letter’s column | The same rule (it undoes itself) |
Worked example: INSTRUMENTS, key MONARCHY
The pairs are IN ST RU ME NT SX (11 letters, so the last S gets an X partner).
| Pair | Positions (row, column) | Rule | Result |
|---|---|---|---|
| IN | I (2, 3), N (0, 2) | Rectangle | GA |
| ST | S (3, 3), T (3, 4) | Same row | TL (T wraps around to L) |
| RU | R (0, 4), U (4, 0) | Rectangle | MZ |
| ME | M (0, 0), E (2, 0) | Same column | CL |
| NT | N (0, 2), T (3, 4) | Rectangle | RQ |
| SX | S (3, 3), X (4, 3) | Same column | XA (X wraps around to A) |
The ciphertext is GATLMZCLRQXA. Some textbooks pad with Z instead of X: then the last pair is SZ, a rectangle, which gives TX.
Decryption
The receiver builds the same square from the same key and reverses each move: left instead of right, up instead of down. The rectangle rule is its own inverse. Decrypting GATLMZCLRQXA gives INSTRUMENTSX, and the reader simply drops the filler X that makes no sense.
How strong is it?
Playfair was a real improvement over single-letter ciphers:
- There are 25! ≈ 1.6 × 10²⁵ possible squares, so trying every key is hopeless.
- A letter does not always encrypt to the same letter. In the example, the two Ts became L and Q.
But it is still breakable by hand with enough text:
- The same pair always encrypts to the same pair, so pair frequencies survive (TH, HE, IN, ER are the most common in English).
- A pair and its reverse give reversed results: if AB → XY, then BA → YX.
- A ciphertext never contains a doubled pair like EE, and a letter never encrypts to itself. Each of these facts narrows the search.
Code
def build_square(key: str) -> list[str]:
square = []
for ch in (key + "ABCDEFGHIKLMNOPQRSTUVWXYZ").upper().replace("J", "I"):
if ch.isalpha() and ch not in square:
square.append(ch)
return square # 25 letters, row by row
def pairs(text: str) -> list[str]:
s = [ch for ch in text.upper().replace("J", "I") if ch.isalpha()]
out, i = [], 0
while i < len(s):
a = s[i]
b = s[i + 1] if i + 1 < len(s) else None
if b is None or a == b: # doubled letter or odd end
out.append(a + ("Q" if a == "X" else "X"))
i += 1
else:
out.append(a + b)
i += 2
return out
def playfair(text: str, key: str, decrypt: bool = False) -> str:
sq = build_square(key)
pos = {ch: divmod(i, 5) for i, ch in enumerate(sq)}
d = -1 if decrypt else 1
todo = [text[i:i + 2] for i in range(0, len(text), 2)] if decrypt else pairs(text)
out = []
for a, b in todo:
(ra, ca), (rb, cb) = pos[a], pos[b]
if ra == rb: # same row: right (left to decrypt)
out += [sq[ra * 5 + (ca + d) % 5], sq[rb * 5 + (cb + d) % 5]]
elif ca == cb: # same column: down (up to decrypt)
out += [sq[(ra + d) % 5 * 5 + ca], sq[(rb + d) % 5 * 5 + cb]]
else: # rectangle: swap the columns
out += [sq[ra * 5 + cb], sq[rb * 5 + ca]]
return "".join(out)
c = playfair("instruments", "monarchy")
print(c) # GATLMZCLRQXA
print(playfair(c, "monarchy", decrypt=True)) # INSTRUMENTSX
#include <cctype>
#include <iostream>
#include <string>
using namespace std;
struct Playfair {
string sq; // 25 letters, row by row
int row[26] = {}, col[26] = {};
explicit Playfair(const string& key) {
for (char ch : key + "ABCDEFGHIKLMNOPQRSTUVWXYZ") {
ch = toupper(static_cast<unsigned char>(ch));
if (ch == 'J') ch = 'I';
if (isalpha(static_cast<unsigned char>(ch)) && sq.find(ch) == string::npos) sq += ch;
}
for (int i = 0; i < 25; i++) { row[sq[i] - 'A'] = i / 5; col[sq[i] - 'A'] = i % 5; }
}
char at(int r, int c) const { return sq[(r + 5) % 5 * 5 + (c + 5) % 5]; }
// d = 1 encrypts, d = -1 decrypts. The text must already be split into valid pairs.
string run(const string& text, int d) const {
string out;
for (size_t i = 0; i + 1 < text.size(); i += 2) {
int ra = row[text[i] - 'A'], ca = col[text[i] - 'A'];
int rb = row[text[i + 1] - 'A'], cb = col[text[i + 1] - 'A'];
if (ra == rb) { out += at(ra, ca + d); out += at(rb, cb + d); }
else if (ca == cb) { out += at(ra + d, ca); out += at(rb + d, cb); }
else { out += at(ra, cb); out += at(rb, ca); }
}
return out;
}
};
int main() {
Playfair pf("MONARCHY");
string c = pf.run("INSTRUMENTSX", 1); // pairs: IN ST RU ME NT SX
cout << c << '\n'; // GATLMZCLRQXA
cout << pf.run(c, -1) << '\n'; // INSTRUMENTSX
}
Common mistakes
- Not skipping repeated key letters. MONARCHY has only 8 letters and no repeats, but a key like BALLOON puts B, A, L, O, N in the square, not B, A, L, L, O, O, N.
- Forgetting the I/J cell. The square must have exactly 25 letters. If yours has 26, J got its own cell.
- Splitting first and inserting X later. Insert fillers while splitting from the left: BALLOON is BA LX LO ON, not BA LL OO N with fillers added afterwards.
- Mixing up the rectangle corners. Each letter keeps its own row and takes the other letter’s column. Swapping rows instead gives the wrong letters.
- Moving left to encrypt. Encrypt moves right and down; decrypt moves left and up.
Complexity at a glance
| Case / operation | Time | Why |
|---|---|---|
| Building the key square | O(1) | At most 26 letters to place. |
| Encrypting or decrypting n letters | O(n) | Keep a table of each letter's row and column. |
| Possible key squares | 25! ≈ 1.6 × 10²⁵ | Far too many to try one by one. |
| Different letter pairs | 25 × 24 = 600 | Pair-frequency analysis needs much more text than letter counting. |
Quick check
Test yourself — pick an answer to see if you got it.
1. With the key MONARCHY, what is the first row of the Playfair square?
The key letters go in first, row by row, skipping repeats. MONARCHY gives M, O, N, A, R in the first row, then C, H, Y continue in the second.
2. How is the plaintext BALLOON split into pairs?
The pair LL has two equal letters, so an X goes between them. The rest then pairs up as LO ON, and the length is already even.
3. Using the MONARCHY square, what does the pair AR encrypt to?
A and R are in the same row (row 0). Each moves one place to the right; R is at the end of the row, so it wraps around to M.
4. Why do I and J share a cell?
Two letters must share a cell. I and J are chosen because J is rare and easy to guess back from the context.
5. If the pair AB encrypts to XY with the rectangle rule, what does BA encrypt to?
The rectangle rule is symmetric. Reversing a pair reverses its result, which is one of the patterns cryptanalysts use against Playfair.