mirror of
https://github.com/Smaug123/ClassicalCiphers.jl
synced 2025-10-09 11:28:41 +00:00
Merge pull request #7 from Smaug123/pull-request/8fb21fd0
Add Playfair encryption/decryption
This commit is contained in:
50
README.md
50
README.md
@@ -15,6 +15,7 @@ The Solitaire cipher is included for completeness, though it is perhaps not stri
|
||||
* [Vigenère]
|
||||
* [Portas]
|
||||
* [Hill]
|
||||
* [Playfair]
|
||||
* [Solitaire]
|
||||
|
||||
## Gotchas
|
||||
@@ -233,6 +234,54 @@ 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":
|
||||
@@ -256,3 +305,4 @@ decrypt_solitaire("EXKYI ZSGEH UNTIQ", collect(1:54))
|
||||
[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
|
||||
|
@@ -10,6 +10,7 @@ include("solitaire.jl")
|
||||
include("portas.jl")
|
||||
include("affine.jl")
|
||||
include("hill.jl")
|
||||
include("playfair.jl")
|
||||
|
||||
export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
|
||||
encrypt_caesar, decrypt_caesar, crack_caesar,
|
||||
@@ -18,6 +19,7 @@ export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
|
||||
encrypt_portas, decrypt_portas,
|
||||
encrypt_solitaire, decrypt_solitaire,
|
||||
encrypt_hill, decrypt_hill,
|
||||
encrypt_playfair, decrypt_playfair,
|
||||
string_fitness, index_of_coincidence
|
||||
|
||||
end # module
|
||||
|
131
src/playfair.jl
Normal file
131
src/playfair.jl
Normal file
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
Converts the given key-string to a Playfair key square.
|
||||
|
||||
Parameter `replacement` is a pair, such as ('I', 'J'), containing
|
||||
the two letters which are combined. Only the first of these letters will
|
||||
be present in the keysquare.
|
||||
"""
|
||||
function playfair_key_to_square(key::AbstractString, replacement)
|
||||
# make the key replacement
|
||||
key = encrypt_monoalphabetic(key, Dict(replacement[2] => replacement[1]))
|
||||
# delete duplicates etc from key
|
||||
key_sanitised = union(uppercase(letters_only(key)))
|
||||
# construct key square
|
||||
remaining = collect(filter(x -> (x != replacement[2] && findfirst(key_sanitised, x) == 0), 'A':'Z'))
|
||||
keysquare = transpose(reshape([key_sanitised; remaining], 5, 5))
|
||||
|
||||
keysquare
|
||||
end
|
||||
|
||||
function encrypt_playfair(plaintext, key::AbstractString; combined=('I','J'))
|
||||
keysquare = playfair_key_to_square(key, combined)
|
||||
|
||||
# make combinations in plaintext
|
||||
plaintext_sanitised = encrypt_monoalphabetic(plaintext, Dict(combined[2] => combined[1]))
|
||||
|
||||
encrypt_playfair(plaintext_sanitised, keysquare, combined=combined)
|
||||
end
|
||||
|
||||
"""
|
||||
Encrypts the given plaintext according to the Playfair cipher.
|
||||
Throws an error if the second entry in the `combined` tuple is present in the key.
|
||||
|
||||
Optional parameters:
|
||||
|
||||
stripped=false. When set to true, encrypt_playfair skips
|
||||
converting the plaintext to uppercase, removing punctuation, and
|
||||
combining characters which are to be combined in the key.
|
||||
combined=('I', 'J'), marks the characters which are to be combined in the text.
|
||||
Only the first of these two may be present in the output of encrypt_playfair.
|
||||
"""
|
||||
function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped=false, combined=('I', 'J'))
|
||||
if !stripped
|
||||
if findfirst(key, combined[2]) != 0
|
||||
error("Key must not contain symbol $(combined[2]), as it was specified to be combined.")
|
||||
end
|
||||
plaintext_sanitised = uppercase(letters_only(plaintext))
|
||||
plaintext_sanitised = encrypt_monoalphabetic(plaintext_sanitised, Dict(combined[2] => combined[1]))
|
||||
else
|
||||
plaintext_sanitised = plaintext
|
||||
end
|
||||
|
||||
# add X's as necessary to break up double letters
|
||||
if combined[2] != 'X'
|
||||
padding_char = 'X'
|
||||
else
|
||||
padding_char = combined[1]
|
||||
end
|
||||
if combined[2] != 'Z'
|
||||
backup_padding_char = 'Z'
|
||||
else
|
||||
backup_padding_char = combined[1]
|
||||
end
|
||||
|
||||
i = 1
|
||||
while i < length(plaintext_sanitised)
|
||||
if plaintext_sanitised[i] == plaintext_sanitised[i+1]
|
||||
if plaintext_sanitised[i] != padding_char
|
||||
plaintext_sanitised = plaintext_sanitised[1:i] * string(padding_char) * plaintext_sanitised[i+1:end]
|
||||
else
|
||||
plaintext_sanitised = plaintext_sanitised[1:i] * string(backup_padding_char) * plaintext_sanitised[i+1:end]
|
||||
end
|
||||
end
|
||||
i += 2
|
||||
end
|
||||
|
||||
if length(plaintext_sanitised) % 2 == 1
|
||||
if plaintext_sanitised[end] != padding_char
|
||||
plaintext_sanitised = plaintext_sanitised * string(padding_char)
|
||||
else
|
||||
plaintext_sanitised = plaintext_sanitised * string(backup_padding_char)
|
||||
end
|
||||
end
|
||||
|
||||
# start encrypting!
|
||||
ans = IOBuffer()
|
||||
|
||||
i = 1
|
||||
while i < length(plaintext_sanitised)
|
||||
l1 = plaintext_sanitised[i]
|
||||
l2 = plaintext_sanitised[i+1]
|
||||
|
||||
l1pos = ind2sub((5, 5), findfirst(key, l1))
|
||||
l2pos = ind2sub((5, 5), findfirst(key, l2))
|
||||
|
||||
@assert l1pos != l2pos
|
||||
|
||||
if l1pos[1] == l2pos[1]
|
||||
print(ans, key[l1pos[1], (((l1pos[2]+1 - 1) % 5) + 1)])
|
||||
print(ans, key[l2pos[1], (((l2pos[2]+1 - 1) % 5) + 1)])
|
||||
elseif l1pos[2] == l2pos[2]
|
||||
print(ans, key[((l1pos[1]+1 - 1) % 5)+1, l1pos[2]])
|
||||
print(ans, key[((l2pos[1]+1 - 1) % 5)+1, l2pos[2]])
|
||||
else
|
||||
|
||||
print(ans, key[l1pos[1], l2pos[2]])
|
||||
print(ans, key[l2pos[1], l1pos[2]])
|
||||
end
|
||||
|
||||
i += 2
|
||||
end
|
||||
|
||||
takebuf_string(ans)
|
||||
end
|
||||
|
||||
|
||||
function decrypt_playfair(ciphertext, key::AbstractString; combined=('I', 'J'))
|
||||
keysquare = playfair_key_to_square(key, combined)
|
||||
decrypt_playfair(ciphertext, keysquare, combined=combined)
|
||||
end
|
||||
|
||||
"""
|
||||
Decrypts the given ciphertext according to the Playfair cipher.
|
||||
|
||||
Does not attempt to delete X's inserted as padding for double letters.
|
||||
"""
|
||||
function decrypt_playfair(ciphertext, key::Array{Char, 2}; combined=('I', 'J'))
|
||||
# to obtain the decrypting keysquare, reverse every row and every column
|
||||
keysquare = mapslices(reverse, key, 2)
|
||||
keysquare = transpose(mapslices(reverse, transpose(keysquare), 2))
|
||||
lowercase(encrypt_playfair(ciphertext, keysquare, combined=combined))
|
||||
end
|
24
test/playfair.jl
Normal file
24
test/playfair.jl
Normal file
@@ -0,0 +1,24 @@
|
||||
using ClassicalCiphers
|
||||
using Base.Test
|
||||
|
||||
# Wikipedia example
|
||||
@test encrypt_playfair("Hide the gold in the tree stump", "playfair example") == "BMODZBXDNABEKUDMUIXMMOUVIF"
|
||||
|
||||
# Simon Singh Black Chamber example
|
||||
@test encrypt_playfair("meet me at hammersmith bridge tonight", "charles") == "GDDOGDRQARKYGDHDNKPRDAMSOGUPGKICQY"
|
||||
|
||||
# doc examples
|
||||
@test encrypt_playfair("Hello, World!", "playfair example") == "DMYRANVQCRGE"
|
||||
@test (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) == "DMYRANVQCRGE")
|
||||
|
||||
@test encrypt_playfair("IJXYZA", "PLAYFIREXM", combined=('I', 'J')) == "RMRMFWYE"
|
||||
@test encrypt_playfair("IJXYZA", "PLAYFIREXM", combined=('X', 'Z')) == "BSGXEY"
|
||||
|
||||
@test decrypt_playfair("BSGXEY", "PLAYFIREXM", combined=('X', 'Z')) == "ijxyxa"
|
||||
@test decrypt_playfair("RMRMFWYE", "PLAYFIREXM", combined=('I', 'J')) == "ixixyzax"
|
||||
@test decrypt_playfair("DMYRANVQCRGE", "playfair example") == "helxloworldx"
|
||||
@test (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'];
|
||||
decrypt_playfair("DMYRANVQCRGE", arr) == "helxloworldx")
|
||||
@test decrypt_playfair("GDDOGDRQARKYGDHDNKPRDAMSOGUPGKICQY", "charles") == "meetmeathamxmersmithbridgetonightx"
|
||||
@test decrypt_playfair("BMODZBXDNABEKUDMUIXMMOUVIF", "playfair example") == "hidethegoldinthetrexestump"
|
@@ -1,7 +1,7 @@
|
||||
using ClassicalCiphers
|
||||
|
||||
tests = ["vigenere", "monoalphabetic", "solitaire",
|
||||
"caesar", "portas", "affine", "hill"]
|
||||
"caesar", "portas", "affine", "hill", "playfair"]
|
||||
|
||||
println("Running tests:")
|
||||
|
||||
|
Reference in New Issue
Block a user