Cybersecurity-Projects/PROJECTS/beginner/caesar-cipher/src/caesar_cipher/cipher.py

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