mirror of
https://github.com/Smaug123/ClassicalCiphers.jl
synced 2025-10-07 18:38:41 +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]
|
||||
* [Vigenère]
|
||||
* [Portas]
|
||||
* [Hill]
|
||||
* [Solitaire]
|
||||
|
||||
## Gotchas
|
||||
@@ -186,6 +187,52 @@ decrypt_portas("URYYB, JBEYQ!", "ab")
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
[Solitaire]: https://en.wikipedia.org/wiki/Solitaire_(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("portas.jl")
|
||||
include("affine.jl")
|
||||
include("hill.jl")
|
||||
|
||||
export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
|
||||
encrypt_caesar, decrypt_caesar, crack_caesar,
|
||||
@@ -16,6 +17,7 @@ export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
|
||||
encrypt_vigenere, decrypt_vigenere, crack_vigenere,
|
||||
encrypt_portas, decrypt_portas,
|
||||
encrypt_solitaire, decrypt_solitaire,
|
||||
encrypt_hill, decrypt_hill,
|
||||
string_fitness, index_of_coincidence
|
||||
|
||||
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
|
||||
|
||||
tests = ["vigenere", "monoalphabetic", "solitaire", "caesar", "portas", "affine"]
|
||||
tests = ["vigenere", "monoalphabetic", "solitaire",
|
||||
"caesar", "portas", "affine", "hill"]
|
||||
|
||||
println("Running tests:")
|
||||
|
||||
|
Reference in New Issue
Block a user