mirror of
https://github.com/Smaug123/ClassicalCiphers.jl
synced 2025-10-09 11:28: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`.
|
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
|
### Solitaire cipher
|
||||||
|
|
||||||
Encrypt the text "Hello, World!" with the Solitaire cipher, key "crypto":
|
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
|
# outputs "aaaaaaaaaaaaaaa", as per https://www.schneier.com/code/sol-test.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
[Caesar]: https://en.wikipedia.org/wiki/Caesar_cipher
|
[Caesar]: https://en.wikipedia.org/wiki/Caesar_cipher
|
||||||
[Vigenère]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
|
[Vigenère]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
|
||||||
[Monoalphabetic substitution]: https://en.wikipedia.org/wiki/Substitution_cipher
|
[Monoalphabetic substitution]: https://en.wikipedia.org/wiki/Substitution_cipher
|
||||||
|
@@ -10,8 +10,8 @@ include("solitaire.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,
|
||||||
encrypt_vigenere, decrypt_vigenere,
|
encrypt_vigenere, decrypt_vigenere, crack_vigenere,
|
||||||
encrypt_solitaire, decrypt_solitaire,
|
encrypt_solitaire, decrypt_solitaire,
|
||||||
string_fitness
|
string_fitness, index_of_coincidence
|
||||||
|
|
||||||
end # module
|
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)
|
Returns (plaintext, key::Integer), such that encrypt_caesar(plaintext, key)
|
||||||
would return ciphertext.
|
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.
|
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 = [(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]
|
texts[end]
|
||||||
end
|
end
|
||||||
|
@@ -95,4 +95,20 @@ function frequencies(input)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
ans
|
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
|
end
|
@@ -69,6 +69,9 @@ end
|
|||||||
"""
|
"""
|
||||||
crack_monoalphabetic cracks the given ciphertext which was encrypted by the monoalphabetic
|
crack_monoalphabetic cracks the given ciphertext which was encrypted by the monoalphabetic
|
||||||
substitution cipher.
|
substitution cipher.
|
||||||
|
|
||||||
|
Returns (the derived key, decrypted plaintext).
|
||||||
|
|
||||||
Possible arguments include:
|
Possible arguments include:
|
||||||
starting_key="", which when specified (for example, as "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
|
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
|
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"
|
# plaintext: string; key: string, so "ab" decrypts "ac" as "ab"
|
||||||
decrypt_vigenere(plaintext, [Int(i)-97 for i in lowercase(letters_only(key))])
|
decrypt_vigenere(plaintext, [Int(i)-97 for i in lowercase(letters_only(key))])
|
||||||
end
|
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