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

174 lines
5.2 KiB
Python

"""
ⒸAngelaMos | 2026
main.py
"""
from pathlib import Path
from typing import Annotated
import typer
from rich.console import Console
from rich.table import Table
from caesar_cipher.analyzer import FrequencyAnalyzer
from caesar_cipher.cipher import CaesarCipher
from caesar_cipher.utils import read_input, validate_key, write_output
app = typer.Typer(
name = "caesar-cipher",
help = "Caesar cipher encryption, decryption, and brute-force cracking tool",
no_args_is_help = True,
)
console = Console()
@app.command()
def encrypt(
text: Annotated[
str | None,
typer.Argument(help = "Text to encrypt (or use --input-file or stdin)"),
] = None,
key: Annotated[int,
typer.Option("--key",
"-k",
help = "Shift key (0-25)")] = 3,
input_file: Annotated[
Path | None,
typer.Option("--input-file",
"-i",
help = "Input file path")] = None,
output_file: Annotated[
Path | None,
typer.Option("--output-file",
"-o",
help = "Output file path")] = None,
quiet: Annotated[
bool,
typer.Option("--quiet",
"-q",
help = "Suppress output messages")] = False,
) -> None:
"""
Encrypt text using Caesar cipher with specified shift key
"""
try:
validate_key(key)
plaintext = read_input(text, input_file)
cipher = CaesarCipher(key = key)
encrypted = cipher.encrypt(plaintext)
if not quiet and not output_file:
console.print(f"[green]Encrypted:[/green] {encrypted}")
else:
write_output(encrypted, output_file, quiet)
except (ValueError, OSError) as e:
console.print(f"[red]Error:[/red] {e}")
raise typer.Exit(code = 1) from None
@app.command()
def decrypt(
text: Annotated[
str | None,
typer.Argument(help = "Text to decrypt (or use --input-file or stdin)"),
] = None,
key: Annotated[int,
typer.Option("--key",
"-k",
help = "Shift key (0-25)")] = 3,
input_file: Annotated[
Path | None,
typer.Option("--input-file",
"-i",
help = "Input file path")] = None,
output_file: Annotated[
Path | None,
typer.Option("--output-file",
"-o",
help = "Output file path")] = None,
quiet: Annotated[
bool,
typer.Option("--quiet",
"-q",
help = "Suppress output messages")] = False,
) -> None:
"""
Decrypt text using Caesar cipher with specified shift key
"""
try:
validate_key(key)
ciphertext = read_input(text, input_file)
cipher = CaesarCipher(key = key)
decrypted = cipher.decrypt(ciphertext)
if not quiet and not output_file:
console.print(f"[blue]Decrypted:[/blue] {decrypted}")
else:
write_output(decrypted, output_file, quiet)
except (ValueError, OSError) as e:
console.print(f"[red]Error:[/red] {e}")
raise typer.Exit(code = 1) from None
@app.command()
def crack(
text: Annotated[
str | None,
typer.Argument(help = "Text to crack (or use --input-file or stdin)"),
] = None,
input_file: Annotated[
Path | None,
typer.Option("--input-file",
"-i",
help = "Input file path")] = None,
top: Annotated[int,
typer.Option("--top",
"-t",
help = "Show top N candidates")] = 5,
show_all: Annotated[
bool,
typer.Option("--all",
"-a",
help = "Show all 26 possible shifts")] = False,
) -> None:
"""
Brute-force decrypt text by trying all shifts with frequency analysis ranking
"""
try:
ciphertext = read_input(text, input_file)
candidates = CaesarCipher.crack(ciphertext)
analyzer = FrequencyAnalyzer()
ranked = analyzer.rank_candidates(candidates)
table = Table(title = "Caesar Cipher Brute Force Results")
table.add_column("Rank", style = "cyan", justify = "right")
table.add_column("Shift", style = "magenta", justify = "right")
table.add_column("Score", style = "yellow", justify = "right")
table.add_column("Decrypted Text", style = "green")
display_count = len(ranked) if show_all else min(top, len(ranked))
for rank, (shift, text_result, score) in enumerate(ranked[:display_count], 1):
table.add_row(
str(rank),
str(shift),
f"{score:.2f}",
text_result[: 80]
)
console.print(table)
console.print(
f"\n[bold]Best match (Shift {ranked[0][0]}):[/bold] {ranked[0][1]}"
)
except (ValueError, OSError) as e:
console.print(f"[red]Error:[/red] {e}")
raise typer.Exit(code = 1) from None
if __name__ == "__main__":
app()