Athena Wiki

Frequency Analysis

cryptobeginner

Break monoalphabetic substitution ciphers using letter frequency statistics.

cryptoclassicalfrequency-analysissubstitutioncaesarmonoalphabeticic

Frequency Analysis

Frequency analysis counts how often each letter appears in a ciphertext and compares those counts to the expected frequency of letters in natural language. It's the primary attack against monoalphabetic substitution ciphers and the first mathematical tool in cryptanalysis history.


Why It Works

In English, letter frequencies are remarkably stable across large texts:

E: 12.7%    T:  9.1%    A:  8.2%    O:  7.5%    I:  7.0%
N:  6.7%    S:  6.3%    H:  6.1%    R:  6.0%    D:  4.3%
L:  4.0%    C:  2.8%    U:  2.8%    M:  2.4%    W:  2.4%
F:  2.2%    G:  2.0%    Y:  2.0%    P:  1.9%    B:  1.5%
V:  1.0%    K:  0.8%    J:  0.2%    X:  0.2%    Q:  0.1%    Z:  0.1%

Key insight: In a monoalphabetic substitution, each letter maps to exactly one other letter. The most frequent letter in the ciphertext likely represents E, the second most frequent likely represents T, and so on.


Index of Coincidence (IC)

The IC measures whether a text has letter-frequency patterns consistent with natural language. It's used to identify the cipher type before breaking it.


Step-by-Step Attack

Count Frequencies

from collections import Counter

ciphertext = "KHOOR ZRUOG WKLV LV D WHVW"
clean = ''.join(c for c in ciphertext.upper() if c.isalpha())
freq = Counter(clean)
total = len(clean)

print(f"{'Letter':8} {'Count':6} {'Freq%':8}")
print("-" * 24)
for letter, count in sorted(freq.items(), key=lambda x: -x[1]):
    print(f"  {letter}      {count:4d}    {count/total*100:5.1f}%")

Compare to English

english_freq_order = "ETAOINSHRDLCUMWFGYPBVKJXQZ"
cipher_freq_order  = ''.join(l for l, _ in sorted(freq.items(), key=lambda x: -x[1]))

print(f"English:    {english_freq_order}")
print(f"Ciphertext: {cipher_freq_order}")
# Most frequent in cipher → likely 'E'
# Second most frequent → likely 'T'

Build and Refine Mapping

# Start with frequency-based guesses
mapping = {}
for cipher_char, eng_char in zip(cipher_freq_order, english_freq_order):
    mapping[cipher_char] = eng_char

# Apply mapping
def apply_mapping(text, mapping):
    return ''.join(mapping.get(c, '_') if c.isalpha() else c for c in text.upper())

decrypted = apply_mapping(ciphertext, mapping)
print(decrypted)

# Refine: look at common words, digrams, trigrams
# Update mapping interactively until it makes sense
mapping['K'] = 'H'   # manual refinement
mapping['O'] = 'E'
mapping['R'] = 'L'

Use Pattern Words

Single-letter words: A or I
Two-letter words: IT, IN, IS, AN, OR, TO, OF, AT, BE, WE, HE
Three-letter words: THE (most common!), AND, FOR, ARE, BUT, NOT
Common digrams: TH, HE, IN, ER, AN, RE, ON, EN
Common trigrams: THE, AND, ING, ION, ENT, FOR
# Find candidate single-letter words
words = ciphertext.split()
single = [w for w in words if len(w) == 1]
print("Single-letter words:", set(single))
# If "D" appears as a single letter → D probably means "a" or "i"

Automated Tools

For CTF, these are the fastest approaches:

quipqiup.com (Best Automated Solver)

Paste ciphertext

Paste your ciphertext into the text box.

Click Solve

Click "Solve" - this tries all possible substitution mappings.

Read Output

Usually gives readable English in seconds for long enough ciphertext.

dcode.fr

Paste ciphertext

Paste your ciphertext.

Analyze Chart

See the visual frequency chart and explore the auto-solve options.

CyberChef

Add Operation

Search for and add the Frequency Distribution operation.

View Results

This will generate and show letter frequencies.


Python Full Implementation


Caesar Cipher (Special Case of Substitution)

Caesar is monoalphabetic with a fixed shift - only 25 possible keys:

def caesar_brute_force(ciphertext):
    for shift in range(1, 26):
        decrypted = ''
        for c in ciphertext:
            if c.isalpha():
                base = ord('A') if c.isupper() else ord('a')
                decrypted += chr((ord(c) - base - shift) % 26 + base)
            else:
                decrypted += c
        print(f"Shift {shift:2d}: {decrypted}")

caesar_brute_force("KHOOR ZRUOG")
# Shift  3: HELLO WORLD  ← readable!

Quick Cipher Identification Reference

ObservationLikely cipher
All letters shifted by the same amountCaesar (26 shifts)
Letters scrambled, same frequency profileMonoalphabetic substitution
IC0.067\mathrm{IC} \approx 0.067Monoalphabetic (frequency analysis works)
IC0.045\mathrm{IC} \approx 0.0450.0550.055Vigenère (Kasiski / Friedman)
IC0.038\mathrm{IC} \approx 0.038One-time pad or strong modern cipher
Symbols instead of lettersPigpen, Morse, etc.
Numbers as A–Z mappingA=1, B=2 style encoding
Uppercase only, spaces strippedCommon CTF setup

Checklist

  • Calculate IC\mathrm{IC}: 0.067\approx 0.067 → frequency analysis; 0.045\approx 0.045 → Vigenère
  • Count frequencies: sort by count, compare to E,T,A,O,I...
  • Try quipqiup.com first - solves most English text automatically
  • dcode.fr/monoalphabetic-substitution for manual + automated
  • Identify single-letter words (A or I), short words (THE, AND)
  • Use digrams/trigrams to resolve ambiguous mappings
  • For Caesar specifically: brute force all 25 shifts
  • ROT13? echo "text" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
  • Polish until plaintext is fully readable English

Practice Challenges


Last updated on

On this page