# Substitution Cipher Solver
### Algorithm for solving simple, monoalphabetic substitution ciphers
This is Python implementation of the algorithm for solving
[simple, monoalphabetic substitution ciphers](https://en.wikipedia.org/wiki/Substitution_cipher#Simple_substitution)
described in the paper
[“A Fast Method for the Cryptanalysis of Substitution Ciphers”](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.55.89&rep=rep1&type=pdf)
by Thomas Jakobsen. The main difference from the paper is that random key swaps are used
instead of a deterministic series of swaps since it yields better results, but the
original method is included and can be used as an option.
#### Installing
pip install cipher_solver
#### API
```python
class SimpleSolver:
"""Simple substitution cipher solver."""
def __init__(self, ciphertext):
"""Create new solver.
Creates a new cipher solver from an initial ciphertext.
"""
def solve(self, method="random"):
"""Solve the cipher.
Run the solver and save the resulting decryption key.
"""
def plaintext(self):
"""Return a plaintext using the current decryption key."""
def reset(self):
"""Reset the solver to its initial state.
Set the decryption key to its initial state, effectively starting over.
"""
```
See the [documentation](https://alimony.github.io/cipher_solver/) for full description
of methods and their parameters.
#### Requirements
* numpy
#### Usage
```python
from cipher_solver.simple import SimpleSolver
# Solve a cipher.
s = SimpleSolver("U kn kgmhksz tkm exmpb xt Gxesxe.")
s.solve()
print(s.plaintext()) # "I am already far north of London."
s.reset() # Discard current solution to start over.
s.solve()
print(s.plaintext()) # We have an alternative solution now.
print(s.decryption_key()) # "goaskbihxvrldepfwntmzqjucy"
# Solve using the original key swap method instead.
d = SimpleSolver("U kn kgmhksz tkm exmpb xt Gxesxe.", method="deterministic")
d.solve()
print(s.plaintext())
```
Note, however, that the above ciphertext is too short to give any meaningful results.
A length of at least a few hundred letters is preferred to solve a cipher. See below for
an example using an included sample text.
#### CLI
A simple command-line interface is included. To solve a cipher, put it into a text file
and run:
```bash
cipher_solver <path_to_ciphertext_file>
```
Example:
```bash
cipher_solver texts/26_char_key/ciphertexts/ciphertext_frankenstein_sample.txt
```
Since the algorithm involves [hill climbing](https://en.wikipedia.org/wiki/Hill_climbing)
and randomness you might sometimes end up with complete gibberish, just run the script
again and the next result should be better.
#### Running tests
make test
#### Checking coverage
make coverage
(Requires the `coverage` package.)
#### Generating documentation
make docs
(Requires the `pdoc3` package.)
Raw data
{
"_id": null,
"home_page": "https://github.com/alimony/cipher_solver",
"name": "cipher-solver",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": null,
"keywords": "monoalphabetic-substitution-cipher substitution-cipher cipher",
"author": "Markus Amalthea Magnuson",
"author_email": "markus@polyscopic.works",
"download_url": "https://files.pythonhosted.org/packages/6c/bd/39bb025690030ff5c9318ab4cef76aea3138d3211e95da7286ce438194bb/cipher_solver-1.0.2.tar.gz",
"platform": null,
"description": "# Substitution Cipher Solver\n\n### Algorithm for solving simple, monoalphabetic substitution ciphers\n\nThis is Python implementation of the algorithm for solving\n[simple, monoalphabetic substitution ciphers](https://en.wikipedia.org/wiki/Substitution_cipher#Simple_substitution)\ndescribed in the paper\n[\u201cA Fast Method for the Cryptanalysis of Substitution Ciphers\u201d](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.55.89&rep=rep1&type=pdf)\nby Thomas Jakobsen. The main difference from the paper is that random key swaps are used\ninstead of a deterministic series of swaps since it yields better results, but the\noriginal method is included and can be used as an option.\n\n#### Installing\n\n pip install cipher_solver\n\n#### API\n\n```python\nclass SimpleSolver:\n \"\"\"Simple substitution cipher solver.\"\"\"\n\n def __init__(self, ciphertext):\n \"\"\"Create new solver.\n\n Creates a new cipher solver from an initial ciphertext.\n \"\"\"\n\n def solve(self, method=\"random\"):\n \"\"\"Solve the cipher.\n\n Run the solver and save the resulting decryption key.\n \"\"\"\n\n def plaintext(self):\n \"\"\"Return a plaintext using the current decryption key.\"\"\"\n\n def reset(self):\n \"\"\"Reset the solver to its initial state.\n\n Set the decryption key to its initial state, effectively starting over.\n \"\"\"\n```\nSee the [documentation](https://alimony.github.io/cipher_solver/) for full description\nof methods and their parameters.\n\n#### Requirements\n\n* numpy\n\n#### Usage\n\n```python\nfrom cipher_solver.simple import SimpleSolver\n\n# Solve a cipher.\ns = SimpleSolver(\"U kn kgmhksz tkm exmpb xt Gxesxe.\")\ns.solve()\nprint(s.plaintext()) # \"I am already far north of London.\"\n\ns.reset() # Discard current solution to start over.\ns.solve()\nprint(s.plaintext()) # We have an alternative solution now.\n\nprint(s.decryption_key()) # \"goaskbihxvrldepfwntmzqjucy\"\n\n# Solve using the original key swap method instead.\nd = SimpleSolver(\"U kn kgmhksz tkm exmpb xt Gxesxe.\", method=\"deterministic\")\nd.solve()\nprint(s.plaintext())\n```\n\nNote, however, that the above ciphertext is too short to give any meaningful results.\nA length of at least a few hundred letters is preferred to solve a cipher. See below for\nan example using an included sample text.\n\n#### CLI\n\nA simple command-line interface is included. To solve a cipher, put it into a text file\nand run:\n\n```bash\ncipher_solver <path_to_ciphertext_file>\n```\n\nExample:\n\n```bash\ncipher_solver texts/26_char_key/ciphertexts/ciphertext_frankenstein_sample.txt\n```\n\nSince the algorithm involves [hill climbing](https://en.wikipedia.org/wiki/Hill_climbing)\nand randomness you might sometimes end up with complete gibberish, just run the script\nagain and the next result should be better.\n\n#### Running tests\n\n make test\n\n#### Checking coverage\n\n make coverage\n\n(Requires the `coverage` package.)\n\n#### Generating documentation\n\n make docs\n\n(Requires the `pdoc3` package.)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Algorithm for solving simple, monoalphabetic substitution ciphers",
"version": "1.0.2",
"project_urls": {
"Documentation": "https://alimony.github.io/cipher_solver/",
"Homepage": "https://github.com/alimony/cipher_solver",
"Source": "https://github.com/alimony/cipher_solver",
"Tracker": "https://github.com/alimony/cipher_solver/issues"
},
"split_keywords": [
"monoalphabetic-substitution-cipher",
"substitution-cipher",
"cipher"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "357ea549abe27dd8e696c8dd239b62dd2e18e87a9225a1353802c37c702cf7c6",
"md5": "66e2a2043507a7b79ccd6b0a8405dd05",
"sha256": "b6419461356465c2f67ff8383156055f91bd6eead1020b684b1a201279b54cea"
},
"downloads": -1,
"filename": "cipher_solver-1.0.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "66e2a2043507a7b79ccd6b0a8405dd05",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 11977,
"upload_time": "2025-07-28T08:39:39",
"upload_time_iso_8601": "2025-07-28T08:39:39.173877Z",
"url": "https://files.pythonhosted.org/packages/35/7e/a549abe27dd8e696c8dd239b62dd2e18e87a9225a1353802c37c702cf7c6/cipher_solver-1.0.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "6cbd39bb025690030ff5c9318ab4cef76aea3138d3211e95da7286ce438194bb",
"md5": "b2e5b020ad3d93811113f42dd75cb42b",
"sha256": "4c8dcf0ef96fab593394f8965705f4810d938d6a0e6537c2af20251c76781606"
},
"downloads": -1,
"filename": "cipher_solver-1.0.2.tar.gz",
"has_sig": false,
"md5_digest": "b2e5b020ad3d93811113f42dd75cb42b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 12017,
"upload_time": "2025-07-28T08:39:40",
"upload_time_iso_8601": "2025-07-28T08:39:40.050847Z",
"url": "https://files.pythonhosted.org/packages/6c/bd/39bb025690030ff5c9318ab4cef76aea3138d3211e95da7286ce438194bb/cipher_solver-1.0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-28 08:39:40",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "alimony",
"github_project": "cipher_solver",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [
{
"name": "numpy",
"specs": [
[
"==",
"2.3.2"
]
]
}
],
"lcname": "cipher-solver"
}