79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
"""
|
|
©AngelaMos | 2026
|
|
cipher.py
|
|
|
|
Caesar cipher with encrypt, decrypt, and brute-force crack methods
|
|
|
|
Provides the CaesarCipher class that performs letter shifting for a given
|
|
key while preserving case, spaces, and punctuation. The static crack()
|
|
method generates all 26 possible decryptions without needing the key,
|
|
leaving ranking to the analyzer layer.
|
|
|
|
Connects to:
|
|
constants.py - imports UPPERCASE_LETTERS, LOWERCASE_LETTERS, ALPHABET_SIZE
|
|
main.py - all three CLI commands instantiate CaesarCipher
|
|
analyzer.py - crack() output is passed to FrequencyAnalyzer for ranking
|
|
"""
|
|
|
|
from caesar_cipher.constants import (
|
|
ALPHABET_SIZE,
|
|
LOWERCASE_LETTERS,
|
|
UPPERCASE_LETTERS,
|
|
)
|
|
|
|
|
|
class CaesarCipher:
|
|
"""
|
|
Caesar cipher implementation with configurable shift key and alphabet support
|
|
"""
|
|
def __init__(self, key: int, alphabet: str | None = None) -> None:
|
|
"""
|
|
Initialize Caesar cipher with shift key and optional custom alphabet
|
|
"""
|
|
if not -25 <= key <= 26:
|
|
msg = "Key must be between -25 and 26"
|
|
raise ValueError(msg)
|
|
|
|
self.key = key % ALPHABET_SIZE
|
|
self.alphabet = alphabet or (UPPERCASE_LETTERS + LOWERCASE_LETTERS)
|
|
|
|
if alphabet and len(set(alphabet)) != len(alphabet):
|
|
msg = "Alphabet must not contain duplicate characters"
|
|
raise ValueError(msg)
|
|
|
|
def _shift_char(self, char: str, shift: int) -> str:
|
|
"""
|
|
Shift a single character by the specified amount while preserving case
|
|
"""
|
|
if char in UPPERCASE_LETTERS:
|
|
idx = UPPERCASE_LETTERS.index(char)
|
|
return UPPERCASE_LETTERS[(idx + shift) % ALPHABET_SIZE]
|
|
if char in LOWERCASE_LETTERS:
|
|
idx = LOWERCASE_LETTERS.index(char)
|
|
return LOWERCASE_LETTERS[(idx + shift) % ALPHABET_SIZE]
|
|
return char
|
|
|
|
def encrypt(self, plaintext: str) -> str:
|
|
"""
|
|
Encrypt plaintext using the configured shift key
|
|
"""
|
|
return "".join(self._shift_char(char, self.key) for char in plaintext)
|
|
|
|
def decrypt(self, ciphertext: str) -> str:
|
|
"""
|
|
Decrypt ciphertext using the configured shift key
|
|
"""
|
|
return "".join(self._shift_char(char, -self.key) for char in ciphertext)
|
|
|
|
@staticmethod
|
|
def crack(ciphertext: str) -> list[tuple[int, str]]:
|
|
"""
|
|
Attempt all possible shifts to decrypt ciphertext without knowing the key
|
|
"""
|
|
results = []
|
|
for shift in range(ALPHABET_SIZE):
|
|
cipher = CaesarCipher(key = shift)
|
|
decrypted = cipher.decrypt(ciphertext)
|
|
results.append((shift, decrypted))
|
|
return results
|