Added substitution cipher

This commit is contained in:
Jake W. Ireland
2021-01-11 19:31:53 +13:00
parent 29ebc22cd2
commit 55183d284c
5 changed files with 167 additions and 2 deletions

View File

@@ -19,6 +19,7 @@ The Solitaire cipher is included for completeness, though it is perhaps not stri
* [Enigma (M3 Army)][Enigma]
* [Solitaire]
* [Rail Fence]
* [Substitution]
## Gotchas
@@ -375,6 +376,28 @@ julia> decrypt_railfence("WECRFACERDSOEE.LETNEAIVDEO", 3)
"wearediscovered.fleeatonce"
```
### Substitution cipher
```julia
julia> encrypt_substitution("Hello, this is plaintext", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm")
"ITSSG, ZIOL OL HSQOFZTBZ"
julia> encrypt_substitution("Hello, this is plaintext", "qwertyuiopasdfghjklzxcvbnm")
"ITSSG, ZIOL OL HSQOFZTBZ"
julia> encrypt_substitution("xyz", Dict('x' => 'd', 'y' => 'e', 'z' => 't'))
"DET"
julia> decrypt_substitution("ITSSG, ZIOL OL HSQOFZTBZ", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm", reverse_dict = true)
"hello, this is plaintext"
julia> encrypt_atbash("some text", "abcdefghijklmnopqrstuvwxyz")
"HLNV GVCG"
julia> decrypt_atbash("HLNV GVCG", "abcdefghijklmnopqrstuvwxyz")
"some text"
```
[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
@@ -385,3 +408,4 @@ julia> decrypt_railfence("WECRFACERDSOEE.LETNEAIVDEO", 3)
[Playfair]: https://en.wikipedia.org/wiki/Playfair_cipher
[Enigma]: https://en.wikipedia.org/wiki/Enigma_machine
[Rail Fence]: https://en.wikipedia.org/wiki/Rail_fence_cipher
[Substitution]: https://en.wikipedia.org/wiki/Substitution_cipher

View File

@@ -13,6 +13,7 @@ include("hill.jl")
include("playfair.jl")
include("enigma.jl")
include("railfence.jl")
include("substitution.jl")
export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
encrypt_caesar, decrypt_caesar, crack_caesar,
@@ -24,6 +25,7 @@ export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
encrypt_playfair, decrypt_playfair,
encrypt_enigma, decrypt_enigma,
string_fitness, index_of_coincidence,
construct_railfence, encrypt_railfence, decrypt_railfence
construct_railfence, encrypt_railfence, decrypt_railfence,
encrypt_substitution, decrypt_substitution, encrypt_atbash, decrypt_atbash
end # module

105
src/substitution.jl Normal file
View File

@@ -0,0 +1,105 @@
construct_sub_dict(A, B) =
Dict{eltype(A), eltype(B)}(a => b for (a, b) in zip(lowercase(A), lowercase(B))) # policy decision: all dictionaries are lowercase
Base.reverse(D::Dict{T, S}) where {T, S} =
Dict{S, T}(reverse(p) for p in D)
something_crypt_substitution(text, sub_dict::Dict{T, S}) where {T, S} =
S[get(sub_dict, c, c) for c in text]
@doc raw"""
Arguably the most simple of the classical ciphers, the substitution cipher works by creating an arbitrary substitution dictionary; e.g.,
```julia
'a' => 'x'
'b' => 'g'
'c' => 'l'
...
```
This dictionary then replaces every corresponding letter in the plaintext input with a different letter (as specified by the dictionary input.)
The function `encrypt_substitution` will either take this dictionary as its second parameter, _or_ it can construct the dictionary itself:
```julia
encrypt_substitution(plaintext, Dict(...))
encrypt_substitution(plaintext, "abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba") # this will create the dictionary 'a' => 'z', 'b' => 'y', ..., 'z' => 'a'
encrypt_substitution(plaintext, "zyxwvutsrqponmlkjihgfedcba") # this will create the dictionary 'a' => 'z', 'b' => 'y', ..., 'z' => 'a' by assuming the keys in the substitution dictionary
```
*All characters undefined in the dictionary are preserved by default; this includes punctionation and spaces.*
As per convention, the output will always be uppercase.
For more information, see https://en.wikipedia.org/wiki/Substitution_cipher.
"""
encrypt_substitution(plaintext, sub_dict::Dict{T, S}) where {T, S} =
uppercase(join(something_crypt_substitution(lowercase(plaintext), sub_dict)))
encrypt_substitution(plaintext, A, B) =
encrypt_substitution(plaintext, construct_sub_dict(A, B))
encrypt_substitution(plaintext, B) =
encrypt_substitution(plaintext, "abcdefghijklmnopqrstuvwxyz", B)
@doc raw"""
Arguably the most simple of the classical ciphers, the substitution cipher works by creating an arbitrary substitution dictionary; e.g.,
```julia
'a' => 'x'
'b' => 'g'
'c' => 'l'
...
```
This dictionary then replaces every corresponding letter in the plaintext input with a different letter (as specified by the dictionary input.)
The function `decrypt_substitution` will either take this dictionary as its second parameter, _or_ it can construct the dictionary itself:
```julia
decrypt_substitution(ciphertext, Dict(...); reverse_dict = true)
decrypt_substitution(ciphertext, "abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba"; reverse_dict = true) # this will create the dictionary 'a' => 'z', 'b' => 'y', ..., 'z' => 'a'
decrypt_substitution(ciphertext, "zyxwvutsrqponmlkjihgfedcba"; reverse_dict = true) # this will create the dictionary 'a' => 'z', 'b' => 'y', ..., 'z' => 'a' by assuming the keys in the substitution dictionary
```
*All characters undefined in the dictionary are preserved by default; this includes punctionation and spaces.*
*If `reverse_dict` is set to true (as it is by default), the input dictionary is assumed to be the same used to _en_crypt, meaning it is reversed in order to _decrypt_ the ciphertext.*
As per convention, the output will always be lowercase.
For more information, see https://en.wikipedia.org/wiki/Substitution_cipher.
"""
function decrypt_substitution(ciphertext, sub_dict::Dict{T, S}; reverse_dict::Bool = true) where {T, S}
sub_dict = reverse_dict ? reverse(sub_dict) : sub_dict
return lowercase(join(something_crypt_substitution(lowercase(ciphertext), sub_dict)))
end
decrypt_substitution(ciphertext, A, B; reverse_dict::Bool = true) =
decrypt_substitution(ciphertext, construct_sub_dict(A, B); reverse_dict = reverse_dict)
decrypt_substitution(ciphertext, B; reverse_dict::Bool = true) =
decrypt_substitution(ciphertext, "abcdefghijklmnopqrstuvwxyz", B; reverse_dict = reverse_dict)
"""
```julia
encrypt_atbash(plaintext, alphabet)
```
A special case of the substitution cipher, the Atbash cipher substitutes a given alphabet with its reverse:
```julia
encrypt_atbash(plaintext, "abcdefghijklmnopqrstuvwxyz") == encrypt_substitution(plaintext, "abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba")
```
*Omitting the alphabet, it will assume you are using the English alphabet.*
"""
encrypt_atbash(plaintext, alphabet) =
encrypt_substitution(plaintext, alphabet, reverse(alphabet))
encrypt_atbash(plaintext) =
encrypt_atbash(plaintext, "abcdefghijklmnopqrstuvwxyz")
"""
```julia
decrypt_atbash(ciphertext, alphabet)
```
A special case of the substitution cipher, the Atbash cipher substitutes a given alphabet with its reverse:
```julia
decrypt_atbash(ciphertext, "abcdefghijklmnopqrstuvwxyz") == decrypt_substitution(ciphertext, "zyxwvutsrqponmlkjihgfedcba", "abcdefghijklmnopqrstuvwxyz")
decrypt_atbash(ciphertext, "abcdefghijklmnopqrstuvwxyz") == decrypt_substitution(ciphertext, "zyxwvutsrqponmlkjihgfedcba"; reverse_dict = true)
```
*Omitting the alphabet, it will assume you are using the English alphabet.*
"""
decrypt_atbash(ciphertext, alphabet) =
decrypt_substitution(ciphertext, reverse(alphabet), alphabet)
decrypt_atbash(ciphertext) =
decrypt_atbash(ciphertext, "abcdefghijklmnopqrstuvwxyz")

View File

@@ -11,7 +11,8 @@ tests = [
"enigma",
"hill",
"solitaire",
"railfence"
"railfence",
"substitution"
]
println("Running tests:")

33
test/substitution.jl Normal file
View File

@@ -0,0 +1,33 @@
using ClassicalCiphers
using Test
@test encrypt_substitution("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz") == "HELLO THIS IS PLAINTEXT"
@test encrypt_substitution("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz") == "HELLO THIS IS PLAINTEXT"
@test encrypt_substitution("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm") == "ITSSG ZIOL OL HSQOFZTBZ"
@test encrypt_substitution("hello this is plaintext", "qwertyuiopasdfghjklzxcvbnm", "abcdefghijklmnopqrstuvwxyz") == "PCSSI EPHL HL JSKHYECUE"
@test encrypt_substitution("ITSSG ZIOL OL HSQOFZTBZ", "qwertyuiopasdfghjklzxcvbnm", "abcdefghijklmnopqrstuvwxyz") == "HELLO THIS IS PLAINTEXT"
@test encrypt_substitution("ITSSG ZIOL OL HSQOFZTBZ", "qwertyuiopasdfghjklzxcvbnm", "abcdefghijklmnopqrstuvwxyz") != encrypt_substitution("ITSSG ZIOL OL HSQOFZTBZ", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm")
@test encrypt_substitution("hello this is plaintext", Dict('n' => 'y','f' => 'n','w' => 'b','d' => 'm','e' => 'c','o' => 'i','h' => 'p','y' => 'f','i' => 'h','r' => 'd','t' => 'e','s' => 'l','j' => 'q','q' => 'a','k' => 'r','a' => 'k','c' => 'v','p' => 'j','m' => 'z','z' => 't','g' => 'o','x' => 'u','u' => 'g','l' => 's','v' => 'w','b' => 'x')) == "PCSSI EPHL HL JSKHYECUE"
@test encrypt_substitution("hello this is plaintext", Dict('n' => 'f','f' => 'y','w' => 'v','d' => 'r','e' => 't','o' => 'g','h' => 'i','j' => 'p','i' => 'o','k' => 'a','r' => 'k','s' => 'l','t' => 'z','q' => 'j','y' => 'n','a' => 'q','c' => 'e','p' => 'h','m' => 'd','z' => 'm','g' => 'u','v' => 'c','l' => 's','u' => 'x','x' => 'b','b' => 'w')) == "ITSSG ZIOL OL HSQOFZTBZ"
@test decrypt_substitution("hello this is ciphertext", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz") == "hello this is ciphertext"
@test decrypt_substitution("hello this is ciphertext", "abcdefghijklmnopqrstuvwxyz") == "hello this is ciphertext"
@test decrypt_substitution("hello this is ciphertext", "abcdefghijklmnopqrstuvwxyz"; reverse_dict = true) == "hello this is ciphertext"
@test decrypt_substitution("hello this is ciphertext", "qwertyuiopasdfghjklzxcvbnm", "abcdefghijklmnopqrstuvwxyz") == "pcssi ephl hl vhjpcdecue"
@test decrypt_substitution("hello this is ciphertext", "qwertyuiopasdfghjklzxcvbnm", "abcdefghijklmnopqrstuvwxyz"; reverse_dict = false) == "itssg ziol ol eohitkztbz"
@test decrypt_substitution("ITSSG ZIOL OL HSQOFZTBZ", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm"; reverse_dict = true) == "ozllu mogs gs iljgymzwm"
@test decrypt_substitution("ITSSG ZIOL OL HSQOFZTBZ", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm"; reverse_dict = false) == "hello this is plaintext"
@test decrypt_substitution("PCSSI EPHL HL JSKHYECUE", "qwertyuiopasdfghjklzxcvbnm", "abcdefghijklmnopqrstuvwxyz"; reverse_dict = true) == "jvllh cjps ps qlrpfcvgc"
@test decrypt_substitution("PCSSI EPHL HL JSKHYECUE", "qwertyuiopasdfghjklzxcvbnm", "abcdefghijklmnopqrstuvwxyz"; reverse_dict = false) == "hello this is plaintext"
@test decrypt_substitution("ITSSG ZIOL OL HSQOFZTBZ", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm"; reverse_dict = false) != decrypt_substitution("ITSSG ZIOL OL HSQOFZTBZ", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm"; reverse_dict = true)
@test encrypt_atbash("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz") == "SVOOL GSRH RH KOZRMGVCG"
@test encrypt_atbash("hello this is plaintext") == "SVOOL GSRH RH KOZRMGVCG"
@test decrypt_atbash("SVOOL GSRH RH KOZRMGVCG", "abcdefghijklmnopqrstuvwxyz") == "hello this is plaintext"
@test decrypt_atbash("SVOOL GSRH RH KOZRMGVCG") == "hello this is plaintext"
@test encrypt_atbash("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz") == encrypt_substitution("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba")
@test decrypt_atbash("SVOOL GSRH RH XRKSVIGVCG", "abcdefghijklmnopqrstuvwxyz") == decrypt_substitution("SVOOL GSRH RH XRKSVIGVCG", "zyxwvutsrqponmlkjihgfedcba", "abcdefghijklmnopqrstuvwxyz")
@test decrypt_atbash("SVOOL GSRH RH XRKSVIGVCG", "abcdefghijklmnopqrstuvwxyz") == decrypt_substitution("SVOOL GSRH RH XRKSVIGVCG", "zyxwvutsrqponmlkjihgfedcba"; reverse_dict = true)