Skip to main content

classical-cipher-analysis

Classical cipher analysis playbook. Use when encountering substitution ciphers, Vigenere, transposition, XOR, or encoded text in CTF challenges that requires frequency analysis, Kasiski examination, or known-plaintext cryptanalysis.

Jump to install

Source facts

Repository
Kur1sulab/whitebox
Last source activity
August 12, 2026 at 15:17
Detected SKILL.md language
English
Stars
1
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
classical-cipher-analysis
description
Classical cipher analysis playbook. Use when encountering substitution ciphers, Vigenere, transposition, XOR, or encoded text in CTF challenges that requires frequency analysis, Kasiski examination, or known-plaintext cryptanalysis.
# SKILL: Classical Cipher Analysis — Expert Cryptanalysis Playbook > **AI LOAD INSTRUCTION**: Expert classical cipher identification and breaking techniques for CTF. Covers cipher identification methodology (frequency analysis, IC, Kasiski), monoalphabetic substitution, Caesar/ROT, Vigenere, Enigma, affine, Hill, transposition ciphers, Bacon/Polybius/Playfair, and XOR ciphers. Base models often skip the identification step and jump to the wrong cipher type, or fail to recognize encoded (base64/hex) ciphertext that needs decoding before analysis. ## 0. RELATED ROUTING - [symmetric-cipher-attacks](../hack-symmetric-cipher-attacks/SKILL.md) when dealing with modern symmetric ciphers (AES/DES) rather than classical - [hash-attack-techniques](https://github.com/xiaoyang-xyc/blackbox/blob/main/hack-hash-attack-techniques/SKILL.md) when the challenge involves hash-based constructions - [lattice-crypto-attacks](../hack-lattice-crypto-attacks/SKILL.md) when knapsack-based ciphers are encountered ### Quick identification guide | Observation | Likely Cipher | First Action | |---|---|---| | All uppercase letters, uneven frequency | Monoalphabetic substitution | Frequency analysis | | All uppercase, flat frequency distribution | Polyalphabetic (Vigenere) | IC + Kasiski | | Only A-Z shifted uniformly | Caesar/ROT | Brute force 25 shifts | | Base64 alphabet (A-Za-z0-9+/=) | Base64 encoded (decode first) | Base64 decode | | Hex string (0-9a-f) | Hex encoded (decode first) | Hex decode | | Binary (0s and 1s) | Binary encoded | Convert to ASCII | | Dots and dashes | Morse code | Morse decode | | Raised/normal text pattern | Bacon cipher | Map to A/B, decode | | 2-digit number pairs (11-55) | Polybius square | Grid lookup | | Text appears scrambled (right letters, wrong order) | Transposition | Anagram analysis | | Non-printable bytes XOR-like | XOR cipher | Single/repeating key XOR analysis | --- ## 1. CIPHER IDENTIFICATION METHODOLOGY ### 1.1 Step 1: Character Set Analysis ```python def analyze_charset(ciphertext): """Identify encoding/cipher by character set.""" chars = set(ciphertext.strip()) if chars <= set('01 \n'): return "Binary encoding" if chars <= set('.-/ \n'): return "Morse code" if chars <= set('0123456789abcdef \n'): return "Hex encoding" if chars <= set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n'): if '=' in ciphertext or len(ciphertext) % 4 == 0: return "Base64 encoding" if chars <= set('ABCDEFGHIJKLMNOPQRSTUVWXYZ \n'): return "Uppercase only — classical cipher" if all(c in '12345' for c in ciphertext.replace(' ', '').replace('\n', '')): return "Polybius square (digits 1-5)" return "Mixed charset — needs further analysis" ``` ### 1.2 Step 2: Frequency Analysis ```python from collections import Counter def frequency_analysis(text): """Compute letter frequency distribution.""" text = text.upper() letters = [c for c in text if c.isalpha()] total = len(letters) freq = Counter(letters) print("Letter frequencies:") for letter, count in freq.most_common(): pct = count / total * 100 bar = '#' * int(pct) print(f" {letter}: {pct:5.1f}% {bar}") return freq # English letter frequency (for comparison): # E T A O I N S H R D L C U M W F G Y P B V K J X Q Z # 12.7 9.1 8.2 7.5 7.0 6.7 6.3 6.1 6.0 4.3 4.0 2.8 ... ``` ### 1.3 Step 3: Index of Coincidence (IC) ```python def index_of_coincidence(text): """ IC ≈ 0.065 → English / monoalphabetic substitution IC ≈ 0.038 → random / polyalphabetic cipher """ text = [c for c in text.upper() if c.isalpha()] N = len(text) freq = Counter(text) ic = sum(f * (f - 1) for f in freq.values()) / (N * (N - 1)) return ic # Interpretation: # IC > 0.060 → monoalphabetic (Caesar, simple substitution, Playfair) # IC ≈ 0.045-0.055 → polyalphabetic with short key (Vigenere key < 10) # IC ≈ 0.038-0.042 → polyalphabetic with long key or random ``` ### 1.4 Step 4: Kasiski Examination (for Polyalphabetic) ```python from math import gcd from functools import reduce def kasiski(ciphertext, min_len=3): """Find repeated sequences and their distances → key length.""" text = ''.join(c for c in ciphertext.upper() if c.isalpha()) distances = [] for length in range(min_len, min(20, len(text) // 3)): for i in range(len(text) - length): seq = text[i:i+length] j = text.find(seq, i + 1) while j != -1: distances.append(j - i) j = text.find(seq, j + 1) if not distances: return None # Key length is likely GCD of common distances common_gcds = Counter() for d in distances: for factor in range(2, min(d + 1, 30)): if d % factor == 0: common_gcds[factor] += 1 print("Likely key lengths (by frequency):") for length, count in common_gcds.most_common(5): print(f" Key length {length}: {count} occurrences") return common_gcds.most_common(1)[0][0] ``` --- ## 2. MONOALPHABETIC SUBSTITUTION ### 2.1 Frequency Analysis Attack ```python def solve_substitution(ciphertext, interactive=False): """Solve monoalphabetic substitution via frequency analysis.""" freq = frequency_analysis(ciphertext) # English frequency order eng_order = "ETAOINSRHLDCUMWFGYPBVKJXQZ" cipher_order = ''.join(c for c, _ in freq.most_common()) # Initial mapping (frequency-based guess) mapping = {} for i, c in enumerate(cipher_order): if i < len(eng_order): mapping[c] = eng_order[i] # Apply mapping result = "" for c in ciphertext.upper(): result += mapping.get(c, c) return result, mapping # Better approach: use automated solvers # quipqiup.com — online substitution solver # dcode.fr/monoalphabetic-substitution — with word pattern matching ``` ### 2.2 Known Plaintext (Crib Dragging) If part of the plaintext is known (e.g., "flag{" prefix): ```python def crib_drag_substitution(ciphertext, known_plain, known_cipher): """Build partial mapping from known plaintext-ciphertext pair.""" mapping = {} for p, c in zip(known_plain.upper(), known_cipher.upper()): mapping[c] = p # Apply partial mapping result = "" for c in ciphertext.upper(): result += mapping.get(c, '?') return result, mapping ``` --- ## 3. CAESAR / ROT CIPHERS ### 3.1 Brute Force ```python def caesar_bruteforce(ciphertext): """Try all 25 shifts, score by English frequency.""" results = [] for shift in range(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 # Chi-squared scoring against English frequency score = chi_squared_score(decrypted) results.append((shift, score, decrypted)) results.sort(key=lambda x: x[1]) return results[0] # best match def chi_squared_score(text): """Lower score = closer to English.""" expected = { '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, } text = text.upper() letters = [c for c in text if c.isalpha()] total = len(letters) if total == 0: return float('inf') freq = Counter(letters) score = sum( (freq.get(c, 0) / total * 100 - expected.get(c, 0)) ** 2 / max(expected.get(c, 0.1), 0.1) for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ) return score ``` ### 3.2 ROT13 and ROT47 ```python import codecs # ROT13 (letters only) rot13 = codecs.decode(ciphertext, 'rot_13') # ROT47 (ASCII 33-126) def rot47(text): return ''.join( chr(33 + (ord(c) - 33 + 47) % 94) if 33 <= ord(c) <= 126 else c for c in text ) ``` --- ## 4. VIGENERE CIPHER ### 4.1 Full Attack Workflow ``` Step 1: Confirm polyalphabetic (IC ≈ 0.04-0.05) Step 2: Find key length (Kasiski + IC per period) Step 3: For each key position, solve as single Caesar cipher Step 4: Assemble key → decrypt ``` ### 4.2 IC-Based Key Length Detection ```python def find_vigenere_key_length(ciphertext, max_key=20): """Use IC to find Vigenere key length.""" text = [c for c in ciphertext.upper() if c.isalpha()] results = [] for kl in range(1, max_key + 1): # Split text into kl columns columns = [[] for _ in range(kl)] for i, c in enumerate(text): columns[i % kl].append(c) # Average IC across columns avg_ic = sum( index_of_coincidence(''.join(col)) for col in columns ) / kl results.append((kl, avg_ic)) print(f" Key length {kl:2d}: IC = {avg_ic:.4f}") # Key length with IC closest to 0.065 best = max(results, key=lambda x: x[1]) return best[0] ``` ### 4.3 Per-Position Frequency Attack ```python def crack_vigenere(ciphertext, key_length): """Crack Vigenere given known key length.""" text = [c for c in ciphertext.upper() if c.isalpha()] key = "" for pos in range(key_length): column = ''.join(text[i] for i in range(pos, len(text), key_length)) # Solve as Caesar cipher shift, score, _ = caesar_bruteforce(column) key += chr(shift + ord('A')) # Decrypt plaintext = "" ki = 0 for c in ciphertext: if c.isalpha(): shift = ord(key[ki % key_length]) - ord('A') base = ord('A') if c.isupper() else ord('a') plaintext += chr((ord(c) - base - shift) % 26 + base) ki += 1 else: plaintext += c return key, plaintext ``` --- ## 5. AFFINE CIPHER ### 5.1 Definition `E(x) = (a·x + b) mod 26` where gcd(a, 26) = 1. Valid a values: 1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25 (12 values). ### 5.2 Brute Force (312 combinations) ```python def crack_affine(ciphertext): """Brute force affine cipher: 12 × 26 = 312 combinations.""" valid_a = [a for a in range(1, 26) if gcd(a, 26) == 1] for a in valid_a: a_inv = pow(a, -1, 26) for b in range(26): plaintext = "" for c in ciphertext.upper(): if c.isalpha(): y = ord(c) - ord('A') x = (a_inv * (y - b)) % 26 plaintext += chr(x + ord('A')) else: plaintext += c score = chi_squared_score(plaintext) if score < 50: # reasonable English print(f"a={a}, b={b}: {plaintext[:50]}...") ``` ### 5.3 Known Plaintext ```python def affine_from_known(plain1, cipher1, plain2, cipher2): """Recover (a, b) from two known plaintext-ciphertext pairs.""" p1, c1 = ord(plain1) - ord('A'), ord(cipher1) - ord('A') p2, c2 = ord(plain2) - ord('A'), ord(cipher2) - ord('A')
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub