Coder Social home page Coder Social logo

classicalciphers.jl's Introduction

ClassicalCiphers.jl

Dev CI Coverage Status Code Style: Blue

Main Features

Provides access to encryption and decryption of strings according to a variety of classical algorithms. Classical ciphers were created before computers, and thus work on letters rather than bits.

The Solitaire cipher is included for completeness, though it is perhaps not strictly classical.

Currently Implemented

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'):

encrypt_caesar("Hello, World!", 3)
# outputs "KHOOR, ZRUOG!"

Notice that encrypt_caesar turns everything upper-case, but retains symbols.

Decrypt the same text:

decrypt_caesar("Khoor, Zruog!", 3)
# outputs "hello, world!"

Likewise, decrypt_caesar turns everything lower-case, but retains symbols.

Automatically crack the same text:

crack_caesar("Khoor, Zruog!")
# outputs ("hello, world!", 3)

Affine cipher

Encrypt the text "Hello, World!" with the function x -> 3x+4:

encrypt_affine("Hello, World!", 3, 4)
# outputs "ZQLLU, SUDLN!"

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:

decrypt_affine("ZQLLU, SUDLN!", 3, 4)
# outputs "hello, world!"

Crack the same text:

crack_affine("ZQLLU, SUDLN!")
# outputs ("hello, world!", (3, 4))

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:

encrypt_monoalphabetic("Hello, World!", "DEFGHIJKLMNOPQRSTUVWXYZABC")
# outputs "KHOOR, ZRUOG!"

Decrypt the same text:

decrypt_monoalphabetic("Khoor, Zruog!", "DEFGHIJKLMNOPQRSTUVWXYZABC")
# outputs "hello, world!"

Encrypt using a Dict:

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:

crack_monoalphabetic(str, chatty=0, rounds=10)
# outputs (decrypted_string, key)

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":

encrypt_vigenere("Hello, World!", "ab")
# outputs "HFLMOXOSLE"

Decrypt the same text with the offsets given as an array:

decrypt_vigenere("HFLMOXOSLE", [0, 1])
# outputs "helloworld"

Notice that the offset 0 corresponds to the key a.

Crack a text:

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":

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:

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]:

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:

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:

encrypt_hill("Hello", "bcfh")
# outputs "PLHCIX"

decrypt_hill("PLHCIX", "bcfh")
# outputs "hellox"

Decrypt the text "PLHCGQWHRY" with key of [1 2; 5 7]:

decrypt_hill("PLHCGQWHRY", [1 2; 5 7])
# outputs "helloworld"

Do the same, but using the string representation of the key:

decrypt_hill("PLHCGQWHRY", "bcfh")
# outputs "helloworld"

Playfair cipher

Encrypt the text "Hello, World!" with the Playfair cipher, key "playfair example":

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:

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'):

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:

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.

Enigma

The variant of Enigma implemented is the M3 Army version. This has five possible rotors, of which three are chosen in some distinct order.

The plugboard may be specified either as a Array{Tuple{Char, Char}} or a string. For example, both the following plugboards have the same effect:

"ABCDEF"
[('A', 'B'), ('C', 'D'), ('E', 'F')]

For no plugboard, use Tuple{Char, Char}[] or "".

The rotor order may be specified as [5, 1, 2] indicating that the leftmost rotor should be rotor 5, the middle should be rotor 1, and the rightmost should be rotor 2. That is, when a letter goes into Enigma, it passes first through rotor 2, then rotor 1, then rotor 5. (That is, letters move through the machine from right to left, before being reflected.)

The ring settings may be specified as a three-character string. For example, "AAA" indicates no adjustment to the rings. TODO: expand this.

The initial key may be specified as a three-character string. For example, "AQY" indicates that the leftmost rotor should start at position 'A', the middle rotor at position 'Q', and the rightmost at position 'Y'.

Three reflectors are given; they may be specified with reflector_id='A' or 'B' or 'C'. Alternatively, specify reflector_id="YRUHQSLDPXNGOKMIEBFZCWVJAT" to use a custom reflector; this particular example happens to be reflector 'B', so is equivalent to reflector_id='B'.

For example, the following encrypts "AAA" with rotors 1, 2, 3, with key "ABC", an empty plugboard, the default 'B' reflector, and ring "AAA":

encrypt_enigma("AAA", [1,2,3], "ABC")
# outputs "CXT"

This is synonymous with:

encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker="")

And also with:

encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id="YRUHQSLDPXNGOKMIEBFZCWVJAT", stecker="")

And also with:

encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker=Tuple{Char, Char}[])

The arguments to decrypt_enigma are identical. (In fact, decrypt_enigma and encrypt_enigma are essentially the same function, because Enigma is reversible.) As ever, encrypt_enigma uppercases its input, and decrypt_enigma lowercases it.

Solitaire cipher

Encrypt the text "Hello, World!" with the Solitaire cipher, key "crypto":

encrypt_solitaire("Hello, World!", "crypto")
# outputs "GRNNQISRYA"

Decrypt text with an initial deck specified:

decrypt_solitaire("EXKYI ZSGEH UNTIQ", collect(1:54))
# outputs "aaaaaaaaaaaaaaa", as per https://www.schneier.com/code/sol-test.txt

Rail Fence cipher

julia> construct_railfence("WE ARE DISCOVERED. FLEE AT ONCE", 3)
3×26 Array{Char,2}:
 'W'  ''  ''  ''  'E'  ''  ''  ''  'C'  ''  ''  ''  'R'    ''  ''  'F'  ''  ''  ''  'A'  ''  ''  ''  'C'  ''
 ''  'E'  ''  'R'  ''  'D'  ''  'S'  ''  'O'  ''  'E'  ''     ''  '.'  ''  'L'  ''  'E'  ''  'T'  ''  'N'  ''  'E'
 ''  ''  'A'  ''  ''  ''  'I'  ''  ''  ''  'V'  ''  ''     'D'  ''  ''  ''  'E'  ''  ''  ''  'O'  ''  ''  ''

julia> encrypt_railfence("WE ARE DISCOVERED. FLEE AT ONCE", 3) # this reads the above matrix row by row
"WECRFACERDSOEE.LETNEAIVDEO"

julia> decrypt_railfence("WECRFACERDSOEE.LETNEAIVDEO", 3)
"wearediscovered.fleeatonce"

Atbash

encrypt_atbash("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz") == encrypt_substitution("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba")

classicalciphers.jl's People

Contributors

cormullion avatar jakewilliami avatar smaug123 avatar staticfloat avatar tkelman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

classicalciphers.jl's Issues

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Expose `frequencies` function for frequency analysis?

While I am adding a couple of issues, this is one that came to mind.

Currently the API doesn't explicitly expose (export) the frequencies function. I think it might me nice to expose it. However, it currently counts from -1 for some reason (I don't know why, but I'm sure there is good reason for it). If you like, I can refactor to make the frequencies function that counts from -1 into an internal function, and expose a new frequencies function which adds 1 to each value, and is exported.

What do you think? Would be a quick change; let me know.

[bug] Cracking substitution/monoalphabetic cipher failing

julia> crack_monoalphabetic("93ee90299")
ERROR: MethodError: no method matching #crack_monoalphabetic#15(::String, ::Float64, ::Float64, ::ClassicalCiphers.var"#17#20", ::Int64, ::Int64, ::typeof(crack_monoalphabetic), ::String)
Closest candidates are:
  #crack_monoalphabetic#15(::AbstractString, ::AbstractFloat, ::AbstractFloat, ::AbstractFloat, ::Integer, ::Integer, ::typeof(crack_monoalphabetic), ::Any) at /Users/jakeireland/.julia/packages/ClassicalCiphers/VPcNx/src/monoalphabetic.jl:172
Stacktrace:
 [1] crack_monoalphabetic(::String) at /Users/jakeireland/.julia/packages/ClassicalCiphers/VPcNx/src/monoalphabetic.jl:182

Suppress `chatty` printing in monoalphabetic tests using Suppressor.jl?

This would be a test-specific dependency.

Due to code coverage, we needed to add a chatty kwarg to one of the monoalphabetic tests. I do not think it ideal to print the output of that in the tests (we only really care about the total outcome for testing; i.e., did it work?). We could use Suppressor.jl for this, as a test-specific dependency. Alternatively, we could alter the crack_monoalphabetic function to take in an input that is an IOStream and default to stdout.

Which would you prefer @Smaug123? Either way I can sort it out, just let me know

Info about upcoming removal of packages in the General registry

As described in https://discourse.julialang.org/t/ann-plans-for-removing-packages-that-do-not-yet-support-1-0-from-the-general-registry/ we are planning on removing packages that do not support 1.0 from the General registry. This package has been detected to not support 1.0 and is thus slated to be removed. The removal of packages from the registry will happen approximately a month after this issue is open.

To transition to the new Pkg system using Project.toml, see https://github.com/JuliaRegistries/Registrator.jl#transitioning-from-require-to-projecttoml.
To then tag a new version of the package, see https://github.com/JuliaRegistries/Registrator.jl#via-the-github-app.

If you believe this package has erroneously been detected as not supporting 1.0 or have any other questions, don't hesitate to discuss it here or in the thread linked at the top of this post.

`index_of_coincidence` has discrepant results with www.dcode.fr/index-coincidence

E.g.,

julia> ClassicalCiphers.index_of_coincidence("""
       To be, or not to be, that is the question—
       Whether 'tis Nobler in the mind to suffer
       The Slings and Arrows of outrageous Fortune,
       Or to take Arms against a Sea of troubles,
       And by opposing end them?
       William Shakespeare - Hamlet
       """)
1.5006420133538778

But dcode.fr reports the IoC as 0.06773.

julia> index_of_coincidence("smaug123classicalciphers")
0.6190476190476191

But decode.fr reports the IoC as 0.06667.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.