Skip to main content

Vigenere cipher solver: FFT periodogram + Kasiski + twist++ + beam search + interpolated n-gram LM, with learned posterior weights and an adaptive 'best' ensemble.

Project description

vigenere

PyPI Python 3.10+ License: MIT

A production-grade Vigenere cipher solver that combines classical cryptanalysis (Kasiski, index of coincidence, FFT coincidence periodogram, twist / twist++) with modern decoding (per-strip beam search guided by an interpolated character n-gram language model) and a learned softmax posterior over key lengths. Pure-Python core, numpy for the FFT, optional rich for live progress.

On the easy regime (random English plaintext, key length 4–12, 400+ characters) the best decoder hits 100 % key recovery on 30 random samples and the learned key-length posterior reaches 96.7 % top-1 / 100 % top-3 out of 29 candidates.

Install

# from PyPI (recommended)
pip install vigenere-solver-ng
# or
uv pip install vigenere-solver-ng
uv tool install vigenere-solver-ng

# as a globally available CLI via git clone locally
git clone https://github.com/supasuge/VigenereSolver-ng
cd VigenereSolver-ng
uv tool install . -e

Requires Python ≥ 3.10. Runtime deps: numpy, rich.

For a dev install (editable, with tests), see docs/BUILDING.md.

Quickstart — library

from vigenere import encrypt, decrypt, solve

ct = encrypt("the quick brown fox jumps over the lazy dog ...", "LEMON")

result = solve(ct, decoder="best")
print(result.key)               # "LEMON"
print(result.plaintext)
print(result.keylen_posterior)  # [(5, 0.41), (10, 0.18), ...]
print(result.confidence)        # 0.92 — strong unique winner
print(result.match_against("LEMON"))  # MatchResult(kind="exact", ...)
print(result.pretty())          # formatted summary

Force a known key length to skip estimation:

solve(ct, decoder="classic", forced_keylens=[7])

Custom n-gram order or weights:

solve(ct, decoder="classic", scorer_kwargs={
    "order": 5,
    "lambdas": [0.02, 0.08, 0.20, 0.30, 0.40],
    "alpha": 1e-4,
})

For a full end-to-end CTF-style walkthrough (Base64-wrapped ciphertext → clean → solve → round-trip verification) see src/core/cipher.py — runnable with python -m core.cipher.

Quickstart — CLI

# Encrypt
echo "the quick brown fox" | vigenere encrypt --key LEMON

# Solve (adaptive auto-mode: fast preset first, escalates only if confidence
# is below threshold).
vigenere solve --in ciphertext.txt
vigenere solve --in ciphertext.txt --no-progress --json
vigenere solve --in ciphertext.txt --decoder classic --keylens 7

# Inspect raw key-length signals
vigenere explain --in ciphertext.txt --max-k 40

Full subcommand reference (including bench, compare, tune, optimize, gen-corpus) lives in docs/CLI.md.

Public API at a glance

from vigenere import (
    encrypt, decrypt,
    clean_letters, random_key,
    solve, solve_auto,
    SolveResult,
)
symbol what it does
encrypt Vigenere encryption; preserves non-letters, normalizes case
decrypt inverse
clean_letters strip non-A–Z, upper-case (use this on raw challenge input)
random_key uniform random A–Z key (deterministic with a seeded Random)
solve full attack pipeline → SolveResult
solve_auto fast preset, escalates to best ensemble only if low confidence
SolveResult dataclass: key, plaintext, keylen_posterior, candidates, confidence, match_against, …

Detailed signatures and every internal module: docs/API.md.

How it works (one-paragraph summary)

For each candidate key length $k$ the solver computes four signals — average per-strip IoC, Kasiski divisor votes, FFT coincidence periodogram with harmonic suppression, and twist++ (Barr & Simoes 2015) — z-scores them, combines them with learned weights, and softmaxes to a posterior $P(m=k \mid \text{ct})$. The top key lengths feed a per-strip Caesar beam search guided by Jensen–Shannon divergence against the English unigram prior; the top keys per beam are re-ranked under an interpolated 1..5-gram character LM. The best decoder runs every scorer, pools their candidates, and re-ranks the union under the n-gram LM — strictly $\geq$ any single decoder on accuracy. Full derivations: docs/THEORY.md.

Measured impact

compare harness, 30 trials, key length 4–12, ≥500 chars:

decoder key_acc exact_acc char_acc mean_sec
best 1.000 1.000 1.000 0.12
classic 1.000 0.733 1.000 0.08
tiny-lm 0.967 0.700 0.999 0.05
legacy 0.93 0.000 0.93 0.04

On harder regimes (very long keys against short ciphertexts — each strip has <20 letters) accuracy degrades because the information isn't there; that's a property of the cipher problem, not the solver.

Build, test, release

See docs/BUILDING.md for editable installs, running the test suite (pytest -v — ~101 tests), building wheels (uv build), and publishing to PyPI (twine upload dist/*).

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vigenere_solver_ng-0.1.1.tar.gz (53.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vigenere_solver_ng-0.1.1-py3-none-any.whl (48.2 kB view details)

Uploaded Python 3

File details

Details for the file vigenere_solver_ng-0.1.1.tar.gz.

File metadata

  • Download URL: vigenere_solver_ng-0.1.1.tar.gz
  • Upload date:
  • Size: 53.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for vigenere_solver_ng-0.1.1.tar.gz
Algorithm Hash digest
SHA256 1e380805909e63ef6a25dd9cd4ee7889e436e19c2b3394f27bbbdf605883c5fa
MD5 9e848d785b1b55978cb68fa3446dd8d2
BLAKE2b-256 425b2450fa9ab773c9be688820ef84046e1a94fc54444f2d9b84b1fa9f67e1e8

See more details on using hashes here.

File details

Details for the file vigenere_solver_ng-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for vigenere_solver_ng-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d9762d28cd40a515ea1649417be54e5923635f0174164331dd94b1aa3f0e71dd
MD5 2b7b45f8ab9fc20e5d43120fd7fa0d05
BLAKE2b-256 ee46aa66f276b64cf9850669a5573fc8698fdbd932df5156a128ac73c184c695

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page