Files
ClassicalCiphers.jl/README.md
2016-01-08 20:09:12 +00:00

309 lines
8.4 KiB
Markdown

[![Build Status](https://travis-ci.org/Smaug123/ClassicalCiphers.jl.svg?branch=master)](https://travis-ci.org/Smaug123/ClassicalCiphers.jl)
# ClassicalCiphers
## Main Features
Provides access to encryption and decryption of strings according to a variety of classical algorithms.
The Solitaire cipher is included for completeness, though it is perhaps not strictly classical.
## Currently Implemented
* [Caesar]
* [Affine]
* [Monoalphabetic substitution]
* [Vigenère]
* [Portas]
* [Hill]
* [Playfair]
* [Solitaire]
## Gotchas
In general, `encrypt` functions turn text upper-case, while `decrypt` functions
turn text lower-case.
This is consistent with convention, but may not be expected.
## Code samples
This is the last section of the readme. Nothing appears after this section.
### Caesar cipher
Encrypt the text "Hello, World!" with a Caesar offset of 3 (that is, sending
'a' to 'd'):
```julia
encrypt_caesar("Hello, World!", 3)
# outputs "khoor, zruog!"
```
Notice that `encrypt_caesar` turns everything upper-case, but retains symbols.
Decrypt the same text:
```julia
decrypt_caesar("Khoor, Zruog!", 3)
# outputs "hello, world!"
```
Likewise, `decrypt_caesar` turns everything lower-case, but retains symbols.
Automatically crack the same text:
```julia
crack_caesar("Khoor, Zruog!")
# outputs ("hello, world!", 3)
```
### Affine cipher
Encrypt the text "Hello, World!" with the function `x -> 3x+4`:
```julia
encrypt_affine("Hello, World!", 3, 4)
# outputs
```
Notice that `encrypt_affine` turns everything upper-case, but retains symbols.
The multiplier is the second argument, and the additive constant is the third.
The multiplier must be coprime to 26, or an error is thrown.
Decrypt the same text:
```julia
decrypt_affine("ZQLLU, SUDLN!", 3, 4)
# outputs "hello, world!"
```
Crack the same text:
```julia
crack_affine("ZQLLU, SUDLN!")
# outputs ((3, 4), "hello, world!")
```
You can provide `mult=` or `add=` options to `crack_affine`, if they are known,
to help it out.
### Monoalphabetic cipher
Encrypt the text "Hello, World!" with the same Caesar cipher, but
viewed as a monoalphabetic substitution:
```julia
encrypt_monoalphabetic("Hello, World!", "DEFGHIJKLMNOPQRSTUVWXYZABC")
# outputs "KHOOR, ZRUOG!"
```
Decrypt the same text, this time demonstrating the dictionary capability:
```julia
decrypt_monoalphabetic("Khoor, Zruog!", "DEFGHIJKLMNOPQRSTUVWXYZABC")
# outputs "hello, world!"
```
Encrypt using a Dict:
```julia
encrypt_monoalphabetic("aBcbDd", Dict{Char, Char}('a' => '5', 'B' => '@', 'b' => 'o'))
# outputs "5@coDd"
```
Notice that `encrypt_monoalphabetic` *does not* convert its input to uppercase
when a Dict key is supplied.
It simply makes all specified changes, and leaves the rest of the string unchanged.
Cracking a cipher:
```julia
crack_monoalphabetic(str, chatty=0, rounds=10)
# outputs (key, decrypted_string)
```
The various optional arguments to `crack_monoalphabetic` are:
* `starting_key=""`, which when specified (for example, as "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
starts the simulation at the given key. The default causes it to start with the most
common characters being decrypted to the most common English characters.
* `min_temp=0.0001`, which is the temperature at which we stop the simulation.
* `temp_factor=0.97`, which is the factor by which the temperature decreases each step.
* `chatty=0`, which can be set to 1 to print whenever the key is updated, or 2 to print
whenever any new key is considered.
* `rounds=1`, which sets the number of repetitions we perform. Each round starts with the
best key we've found so far.
* `acceptance_prob=((e, ep, t) -> ep>e ? 1 : exp(-(e-ep)/t))`, which is the probability
with which we accept new key of fitness ep, given that the current key has fitness e,
at temperature t.
The simulation is set up to start each round off at a successively lower temperature.
### Vigenère cipher
Encrypt the text "Hello, World!" with a Vigenère cipher of key "ab":
```julia
encrypt_vigenere("Hello, World!", "ab")
# outputs "HFLMOXOSLE"
```
Decrypt the same text with the offsets given as an array:
```julia
decrypt_vigenere("HFLMOXOSLE", [0, 1])
# outputs "helloworld"
```
Notice that the offset `0` corresponds to the key `a`.
Crack a text:
```julia
crack_vigenere(str)
```
This attempts to use the index of coincidence to find the keylength,
and then performs frequency analysis to derive the key.
It returns (key, decrypted text).
If the keylength is known, specifying it as `crack_vigenere(str, keylength=6)`
may aid decryption.
### Portas cipher
Encrypt the text "Hello, World!" with a Portas cipher of key "ab":
```julia
encrypt_portas("Hello, World!", "ab")
# outputs "URYYB, JBEYQ!"
```
Note that the input has been made uppercase, but symbols have been preserved.
The key is expected to be letters only; it is converted to uppercase and symbols
are stripped out before use.
Decrypt the same text:
```julia
decrypt_portas("URYYB, JBEYQ!", "ab")
# outputs "hello, world!"
```
Notice that the input has been made lowercase.
### Hill cipher
Encrypt the text "Hello, World!" with a Hill key of matrix `[1 2; 5 7]`:
```julia
encrypt_hill("Hello, World!", [1 2; 5 7])
# outputs "PHHRGUWQRV"
```
Notice that the input has been made uppercase and symbols have been stripped out.
The key matrix must be invertible mod 26. That is, its determinant must be
coprime to 26.
Encrypt the same text with the same key, this time represented as a string:
```julia
encrypt_hill("Hello, World!", "bcfh")
# outputs "PLHCGQWHRY"
```
If the plaintext-length is not a multiple of the dimension of the key matrix,
it is padded with X:
```julia
encrypt_hill("Hello", "bcfh")
# outputs "PLHCIX"
decrypt_hill("PLHCIX", "bcfh")
# outputs "hellox"
```
Decrypt the text "PLHCGQWHRY" with key of `[1 2; 5 7]`:
```julia
decrypt_hill("PLHCGQWHRY", [1 2; 5 7])
# outputs "helloworld"
```
Do the same, but using the string representation of the key:
```julia
decrypt_hill("PLHCGQWHRY", "bcfh")
# outputs "helloworld"
```
### Playfair cipher
Encrypt the text "Hello, World!" with the Playfair cipher, key "playfair example":
```julia
encrypt_playfair("Hello, World!", "playfair example")
# outputs "DMYRANVQCRGE"
```
The key is converted to "PLAYFIREXM", removing duplicate letters and punctuation.
The padding character used to separate double letters, and to ensure the final
plaintext is of even length, is 'X'; the backup character is 'Z' (used for separating
consecutive 'X's).
Encrypt the same text using an explicitly specified keysquare:
```julia
arr = ['P' 'L' 'A' 'Y' 'F'; 'I' 'R' 'E' 'X' 'M'; 'B' 'C' 'D' 'G' 'H'; 'K' 'N' 'O' 'Q' 'S'; 'T' 'U' 'V' 'W' 'Z']
encrypt_playfair("Hello, World!", arr)
# outputs "DMYRANVQCRGE"
```
Note that the keysquare must be 25 letters, in a 5x5 array.
Optionally specify the two letters which are to be combined (default 'I','J'):
```julia
encrypt_playfair("IJXYZA", "PLAYFIREXM", combined=('I', 'J'))
# outputs "RMRMFWYE"
encrypt_playfair("IJXYZA", "PLAYFIREXM", combined=('X', 'Z'))
# outputs "BSGXEY"
```
In this case, the letters are combined in the plaintext, and then treated as one throughout.
Decrypt the same text:
```julia
decrypt_playfair("RMRMFWYE", "playfair example")
# outputs "ixixyzax"
```
The decrypting function does not attempt to delete padding letters.
Note that in the above example, the text originally encrypted was "IJXYZA";
the 'J' was transcribed as 'I', as specified by the default `combined=('I', 'J')`,
and then padding 'X's were introduced to ensure no digraph was a double letter.
Finally, an 'X' was appended to the string, to ensure that the string was not of odd
length.
### Solitaire cipher
Encrypt the text "Hello, World!" with the Solitaire cipher, key "crypto":
```julia
encrypt_solitaire("Hello, World!", "crypto")
# outputs "GRNNQISRYA"
```
Decrypt text with an initial deck specified:
```julia
decrypt_solitaire("EXKYI ZSGEH UNTIQ", collect(1:54))
# outputs "aaaaaaaaaaaaaaa", as per https://www.schneier.com/code/sol-test.txt
```
[Caesar]: https://en.wikipedia.org/wiki/Caesar_cipher
[Affine]: https://en.wikipedia.org/wiki/Affine_cipher
[Vigenère]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
[Monoalphabetic substitution]: https://en.wikipedia.org/wiki/Substitution_cipher
[Solitaire]: https://en.wikipedia.org/wiki/Solitaire_(cipher)
[Portas]: http://practicalcryptography.com/ciphers/porta-cipher/
[Hill]: https://en.wikipedia.org/wiki/Hill_cipher
[Playfair]: https://en.wikipedia.org/wiki/Playfair_cipher