Polybius Cipher Programming Guide & Examples
This comprehensive guide provides complete implementations of the Polybius Square cipher in multiple programming languages, along with historical examples and educational exercises to deepen your understanding of coordinate-based cryptography.
Algorithm Overview
The Polybius Square cipher converts letters into coordinate pairs based on their position in a grid. This coordinate-based approach made it ideal for long-distance communication using visual or auditory signals.
Core Concepts
Grid Structure: Letters are arranged in a square grid (typically 5×5 or 6×6) Coordinate Pairs: Each letter becomes (row, column) coordinates I/J Merger: In 5×5 grids, I and J traditionally share the same position Format Flexibility: Coordinates can be numeric (1-5) or alphabetic (A-E)
Complete Python Implementation
Basic Polybius Class
class PolybiusSquare:
"""
Complete Polybius Square cipher implementation
Supports both 5x5 and 6x6 grids with multiple coordinate formats
"""
def __init__(self, grid_size='5x5', custom_alphabet=None):
self.grid_size = grid_size
self.size = 5 if grid_size == '5x5' else 6
# Set alphabet based on grid size
if custom_alphabet:
self.alphabet = custom_alphabet.upper()
elif grid_size == '5x5':
# Classical: I and J merged
self.alphabet = 'ABCDEFGHIKLMNOPQRSTUVWXYZ'
else: # 6x6
# Extended: all letters plus digits
self.alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
# Create the grid
self.grid = self._create_grid()
# Create reverse lookup for encoding
self.char_to_pos = {}
for row in range(self.size):
for col in range(self.size):
if self.grid[row][col]:
self.char_to_pos[self.grid[row][col]] = (row, col)
def _create_grid(self):
"""Create the character grid"""
grid = []
for i in range(self.size):
row = []
for j in range(self.size):
index = i * self.size + j
if index < len(self.alphabet):
row.append(self.alphabet[index])
else:
row.append('')
grid.append(row)
return grid
def encode(self, text, coordinate_type='numeric', separator=' '):
"""
Encode text to coordinate pairs
Args:
text: Input text to encode
coordinate_type: 'numeric' (1-5) or 'letter' (A-E)
separator: Character to separate coordinate pairs
Returns:
Encoded coordinate string
"""
result = []
for char in text.upper():
# Skip non-alphabetic characters or handle specially
if not char.isalnum():
continue
# Handle J -> I conversion for 5x5 grid
if self.grid_size == '5x5' and char == 'J':
char = 'I'
# Get position
if char in self.char_to_pos:
row, col = self.char_to_pos[char]
if coordinate_type == 'numeric':
coord = f"{row + 1}{col + 1}"
else: # letter
row_letter = chr(65 + row) # A, B, C...
col_letter = chr(65 + col)
coord = f"{row_letter}{col_letter}"
result.append(coord)
return separator.join(result)
def decode(self, coordinates, coordinate_type='numeric'):
"""
Decode coordinate pairs to text
Args:
coordinates: Space-separated coordinate pairs
coordinate_type: 'numeric' or 'letter'
Returns:
Decoded text string
"""
# Handle different separators
import re
pairs = re.split(r'[\s,|\-]+', coordinates.strip())
result = []
for pair in pairs:
if len(pair) == 2:
try:
if coordinate_type == 'numeric':
row = int(pair[0]) - 1
col = int(pair[1]) - 1
else: # letter
row = ord(pair[0]) - 65 # A=0, B=1...
col = ord(pair[1]) - 65
# Validate coordinates
if (0 <= row < self.size and 0 <= col < self.size
and self.grid[row][col]):
result.append(self.grid[row][col])
else:
result.append('?') # Invalid coordinate
except (ValueError, IndexError):
result.append('?') # Parsing error
else:
result.append(pair) # Keep non-coordinate text
return ''.join(result)
def analyze_frequency(self, text):
"""
Analyze character frequency in text
Useful for educational cryptanalysis
"""
frequency = {}
total_chars = 0
for char in text.upper():
if char.isalpha():
frequency[char] = frequency.get(char, 0) + 1
total_chars += 1
# Convert to percentages and sort
freq_analysis = []
for char, count in sorted(frequency.items(),
key=lambda x: x[1], reverse=True):
percentage = (count / total_chars) * 100
coord = self.encode(char)
freq_analysis.append({
'char': char,
'count': count,
'percentage': round(percentage, 2),
'coordinate': coord
})
return freq_analysis
def display_grid(self):
"""Display the grid in a readable format"""
print(f"\nPolybius Square ({self.grid_size}):")
print(" ", end="")
# Column headers
for i in range(self.size):
print(f" {chr(65 + i)}", end="")
print()
# Grid rows
for i, row in enumerate(self.grid):
print(f" {chr(65 + i)} ", end="")
for cell in row:
if cell:
print(f" {cell}", end="")
else:
print(" ", end="")
print()
# Usage Examples
def main():
# Create 5x5 cipher
cipher = PolybiusSquare('5x5')
print("=== Polybius Square Cipher Examples ===")
# Basic encoding
message = "HELLO WORLD"
encoded = cipher.encode(message)
print(f"\nOriginal: {message}")
print(f"Encoded: {encoded}")
# Decoding
decoded = cipher.decode(encoded)
print(f"Decoded: {decoded}")
# Different coordinate formats
letter_coords = cipher.encode(message, 'letter')
print(f"\nLetter coordinates: {letter_coords}")
print(f"Decoded from letters: {cipher.decode(letter_coords, 'letter')}")
# Concatenated format (no separators)
concat_coords = cipher.encode(message, separator='')
print(f"\nConcatenated: {concat_coords}")
# Frequency analysis
print(f"\n=== Frequency Analysis for '{message}' ===")
freq_analysis = cipher.analyze_frequency(message)
for item in freq_analysis:
print(f"{item['char']}: {item['count']} times "
f"({item['percentage']}%) - coordinate {item['coordinate']}")
# Display grid
cipher.display_grid()
# 6x6 example with digits
print(f"\n=== 6x6 Grid Example ===")
cipher_6x6 = PolybiusSquare('6x6')
mixed_message = "CODE123"
encoded_6x6 = cipher_6x6.encode(mixed_message)
print(f"Message: {mixed_message}")
print(f"Encoded: {encoded_6x6}")
print(f"Decoded: {cipher_6x6.decode(encoded_6x6)}")
if __name__ == "__main__":
main()
Advanced JavaScript Implementation
/**
* Modern JavaScript Polybius Square Implementation
* ES6+ with advanced features and browser compatibility
*/
class PolybiusSquare {
constructor(gridSize = '5x5', customAlphabet = null) {
this.gridSize = gridSize;
this.size = gridSize === '5x5' ? 5 : 6;
// Set alphabet
if (customAlphabet) {
this.alphabet = customAlphabet.toUpperCase();
} else if (gridSize === '5x5') {
this.alphabet = 'ABCDEFGHIKLMNOPQRSTUVWXYZ'; // I/J merged
} else {
this.alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
}
this.grid = this.createGrid();
this.charToPos = this.createLookupMap();
}
createGrid() {
const grid = [];
for (let i = 0; i < this.size; i++) {
const row = [];
for (let j = 0; j < this.size; j++) {
const index = i * this.size + j;
row.push(this.alphabet[index] || '');
}
grid.push(row);
}
return grid;
}
createLookupMap() {
const map = new Map();
for (let row = 0; row < this.size; row++) {
for (let col = 0; col < this.size; col++) {
if (this.grid[row][col]) {
map.set(this.grid[row][col], { row, col });
}
}
}
return map;
}
encode(text, coordinateType = 'numeric', separator = ' ') {
return text.toUpperCase()
.split('')
.filter(char => char.match(/[A-Z0-9]/))
.map(char => {
// Handle J -> I conversion for 5x5
if (this.gridSize === '5x5' && char === 'J') {
char = 'I';
}
const pos = this.charToPos.get(char);
if (pos) {
if (coordinateType === 'numeric') {
return `${pos.row + 1}${pos.col + 1}`;
} else {
const rowLetter = String.fromCharCode(65 + pos.row);
const colLetter = String.fromCharCode(65 + pos.col);
return `${rowLetter}${colLetter}`;
}
}
return ''; // Skip unknown characters
})
.filter(coord => coord.length > 0)
.join(separator);
}
decode(coordinates, coordinateType = 'numeric') {
// Handle various separators
const pairs = coordinates.split(/[\s,|\-]+/).filter(pair => pair.length > 0);
return pairs
.map(pair => {
if (pair.length === 2) {
let row, col;
if (coordinateType === 'numeric') {
row = parseInt(pair[0]) - 1;
col = parseInt(pair[1]) - 1;
} else {
row = pair.charCodeAt(0) - 65;
col = pair.charCodeAt(1) - 65;
}
if (row >= 0 && row < this.size &&
col >= 0 && col < this.size &&
this.grid[row][col]) {
return this.grid[row][col];
}
return '?'; // Invalid coordinate
}
return pair; // Keep non-coordinate text
})
.join('');
}
analyzeFrequency(text) {
const frequency = new Map();
const totalChars = text.replace(/[^A-Z]/g, '').length;
for (const char of text.toUpperCase()) {
if (char.match(/[A-Z]/)) {
frequency.set(char, (frequency.get(char) || 0) + 1);
}
}
return Array.from(frequency.entries())
.map(([char, count]) => ({
char,
count,
percentage: Math.round((count / totalChars) * 100 * 100) / 100,
coordinate: this.encode(char)
}))
.sort((a, b) => b.count - a.count);
}
displayGrid() {
console.log(`\nPolybius Square (${this.gridSize}):`);
console.log(' ' + Array.from({length: this.size}, (_, i) =>
String.fromCharCode(65 + i)).join(' '));
this.grid.forEach((row, i) => {
const rowStr = String.fromCharCode(65 + i) + ' | ' +
row.map(cell => cell || ' ').join(' ');
console.log(rowStr);
});
}
// Utility methods for web applications
toJSON() {
return {
gridSize: this.gridSize,
alphabet: this.alphabet,
grid: this.grid
};
}
exportToCSV(analysis) {
const headers = ['Character', 'Count', 'Percentage', 'Coordinate'];
const rows = analysis.map(item =>
[item.char, item.count, item.percentage, item.coordinate]
);
return [headers, ...rows]
.map(row => row.join(','))
.join('\n');
}
}
// Browser-compatible usage examples
function demonstratePolybius() {
console.log('=== Polybius Square JavaScript Demo ===');
const cipher = new PolybiusSquare('5x5');
// Basic operations
const message = 'CRYPTOGRAPHY';
const encoded = cipher.encode(message);
const decoded = cipher.decode(encoded);
console.log(`Original: ${message}`);
console.log(`Encoded: ${encoded}`);
console.log(`Decoded: ${decoded}`);
// Frequency analysis
const frequency = cipher.analyzeFrequency(message);
console.log('\nFrequency Analysis:');
frequency.forEach(item => {
console.log(`${item.char}: ${item.count} (${item.percentage}%) → ${item.coordinate}`);
});
cipher.displayGrid();
}
// Web API integration example
class PolybiusWebAPI {
constructor() {
this.cipher = new PolybiusSquare();
}
async processText(text, options = {}) {
return new Promise((resolve) => {
setTimeout(() => {
const result = {
original: text,
encoded: this.cipher.encode(text, options.coordinateType),
frequency: this.cipher.analyzeFrequency(text),
timestamp: new Date().toISOString()
};
resolve(result);
}, 100); // Simulate async processing
});
}
batchProcess(textArray) {
return Promise.all(
textArray.map(text => this.processText(text))
);
}
}
// Export for module systems
if (typeof module !== 'undefined' && module.exports) {
module.exports = { PolybiusSquare, PolybiusWebAPI };
}
Historical Examples & Applications
Ancient Greek Signal Communications
The original Polybius system used torch signals for long-distance communication:
def ancient_torch_signals():
"""
Simulate ancient Greek torch signaling
Each coordinate transmitted as groups of torches
"""
message = "ENEMY APPROACHING"
cipher = PolybiusSquare('5x5')
encoded = cipher.encode(message)
print("Ancient Torch Signal Protocol:")
print(f"Message: {message}")
print(f"Coordinates: {encoded}")
print("\nTorch Signal Instructions:")
for i, coord in enumerate(encoded.split()):
if coord.isdigit() and len(coord) == 2:
row, col = int(coord[0]), int(coord[1])
print(f"Letter {i+1}: {row} torches, pause, {col} torches")
Telegraph Era Implementation
During the telegraph era, Polybius coordinates were ideal for transmission:
def telegraph_encoding():
"""
Telegraph-optimized Polybius encoding
Concatenated format with error detection
"""
def add_checksum(encoded_text):
"""Add simple checksum for error detection"""
checksum = sum(int(digit) for digit in encoded_text if digit.isdigit()) % 100
return f"{encoded_text}{checksum:02d}"
def verify_checksum(received_text):
"""Verify telegram integrity"""
if len(received_text) < 2:
return False, "Too short"
message = received_text[:-2]
expected_checksum = int(received_text[-2:])
actual_checksum = sum(int(d) for d in message if d.isdigit()) % 100
return actual_checksum == expected_checksum, message
cipher = PolybiusSquare('5x5')
message = "URGENT SUPPLIES NEEDED"
# Encode without separators (telegraph style)
encoded = cipher.encode(message, separator='')
secured = add_checksum(encoded)
print("Telegraph Transmission:")
print(f"Original: {message}")
print(f"Encoded: {encoded}")
print(f"With checksum: {secured}")
# Verify and decode
is_valid, clean_message = verify_checksum(secured)
if is_valid:
decoded = cipher.decode(' '.join([clean_message[i:i+2]
for i in range(0, len(clean_message), 2)]))
print(f"Decoded: {decoded}")
else:
print("Checksum failed - transmission error detected")
Prison Tap Code Conversion
Converting Polybius to the famous prison tap code:
def prison_tap_code():
"""
Convert Polybius coordinates to tap code sequences
Used by POWs for wall communication
"""
def text_to_taps(text):
cipher = PolybiusSquare('5x5')
encoded = cipher.encode(text)
tap_sequence = []
for coord in encoded.split():
if len(coord) == 2:
row_taps = int(coord[0])
col_taps = int(coord[1])
# Create tap pattern
row_pattern = '•' * row_taps
col_pattern = '•' * col_taps
tap_sequence.append(f"{row_pattern} | {col_pattern}")
return ' '.join(tap_sequence)
# Historical POW messages
messages = [
"GOD BLESS AMERICA",
"STAY STRONG",
"REMEMBER YOUR TRAINING"
]
print("Prison Tap Code Examples:")
for msg in messages:
taps = text_to_taps(msg)
print(f"\nMessage: {msg}")
print(f"Tap code: {taps}")
Educational Exercises
Exercise 1: Frequency Analysis
Analyze the vulnerability of Polybius ciphers to frequency attacks:
def frequency_vulnerability_demo():
"""
Demonstrate frequency analysis vulnerability
"""
cipher = PolybiusSquare('5x5')
# English text with natural letter frequencies
sample_text = """
THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
THIS IS A SAMPLE TEXT FOR FREQUENCY ANALYSIS
NOTICE HOW CERTAIN LETTERS APPEAR MORE OFTEN
"""
# Encode and analyze
encoded = cipher.encode(sample_text)
frequency = cipher.analyze_frequency(sample_text)
print("Frequency Vulnerability Analysis:")
print(f"Sample text length: {len(sample_text.replace(' ', '').replace('\n', ''))} characters")
print("\nTop 5 most frequent characters:")
for item in frequency[:5]:
print(f"{item['char']}: {item['count']} times ({item['percentage']}%) "
f"→ always encoded as {item['coordinate']}")
print(f"\nEncoded coordinates: {encoded}")
print("\nSecurity note: The coordinate '44' appears frequently,")
print("indicating the letter 'T' is common in English text.")
Exercise 2: Grid Variations
Explore different grid configurations and their implications:
def compare_grid_sizes():
"""
Compare 5x5 vs 6x6 grid characteristics
"""
message = "HELLO123"
# 5x5 grid
cipher_5x5 = PolybiusSquare('5x5')
try:
encoded_5x5 = cipher_5x5.encode(message)
print(f"5x5 encoding: {encoded_5x5}")
except:
print("5x5 grid cannot encode digits")
# 6x6 grid
cipher_6x6 = PolybiusSquare('6x6')
encoded_6x6 = cipher_6x6.encode(message)
print(f"6x6 encoding: {encoded_6x6}")
print("\nGrid Comparison:")
print("5x5: Classical, I/J merged, letters only")
print("6x6: Modern, all letters + digits, longer coordinates")
Exercise 3: Custom Alphabets
Experiment with custom character arrangements:
def custom_alphabet_cipher():
"""
Create Polybius cipher with custom alphabet arrangement
"""
# Keyword-based alphabet (remove duplicates, add remaining letters)
keyword = "CRYPTOGRAPHY"
remaining = "BEFHJKLMNQSUVWXZ" # Letters not in keyword
custom_alphabet = keyword + remaining
print(f"Custom alphabet: {custom_alphabet}")
cipher = PolybiusSquare('5x5', custom_alphabet)
cipher.display_grid()
message = "SECRET MESSAGE"
encoded = cipher.encode(message)
print(f"\nMessage: {message}")
print(f"Encoded with custom alphabet: {encoded}")
This comprehensive guide provides the foundation for understanding and implementing Polybius Square ciphers in modern programming environments while maintaining historical accuracy and educational value.