Add Vigenere cracking capability

This commit is contained in:
Smaug123
2016-01-05 14:17:14 +00:00
parent 8c64db1a07
commit 07c3070c7e
6 changed files with 78 additions and 4 deletions

View File

@@ -118,6 +118,19 @@ decrypt_vigenere("HFLMOXOSLE", [0, 1])
Notice that the offset `0` corresponds to the key `a`.
Crack a text:
```julia
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.
### Solitaire cipher
Encrypt the text "Hello, World!" with the Solitaire cipher, key "crypto":
@@ -134,6 +147,7 @@ decrypt_solitaire("EXKYI ZSGEH UNTIQ", collect(1:54))
# outputs "aaaaaaaaaaaaaaa", as per https://www.schneier.com/code/sol-test.txt
```
[Caesar]: https://en.wikipedia.org/wiki/Caesar_cipher
[Vigenère]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
[Monoalphabetic substitution]: https://en.wikipedia.org/wiki/Substitution_cipher

View File

@@ -10,8 +10,8 @@ include("solitaire.jl")
export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic,
encrypt_caesar, decrypt_caesar, crack_caesar,
encrypt_vigenere, decrypt_vigenere,
encrypt_vigenere, decrypt_vigenere, crack_vigenere,
encrypt_solitaire, decrypt_solitaire,
string_fitness
string_fitness, index_of_coincidence
end # module

View File

@@ -30,10 +30,17 @@ Cracks the given ciphertext according to the Caesar cipher.
Returns (plaintext, key::Integer), such that encrypt_caesar(plaintext, key)
would return ciphertext.
With cleverness=0, simply does the shift that maximises e's frequency.
With cleverness=1, maximises the string's total fitness.
Converts the input to lowercase.
"""
function crack_caesar(ciphertext)
function crack_caesar(ciphertext; cleverness=1)
texts = [(decrypt_caesar(ciphertext,key), key) for key in 1:26]
texts = sort(texts, by=(x -> string_fitness(first(x))))
if cleverness == 1
texts = sort(texts, by=(x -> string_fitness(first(x))))
else
texts = sort(texts, by=(x -> length(collect(filter(i -> (i == 'e'), first(x))))))
end
texts[end]
end

View File

@@ -95,4 +95,20 @@ function frequencies(input)
end
end
ans
end
"""
Finds the index of coincidence of the input string. Uppercase characters are considered to be
equal to their lowercase counterparts.
"""
function index_of_coincidence(input)
freqs = frequencies(lowercase(letters_only(input)))
len = length(lowercase(letters_only(input)))
ans = 0
for i in 'a':'z'
ans += (x -> x*(x-1))(get(freqs, i, 0))
end
ans /= (len * (len-1) / 26)
end

View File

@@ -69,6 +69,9 @@ end
"""
crack_monoalphabetic cracks the given ciphertext which was encrypted by the monoalphabetic
substitution cipher.
Returns (the derived key, decrypted plaintext).
Possible arguments include:
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

View File

@@ -36,3 +36,37 @@ function decrypt_vigenere(plaintext, key::AbstractString)
# plaintext: string; key: string, so "ab" decrypts "ac" as "ab"
decrypt_vigenere(plaintext, [Int(i)-97 for i in lowercase(letters_only(key))])
end
"""
Cracks the given text encrypted with the Vigenere cipher.
Returns (derived key, decrypted plaintext).
Optional parameters:
keylength=0: if the key length is known, specifying it may help the solver.
If 0, the solver will attempt to derive the key length using the index
of coincidence.
"""
function crack_vigenere(plaintext; keylength=0)
stripped_text = letters_only(lowercase(plaintext))
if keylength == 0
lens = sort(collect(2:15), by= len -> mean([index_of_coincidence(stripped_text[i:len:end]) for i in 1:len]))
keylength = lens[end]
end
everyother = [stripped_text[i:keylength:end] for i in 1:keylength]
decr = [crack_caesar(st)[1] for st in everyother]
ans = IOBuffer()
for i in 1:length(decr[1])
for j in 1:keylength
if i <= length(decr[j])
print(ans, decr[j][i])
end
end
end
derived_key = join([crack_caesar(st)[2] for st in everyother], "")
(derived_key, takebuf_string(ans))
end