Ciphers

class blaise.ciphers.Caesar

Bases: Cipher

The Caesar Cipher is the simplest of ciphers, where each letter is shifted by a fixed amount in either direction. Wikipedia page: https://en.wikipedia.org/wiki/Caesar_cipher

crack(ciphertext, scorer: Scorer | None = None, top_n: int | None = None) DataFrame

Cracks a Caesar shift cipher. It will try all shifts and score them with an ngram scorer.

Parameters:
  • ciphertext (str) – The ciphertext to crack.

  • scorer (optional) – Scorer function to evaluate plaintext candidates.

  • top_n (optional) – Number of top results to return.

Example

>>> Caesar().crack('EBIILTLOIA', top_n=3)
shape: (3, 3)
┌─────┬────────────┬──────────┐
│ key ┆ plaintext  ┆ score    │
│ --- ┆ ---        ┆ ---      │
│ i64 ┆ str        ┆ f64      │
╞═════╪════════════╪══════════╡
│ 23  ┆ HELLOWORLD ┆ 1.535752 │
│ 8   ┆ WTAADLDGAS ┆ 1.995915 │
│ 19  ┆ LIPPSASVPH ┆ 2.051054 │
└─────┴────────────┴──────────┘
decrypt(ciphertext: str, key: int) str

Decrypts a Caesar shift that has been applied. Positive key value indicates that it was encrypted with a right shift, and negative with a left shift.

encrypt(plaintext: str, key: int) str

Applies a Caesar shift to plaintext. Positive key value is a right shift, negative is a left shift.

class blaise.ciphers.Playfair(fill_char='X', alt_fill_char='Q', missing_letter: str = 'J->I')

Bases: Cipher

Wikipedia page: https://en.wikipedia.org/wiki/Playfair_cipher.

decrypt(ciphertext: str, key: str, remove_fill=False) str

Decrypts using the Playfair cipher. Will fail if: - Any bigram is a repeat - Any character in the ciphertext isn’t present in the alphabet provided

>>> Playfair().decrypt("BMODZBXDNABEKUDMUIXMMOUVIF", "playfairexample")
'HIDETHEGOLDINTHETREXESTUMP'

Optionally there is a heuristic approach to removing fill characters. It isn’t perfect - will be caught out by a legitimate EX EC for example - would remove the X in that case.

>>> Playfair().decrypt("BMODZBXDNABEKUDMUIXMMOUVIF", "playfairexample", remove_fill=True)
'HIDETHEGOLDINTHETREESTUMP'
encrypt(plaintext: str, key: str) str

Encrypts using the Playfair cipher.

>>> Playfair().encrypt("hide the gold in the tree stump", "playfairexample")
'BMODZBXDNABEKUDMUIXMMOUVIF'
class blaise.ciphers.Vigenere

Bases: Cipher

The Vigenère Cipher is a straightforward cipher formed by interleaving Caesar shifts. Wikipedia page: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher.

crack(ciphertext: str, key_length: int | Iterable[int] = range(3, 8), top_n=10, n_trials: int = 1000, scorer=None, dist='en_wiki') DataFrame

Cracks a Vigenère cipher.

Parameters:
  • ciphertext (str) – The ciphertext to crack. Note that non-alphabetic characters are ignored.

  • key_length (int | Iterable[int], optional) – The length(s) of the key to try. If an integer is supplied, the function will attempt to crack a key of that exact length. If an iterable is supplied, the function will iterate over each length in the iterable. The default is range(3, 8), which tries key lengths 3 through 7.

  • top_n (int, optional) – The maximum number of results to return. Results are sorted by the provided scorer.

  • n_trials (int, optional) – The maximum number of key combinations to evaluate for each key_length. This limits the search space for long keys.

  • scorer (Scorer, optional) – The scorer to use for assessing the quality of the output plaintext.

  • dist (The frequency distribution to use for scoring the Caesar step.)

Returns:

A DataFrame with columns key and plaintext containing the best candidate keys and their corresponding decrypted plaintexts. The DataFrame is sorted by score in descending order and limited to top_n rows.

Return type:

polars.DataFrame

Notes

The function works by splitting the ciphertext into key_length interleaved subsequences and cracking each subsequence with a Caesar-cipher cracker. The resulting candidate keys are then combined using a Cartesian product to form full Vigenère keys in order of likelihood based on frequency analysis of each interleaved Caesar shift. The search is truncated after n_trials combinations to keep runtime reasonable for long keys.

Examples

Here is an example with a relatively short ciphertext. Note that the correct decrypt is not the top result because the ciphertext is too short for very accurate decryption.

>>> Vigenere().crack("DLCFMEORCBIASTFOV", key_length=3, top_n=3)
shape: (3, 3)
┌─────┬───────────────────┬──────────┐
│ key ┆ plaintext         ┆ score    │
│ --- ┆ ---               ┆ ---      │
│ str ┆ str               ┆ f64      │
╞═════╪═══════════════════╪══════════╡
│ OAY ┆ PLERMGARENICETHAV ┆ 1.135415 │
│ KEY ┆ THEVIGENERECIPHER ┆ 1.157981 │
│ OEY ┆ PHERIGANENECEPHAR ┆ 1.158325 │
└─────┴───────────────────┴──────────┘
decrypt(ciphertext: str, key: str) str

Decrypt ciphertext that was encrypted with the Vigenère cipher.

Parameters:
  • ciphertext – The text to decrypt.

  • key – The decryption key. It must consist only of alphabetic characters; the function will raise a ValueError if this condition is not met.

Returns:

The original plaintext recovered from ciphertext.

Return type:

str

Examples

>>> Vigenere().decrypt("MSZQC", "FOO")
'HELLO'
encrypt(plaintext: str, key: str) str

Encrypt plaintext using the Vigenère cipher.

Parameters:
  • plaintext – The text to encrypt.

  • key – The encryption key. It must consist only of alphabetic characters; the function will raise a ValueError if this condition is not met.

Returns:

The ciphertext produced by applying the Vigenère shift to each alphabetic character of plaintext.

Return type:

str

Examples

>>> Vigenere().encrypt("HELLO", "FOO")
'MSZQC'