mirror of
https://github.com/Smaug123/ClassicalCiphers.jl
synced 2025-10-08 02:48:45 +00:00
Merge pull request #6 from Smaug123/pull-request/d6813fa1
Add Hill encrypt/decrypt
This commit is contained in:
48
README.md
48
README.md
@@ -14,6 +14,7 @@ The Solitaire cipher is included for completeness, though it is perhaps not stri
|
|||||||
* [Monoalphabetic substitution]
|
* [Monoalphabetic substitution]
|
||||||
* [Vigenère]
|
* [Vigenère]
|
||||||
* [Portas]
|
* [Portas]
|
||||||
|
* [Hill]
|
||||||
* [Solitaire]
|
* [Solitaire]
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
@@ -186,6 +187,52 @@ decrypt_portas("URYYB, JBEYQ!", "ab")
|
|||||||
|
|
||||||
Notice that the input has been made lowercase.
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
### Solitaire cipher
|
### Solitaire cipher
|
||||||
|
|
||||||
Encrypt the text "Hello, World!" with the Solitaire cipher, key "crypto":
|
Encrypt the text "Hello, World!" with the Solitaire cipher, key "crypto":
|
||||||
@@ -208,3 +255,4 @@ decrypt_solitaire("EXKYI ZSGEH UNTIQ", collect(1:54))
|
|||||||
[Monoalphabetic substitution]: https://en.wikipedia.org/wiki/Substitution_cipher
|
[Monoalphabetic substitution]: https://en.wikipedia.org/wiki/Substitution_cipher
|
||||||
[Solitaire]: https://en.wikipedia.org/wiki/Solitaire_(cipher)
|
[Solitaire]: https://en.wikipedia.org/wiki/Solitaire_(cipher)
|
||||||
[Portas]: http://practicalcryptography.com/ciphers/porta-cipher/
|
[Portas]: http://practicalcryptography.com/ciphers/porta-cipher/
|
||||||
|
[Hill]: https://en.wikipedia.org/wiki/Hill_cipher
|
||||||
|
@@ -9,6 +9,7 @@ include("vigenere.jl")
|
|||||||
include("solitaire.jl")
|
include("solitaire.jl")
|
||||||
include("portas.jl")
|
include("portas.jl")
|
||||||
include("affine.jl")
|
include("affine.jl")
|
||||||
|
include("hill.jl")
|
||||||
|
|
||||||
export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
|
export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
|
||||||
encrypt_caesar, decrypt_caesar, crack_caesar,
|
encrypt_caesar, decrypt_caesar, crack_caesar,
|
||||||
@@ -16,6 +17,7 @@ export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
|
|||||||
encrypt_vigenere, decrypt_vigenere, crack_vigenere,
|
encrypt_vigenere, decrypt_vigenere, crack_vigenere,
|
||||||
encrypt_portas, decrypt_portas,
|
encrypt_portas, decrypt_portas,
|
||||||
encrypt_solitaire, decrypt_solitaire,
|
encrypt_solitaire, decrypt_solitaire,
|
||||||
|
encrypt_hill, decrypt_hill,
|
||||||
string_fitness, index_of_coincidence
|
string_fitness, index_of_coincidence
|
||||||
|
|
||||||
end # module
|
end # module
|
||||||
|
108
src/hill.jl
Normal file
108
src/hill.jl
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using Iterators
|
||||||
|
|
||||||
|
"""
|
||||||
|
Encrypts the given plaintext according to the Hill cipher.
|
||||||
|
The key may be given as a matrix (that is, two-dimensional Array{Int}) or as a string.
|
||||||
|
|
||||||
|
If the key is given as a string, the string is converted to uppercase before use, and
|
||||||
|
symbols are removed. It is assumed to be of square integer length, and the matrix entries
|
||||||
|
are filled top-left to top-right, then next-top left to next-top right, and so on down
|
||||||
|
to bottom-left to bottom-right. If the string is not of square integer length, an error
|
||||||
|
is thrown.
|
||||||
|
|
||||||
|
The matrix must be invertible modulo 26. If it is not, an error is thrown.
|
||||||
|
"""
|
||||||
|
function encrypt_hill{I <: Integer}(plaintext, key::Array{I, 2})
|
||||||
|
if round(Integer, det(key)) % 26 == 0
|
||||||
|
error("Key must be invertible mod 26.")
|
||||||
|
end
|
||||||
|
|
||||||
|
keysize = size(key, 1)
|
||||||
|
text = uppercase(letters_only(plaintext))
|
||||||
|
|
||||||
|
# split the plaintext up into that-size chunks
|
||||||
|
|
||||||
|
# pad if necessary
|
||||||
|
if length(text) % keysize != 0
|
||||||
|
text = text * ("X"^(keysize - (length(text) % keysize)))
|
||||||
|
end
|
||||||
|
chars = [Int(ch[1]) - 65 for ch in split(text, "")]
|
||||||
|
# split
|
||||||
|
split_text = reshape(chars, (keysize, div(length(text), keysize)))
|
||||||
|
|
||||||
|
encrypted = mapslices(group -> 65 .+ ((key * group) .% 26), split_text, 1)
|
||||||
|
|
||||||
|
ans = IOBuffer()
|
||||||
|
for group in encrypted
|
||||||
|
for x in group
|
||||||
|
print(ans, Char(x))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
takebuf_string(ans)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function encrypt_hill(plaintext, key::AbstractString)
|
||||||
|
|
||||||
|
if round(Integer, sqrt(length(key))) != sqrt(length(key))
|
||||||
|
error("Hill key must be of square integer size.")
|
||||||
|
end
|
||||||
|
|
||||||
|
matrix_dim = round(Integer, sqrt(length(key)))
|
||||||
|
key_string = uppercase(letters_only(key))
|
||||||
|
|
||||||
|
key_matrix = Array{Integer}(matrix_dim, matrix_dim)
|
||||||
|
for i in 1:length(key)
|
||||||
|
key_matrix[ind2sub([matrix_dim, matrix_dim], i)...] = Int(key_string[i]) - 65
|
||||||
|
end
|
||||||
|
|
||||||
|
encrypt_hill(plaintext, transpose(key_matrix))
|
||||||
|
end
|
||||||
|
|
||||||
|
function minor{I <: Integer}(mat::Array{I, 2}, i, j)
|
||||||
|
d = det(mat[[1:i-1; i+1:end], [1:j-1; j+1:end]])
|
||||||
|
round(Integer, d)
|
||||||
|
end
|
||||||
|
|
||||||
|
"""
|
||||||
|
Computes the adjugate matrix for given matrix.
|
||||||
|
"""
|
||||||
|
function adjugate{I <: Integer}(mat::Array{I, 2})
|
||||||
|
arr = [(-1)^(i+j) * minor(mat, i, j) for (i, j) in product(1:size(mat, 1), 1:size(mat, 2))]
|
||||||
|
ans = reshape(arr, size(mat))
|
||||||
|
Array{Integer, 2}(transpose(ans))
|
||||||
|
end
|
||||||
|
|
||||||
|
function decrypt_hill{I <: Integer}(ciphertext, key::Array{I, 2})
|
||||||
|
if ndims(key) != 2
|
||||||
|
error("Key must be a two-dimensional matrix.")
|
||||||
|
end
|
||||||
|
if round(Integer, det(key)) % 26 == 0
|
||||||
|
error("Key must be invertible mod 26.")
|
||||||
|
end
|
||||||
|
|
||||||
|
# invert the input array mod 26
|
||||||
|
inv = (adjugate(key) .% 26)
|
||||||
|
inv = invmod(round(Integer, det(key)), 26) .* inv
|
||||||
|
inv = inv .% 26
|
||||||
|
inv = (inv .+ (26 * 26)) .% 26
|
||||||
|
|
||||||
|
lowercase(encrypt_hill(ciphertext, inv))
|
||||||
|
end
|
||||||
|
|
||||||
|
function decrypt_hill(ciphertext, key::AbstractString)
|
||||||
|
|
||||||
|
if round(Integer, sqrt(length(key))) != sqrt(length(key))
|
||||||
|
error("Hill key must be of square integer size.")
|
||||||
|
end
|
||||||
|
|
||||||
|
matrix_dim = round(Integer, sqrt(length(key)))
|
||||||
|
key_string = uppercase(letters_only(key))
|
||||||
|
|
||||||
|
key_matrix = Array{Integer}(matrix_dim, matrix_dim)
|
||||||
|
for i in 1:length(key)
|
||||||
|
key_matrix[ind2sub([matrix_dim, matrix_dim], i)...] = Int(key_string[i]) - 65
|
||||||
|
end
|
||||||
|
|
||||||
|
decrypt_hill(ciphertext, transpose(key_matrix))
|
||||||
|
end
|
28
test/hill.jl
Normal file
28
test/hill.jl
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using ClassicalCiphers
|
||||||
|
using Base.Test
|
||||||
|
|
||||||
|
# Wikipedia examples
|
||||||
|
|
||||||
|
@test encrypt_hill("help!", [3 3; 2 5]) == "HIAT"
|
||||||
|
@test decrypt_hill("hiat", [3 3; 2 5]) == "help"
|
||||||
|
|
||||||
|
@test encrypt_hill("act", "GYBNQKURP") == "POH"
|
||||||
|
@test decrypt_hill("POH", "GYBNQKURP") == "act"
|
||||||
|
@test encrypt_hill("cat", "GYBNQKURP") == "FIN"
|
||||||
|
@test decrypt_hill("fin", "GYBNQKURP") == "cat"
|
||||||
|
|
||||||
|
# doc examples
|
||||||
|
|
||||||
|
@test encrypt_hill("Hello, World!", [1 2; 5 7]) == "PLHCGQWHRY"
|
||||||
|
@test encrypt_hill("Hello, World!", "bcfh") == "PLHCGQWHRY"
|
||||||
|
@test encrypt_hill("Hello", "bcfh") == "PLHCIX"
|
||||||
|
@test decrypt_hill("PLHCIX", "bcfh") == "hellox"
|
||||||
|
@test decrypt_hill("PLHCGQWHRY", [1 2; 5 7]) == "helloworld"
|
||||||
|
@test decrypt_hill("PLHCGQWHRY", "bcfh") == "helloworld"
|
||||||
|
|
||||||
|
# Practical Cryptography examples
|
||||||
|
|
||||||
|
@test encrypt_hill("att", [2 4 5; 9 2 1; 3 17 7]) == "PFO"
|
||||||
|
|
||||||
|
@test encrypt_hill("the gold is buried in orono", [5 17; 4 15]) == uppercase("gzscxnvcdjzxeovcrclsrc")
|
||||||
|
@test decrypt_hill("gzscxnvcdjzxeovcrclsrc", [5 17; 4 15]) == "thegoldisburiedinorono"
|
@@ -1,6 +1,7 @@
|
|||||||
using ClassicalCiphers
|
using ClassicalCiphers
|
||||||
|
|
||||||
tests = ["vigenere", "monoalphabetic", "solitaire", "caesar", "portas", "affine"]
|
tests = ["vigenere", "monoalphabetic", "solitaire",
|
||||||
|
"caesar", "portas", "affine", "hill"]
|
||||||
|
|
||||||
println("Running tests:")
|
println("Running tests:")
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user