mirror of
https://github.com/Smaug123/ClassicalCiphers.jl
synced 2025-10-06 09:58:41 +00:00
Add Vigenere cracking capability
This commit is contained in:
14
README.md
14
README.md
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
@@ -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
|
||||
|
@@ -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
|
Reference in New Issue
Block a user