mirror of
https://github.com/Smaug123/ClassicalCiphers.jl
synced 2025-10-05 09:28:44 +00:00
Refined code style to be more in line with Blue Code Style guide
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
[](https://travis-ci.org/Smaug123/ClassicalCiphers.jl)
|
||||
|
||||
[](https://coveralls.io/github/Smaug123/ClassicalCiphers.jl?branch=master)
|
||||
[](https://travis-ci.org/Smaug123/ClassicalCiphers.jl) [](https://coveralls.io/github/Smaug123/ClassicalCiphers.jl?branch=master) [](https://github.com/invenia/BlueStyle)
|
||||
|
||||
# ClassicalCiphers
|
||||
|
||||
@@ -185,7 +183,7 @@ are stripped out before use.
|
||||
Decrypt the same text:
|
||||
|
||||
```julia
|
||||
decrypt_portas("URYYB, JBEYQ!", "ab")
|
||||
decrypt_portas("URYYB, JBEYQ!", "ab")
|
||||
# outputs "hello, world!"
|
||||
```
|
||||
|
||||
|
@@ -16,7 +16,7 @@ function encrypt_affine(plaintext, mult::T, add::T; offset::T = 0) where {T <: I
|
||||
end
|
||||
|
||||
keystr = join(Char[Char(((mult * i + add) % 26) + 97 - offset) for i in offset:(25 + offset)])
|
||||
encrypt_monoalphabetic(plaintext, keystr)
|
||||
return encrypt_monoalphabetic(plaintext, keystr)
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -37,7 +37,7 @@ function decrypt_affine(ciphertext, mult::T, add::T; offset=0) where {T <: Integ
|
||||
end
|
||||
|
||||
keystr = join(Char[Char(((mult*i + add) % 26) + 97 - offset) for i in offset:25+offset], "")
|
||||
decrypt_monoalphabetic(ciphertext, keystr)
|
||||
return decrypt_monoalphabetic(ciphertext, keystr)
|
||||
end
|
||||
|
||||
function max_by(arr::AbstractArray, f::Function)
|
||||
@@ -57,7 +57,7 @@ function max_by(arr::AbstractArray, f::Function)
|
||||
end
|
||||
end
|
||||
end
|
||||
currAns
|
||||
return currAns
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -72,8 +72,7 @@ add=-1, which specifies the additive constant if known.
|
||||
function crack_affine(ciphertext; mult::T = 0, add::T = -1) where {T <: Integer}
|
||||
mults = mult != 0 ? Int[mult] : Int[i for i in filter(x -> (x % 2 != 0 && x % 13 != 0), 1:25)]
|
||||
adds = add != -1 ? Int[add] : (0:25)
|
||||
|
||||
possible_keys = Iterators.product(mults, adds)
|
||||
|
||||
reverse(max_by(Tuple{eltype(possible_keys), AbstractString}[(i, decrypt_affine(ciphertext, i[1], i[2])) for i in possible_keys], x -> string_fitness(x[2])))
|
||||
|
||||
return reverse(max_by(Tuple{eltype(possible_keys), AbstractString}[(i, decrypt_affine(ciphertext, i[1], i[2])) for i in possible_keys], x -> string_fitness(x[2])))
|
||||
end
|
||||
|
@@ -6,10 +6,10 @@ so encrypt_caesar("abc", 1) == "BCD".
|
||||
Converts the input to uppercase.
|
||||
"""
|
||||
function encrypt_caesar(plaintext, key::T) where {T <: Integer}
|
||||
# plaintext: string; key: integer offset, so k=1 sends "a" to "b"
|
||||
key = ((key - 1) % 26) + 1
|
||||
keystr = join(vcat(collect(Char(97 + key):'z'), collect('a':Char(97 + key - 1))))
|
||||
encrypt_monoalphabetic(plaintext, keystr)
|
||||
# plaintext: string; key: integer offset, so k=1 sends "a" to "b"
|
||||
key = ((key - 1) % 26) + 1
|
||||
keystr = join(vcat(collect(Char(97 + key):'z'), collect('a':Char(97 + key - 1))))
|
||||
return encrypt_monoalphabetic(plaintext, keystr)
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -20,9 +20,9 @@ so decrypt_caesar("abcd", 1) == "zabc".
|
||||
Converts the input to lowercase.
|
||||
"""
|
||||
function decrypt_caesar(ciphertext, key::T) where {T <: Integer}
|
||||
# ciphertext: string; key: integer offset, so k=1 decrypts "B" as "a"
|
||||
key = ((key - 1) % 26) + 1
|
||||
lowercase(encrypt_caesar(ciphertext, 26 - key))
|
||||
# ciphertext: string; key: integer offset, so k=1 decrypts "B" as "a"
|
||||
key = ((key - 1) % 26) + 1
|
||||
return lowercase(encrypt_caesar(ciphertext, 26 - key))
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -36,11 +36,12 @@ With cleverness=1, maximises the string's total fitness.
|
||||
Converts the input to lowercase.
|
||||
"""
|
||||
function crack_caesar(ciphertext; cleverness::T = 1) where {T <: Integer}
|
||||
texts = Tuple{String, Int}[(decrypt_caesar(ciphertext,key), key) for key in 0:25]
|
||||
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 = Tuple{String, Int}[(decrypt_caesar(ciphertext,key), key) for key in 0:25]
|
||||
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
|
||||
|
||||
return texts[end]
|
||||
end
|
||||
|
159
src/common.jl
159
src/common.jl
@@ -1,62 +1,63 @@
|
||||
function letters_only(text::AbstractString)
|
||||
# text: string; removes all non-alphabetic characters
|
||||
filter(x -> ('A' <= x <= 'Z' || 'a' <= x <= 'z'), text)
|
||||
# text: string; removes all non-alphabetic characters
|
||||
return filter(x -> ('A' <= x <= 'Z' || 'a' <= x <= 'z'), text)
|
||||
end
|
||||
|
||||
function rotateRight(arr::AbstractVector, n::T) where {T <: Integer}
|
||||
# implementation of the Mathematica function RotateRight - or you could try circshift()?
|
||||
ans = copy(arr)
|
||||
for i in 1:length(arr)
|
||||
ans[i] = arr[((2*length(ans)+i-n-1) % length(ans)) + 1]
|
||||
end
|
||||
ans
|
||||
end
|
||||
|
||||
function rotateLeft(arr::AbstractVector, n::T) where {T <: Integer}
|
||||
# implementation of the Mathematica function RotateLeft
|
||||
ans = copy(arr)
|
||||
for i in 1:length(arr)
|
||||
ans[i] = arr[((i + n - 1) % length(ans)) + 1]
|
||||
end
|
||||
ans
|
||||
end
|
||||
|
||||
function rotateLeftStr(st::AbstractString, n::T) where {T <: Integer}
|
||||
join(rotateLeft(collect(st), n))
|
||||
end
|
||||
|
||||
function rotateRightStr(st::AbstractString, n::T) where {T <: Integer}
|
||||
join(rotateRight(collect(st), n))
|
||||
end
|
||||
|
||||
function splitBy(arr::AbstractVector, func::Function)
|
||||
# implementation of the Mathematica function SplitBy
|
||||
# splits the array into sublists so that each list has the same value of func
|
||||
# on its elements
|
||||
arrinternal = map(func, arr)
|
||||
currans = Vector{Integer}[[arr[1]]]
|
||||
for i in 2:length(arr)
|
||||
if arrinternal[i] != arrinternal[i - 1]
|
||||
append!(currans, Vector{Integer}[[arr[i]]])
|
||||
else
|
||||
append!(currans[end], [arr[i]])
|
||||
function rotate_right(arr::AbstractVector, n::T) where {T <: Integer}
|
||||
# implementation of the Mathematica function rotate_right - or you could try circshift()?
|
||||
ans = copy(arr)
|
||||
for i in 1:length(arr)
|
||||
ans[i] = arr[((2*length(ans)+i-n-1) % length(ans)) + 1]
|
||||
end
|
||||
end
|
||||
currans
|
||||
|
||||
return ans
|
||||
end
|
||||
|
||||
function rotate_left(arr::AbstractVector, n::T) where {T <: Integer}
|
||||
# implementation of the Mathematica function rotate_left
|
||||
ans = copy(arr)
|
||||
for i in 1:length(arr)
|
||||
ans[i] = arr[((i + n - 1) % length(ans)) + 1]
|
||||
end
|
||||
|
||||
return ans
|
||||
end
|
||||
|
||||
rotate_left_str(st::AbstractString, n::T) where {T <: Integer} =
|
||||
join(rotate_left(collect(st), n))
|
||||
rotate_right_str(st::AbstractString, n::T) where {T <: Integer} =
|
||||
join(rotate_right(collect(st), n))
|
||||
|
||||
function split_by(arr::AbstractVector, func::Function)
|
||||
# implementation of the Mathematica function split_by
|
||||
# splits the array into sublists so that each list has the same value of func
|
||||
# on its elements
|
||||
arrinternal = map(func, arr)
|
||||
currans = Vector{Integer}[[arr[1]]]
|
||||
for i in 2:length(arr)
|
||||
if arrinternal[i] != arrinternal[i - 1]
|
||||
append!(currans, Vector{Integer}[[arr[i]]])
|
||||
else
|
||||
append!(currans[end], [arr[i]])
|
||||
end
|
||||
end
|
||||
|
||||
return currans
|
||||
end
|
||||
|
||||
function get_trigram_fitnesses(datafile::AbstractString)
|
||||
# The quadgrams file we use is licensed MIT, as per
|
||||
# http://practicalcryptography.com/cryptanalysis/text-characterisation/quadgrams/#comment-2007984751
|
||||
dict = Dict{AbstractString, Int32}()
|
||||
|
||||
open(datafile) do io
|
||||
while ! eof(io)
|
||||
(ngram, fitness) = split(readline(io))
|
||||
dict[ngram] = parse(Int32, fitness)
|
||||
# The quadgrams file we use is licensed MIT, as per
|
||||
# http://practicalcryptography.com/cryptanalysis/text-characterisation/quadgrams/#comment-2007984751
|
||||
dict = Dict{AbstractString, Int32}()
|
||||
|
||||
open(datafile) do io
|
||||
while ! eof(io)
|
||||
(ngram, fitness) = split(readline(io))
|
||||
dict[ngram] = parse(Int32, fitness)
|
||||
end
|
||||
end
|
||||
end
|
||||
dict
|
||||
|
||||
return dict
|
||||
end
|
||||
|
||||
trigram_fitnesses = get_trigram_fitnesses(joinpath(dirname(Base.source_path()), "english_trigrams.txt"))
|
||||
@@ -67,20 +68,20 @@ is to English. That is, splits the input string into groups of three letters,
|
||||
and assigns a score based on the frequency of the trigrams in true English.
|
||||
"""
|
||||
function string_fitness(input::AbstractString; alreadystripped::Bool = false)
|
||||
if !alreadystripped
|
||||
str = letters_only(input)
|
||||
else
|
||||
str = input
|
||||
end
|
||||
if alreadystripped
|
||||
str = input
|
||||
else
|
||||
str = letters_only(input)
|
||||
end
|
||||
|
||||
str = uppercase(str)
|
||||
str = uppercase(str)
|
||||
|
||||
ans = 0
|
||||
for i in 1:(length(str)-2)
|
||||
ans += get(trigram_fitnesses, str[i:(i + 2)], 0)
|
||||
end
|
||||
ans = 0
|
||||
for i in 1:(length(str) - 2)
|
||||
ans += get(trigram_fitnesses, str[i:(i + 2)], 0)
|
||||
end
|
||||
|
||||
log(ans / length(str))
|
||||
return log(ans / length(str))
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -88,16 +89,16 @@ Finds the frequencies of all characters in the input string, returning a Dict
|
||||
of 'a' => 4, for instance. Uppercase characters are considered distinct from lowercase.
|
||||
"""
|
||||
function frequencies(input::AbstractString)
|
||||
ans = Dict{Char, Int}()
|
||||
for c in input
|
||||
index = Base.ht_keyindex2!(ans, c)
|
||||
if index > 0
|
||||
@inbounds ans.vals[index] += 1
|
||||
else
|
||||
@inbounds Base._setindex!(ans, 0, c, -index)
|
||||
end
|
||||
end
|
||||
ans
|
||||
ans = Dict{Char, Int}()
|
||||
for c in input
|
||||
index = Base.ht_keyindex2!(ans, c)
|
||||
if index > 0
|
||||
@inbounds ans.vals[index] += 1
|
||||
else
|
||||
@inbounds Base._setindex!(ans, 0, c, -index)
|
||||
end
|
||||
end
|
||||
return ans
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -105,13 +106,13 @@ Finds the index of coincidence of the input string. Uppercase characters are con
|
||||
equal to their lowercase counterparts.
|
||||
"""
|
||||
function index_of_coincidence(input::AbstractString)
|
||||
freqs = frequencies(lowercase(letters_only(input)))
|
||||
len = length(lowercase(letters_only(input)))
|
||||
freqs = frequencies(lowercase(letters_only(input)))
|
||||
len = length(lowercase(letters_only(input)))
|
||||
|
||||
ans = 0
|
||||
for i in 'a':'z'
|
||||
ans += map(x -> x * (x - 1), get(freqs, i, 0))
|
||||
end
|
||||
ans = 0
|
||||
for i in 'a':'z'
|
||||
ans += map(x -> x * (x - 1), get(freqs, i, 0))
|
||||
end
|
||||
|
||||
ans /= (len * (len - 1) / 26)
|
||||
return ans /= (len * (len - 1) / 26)
|
||||
end
|
||||
|
@@ -15,7 +15,7 @@ function parse_stecker(stecker::AbstractString)
|
||||
sp = collect(stecker)
|
||||
steck_parsed = Tuple{Char, Char}[(sp[i][1], sp[i + 1][1]) for i in 1:2:length(sp)]
|
||||
end
|
||||
steck_parsed
|
||||
return steck_parsed
|
||||
end
|
||||
|
||||
function parse_stecker(stecker::Array{Tuple{Char, Char}})
|
||||
@@ -24,6 +24,7 @@ function parse_stecker(stecker::Array{Tuple{Char, Char}})
|
||||
else
|
||||
return stecker
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
function parse_reflector(reflector::Char)
|
||||
@@ -36,6 +37,7 @@ function parse_reflector(reflector::Char)
|
||||
else
|
||||
error("Reflector $(reflector) unrecognised.")
|
||||
end
|
||||
return nothing
|
||||
end
|
||||
|
||||
function parse_reflector(reflector::AbstractString)
|
||||
@@ -49,7 +51,7 @@ function parse_reflector(reflector::AbstractString)
|
||||
error("Reflector must not contain any character used more than once.")
|
||||
end
|
||||
|
||||
ans
|
||||
return ans
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -238,9 +240,8 @@ function encrypt_enigma(plaintext,
|
||||
print(ans, working_ch)
|
||||
end
|
||||
|
||||
uppercase(String(take!(ans)))
|
||||
return uppercase(String(take!(ans)))
|
||||
end
|
||||
|
||||
function decrypt_enigma(args1...; args2...)
|
||||
decrypt_enigma(args1...; args2...) =
|
||||
lowercase(encrypt_enigma(args1...; args2...))
|
||||
end
|
||||
|
13
src/hill.jl
13
src/hill.jl
@@ -36,7 +36,8 @@ function encrypt_hill(plaintext::AbstractString, key::AbstractArray{T, 2}) where
|
||||
print(ans, Char(x))
|
||||
end
|
||||
end
|
||||
String(take!(ans))
|
||||
|
||||
return String(take!(ans))
|
||||
end
|
||||
|
||||
|
||||
@@ -50,12 +51,12 @@ function encrypt_hill(plaintext::AbstractString, key::AbstractString)
|
||||
|
||||
key_matrix = reshape(keys, matrix_dim, matrix_dim)
|
||||
|
||||
encrypt_hill(plaintext, transpose(key_matrix))
|
||||
return encrypt_hill(plaintext, transpose(key_matrix))
|
||||
end
|
||||
|
||||
function minor(mat::AbstractArray{T, 2}, i::K, j::K) where {T <: Integer, K <: Integer}
|
||||
d = det(mat[vcat(1:(i - 1), (i + 1):end), vcat(1:(j - 1), (j + 1):end)])
|
||||
round(Integer, d)
|
||||
return round(Integer, d)
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -64,7 +65,7 @@ Computes the adjugate matrix for given matrix.
|
||||
function adjugate(mat::AbstractArray{T, 2}) where {T <: Integer}
|
||||
arr = Integer[(-1)^(i+j) * minor(mat, i, j) for (i, j) in Iterators.product(1:size(mat, 1), 1:size(mat, 2))]
|
||||
ans = reshape(arr, size(mat))
|
||||
Array{Integer, 2}(transpose(ans))
|
||||
return Array{Integer, 2}(transpose(ans))
|
||||
end
|
||||
|
||||
function decrypt_hill(ciphertext, key::AbstractArray{T, 2}) where {T<:Integer}
|
||||
@@ -81,7 +82,7 @@ function decrypt_hill(ciphertext, key::AbstractArray{T, 2}) where {T<:Integer}
|
||||
inverse_mat = inverse_mat .% 26
|
||||
inverse_mat = (inverse_mat .+ (26 * 26)) .% 26
|
||||
|
||||
lowercase(encrypt_hill(ciphertext, inverse_mat))
|
||||
return lowercase(encrypt_hill(ciphertext, inverse_mat))
|
||||
end
|
||||
|
||||
function decrypt_hill(ciphertext, key::AbstractString)
|
||||
@@ -93,5 +94,5 @@ function decrypt_hill(ciphertext, key::AbstractString)
|
||||
keys = map(x -> Int(x) - 65, collect(uppercase(letters_only(key))))
|
||||
key_matrix = reshape(keys, matrix_dim, matrix_dim)
|
||||
|
||||
decrypt_hill(ciphertext, transpose(key_matrix))
|
||||
return decrypt_hill(ciphertext, transpose(key_matrix))
|
||||
end
|
||||
|
@@ -1,5 +1,5 @@
|
||||
function keystr_to_dict(keystr::AbstractString)
|
||||
Dict{Char, Char}(map(x -> (Char(x[1] + 64), x[2]), enumerate(uppercase(keystr))))
|
||||
return Dict{Char, Char}(map(x -> (Char(x[1] + 64), x[2]), enumerate(uppercase(keystr))))
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -14,8 +14,8 @@ If the key is given as a Dict, the only substitutions made are those in the Dict
|
||||
in particular, the string is not converted to lowercase automatically.
|
||||
"""
|
||||
function encrypt_monoalphabetic(plaintext, key::Dict{Char, Char})
|
||||
# plaintext: string; key: dictionary of {'a' => 'b'}, etc, for replacing 'a' with 'b'
|
||||
join(eltype(keys(key))[get(key, i, i) for i in plaintext])
|
||||
# plaintext: string; key: dictionary of {'a' => 'b'}, etc, for replacing 'a' with 'b'
|
||||
return join(eltype(keys(key))[get(key, i, i) for i in plaintext])
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -30,25 +30,25 @@ If the key is given as a Dict, the only substitutions made are those in the Dict
|
||||
in particular, the string is not converted to lowercase automatically.
|
||||
"""
|
||||
function decrypt_monoalphabetic(ciphertext, key::Dict{Char, Char})
|
||||
# ciphertext: string; key: dictionary of {'a' => 'b'}, etc, where the plaintext 'a' was
|
||||
# replaced by ciphertext 'b'. No character should appear more than once
|
||||
# as a value in {key}.
|
||||
encrypt_monoalphabetic(ciphertext, Dict{Char, Char}([reverse(a) for a in key]))
|
||||
# ciphertext: string; key: dictionary of {'a' => 'b'}, etc, where the plaintext 'a' was
|
||||
# replaced by ciphertext 'b'. No character should appear more than once
|
||||
# as a value in {key}.
|
||||
return encrypt_monoalphabetic(ciphertext, Dict{Char, Char}([reverse(a) for a in key]))
|
||||
end
|
||||
|
||||
function encrypt_monoalphabetic(plaintext, key::AbstractString)
|
||||
# plaintext: string; key: string of length 26, first character is the image of 'a', etc
|
||||
# working in lowercase; key is assumed only to have each element appearing once
|
||||
encrypt_monoalphabetic(uppercase(plaintext), keystr_to_dict(key))
|
||||
# plaintext: string; key: string of length 26, first character is the image of 'a', etc
|
||||
# working in lowercase; key is assumed only to have each element appearing once
|
||||
return encrypt_monoalphabetic(uppercase(plaintext), keystr_to_dict(key))
|
||||
end
|
||||
|
||||
function decrypt_monoalphabetic(ciphertext, key::AbstractString)
|
||||
# ciphertext: string; key: string of length 26, first character is the image of 'a', etc
|
||||
# working in lowercase; key is assumed only to have each element appearing once
|
||||
# and to be in lowercase
|
||||
# so decrypt_monoalphabetic("cb", "cbade…") is "ab"
|
||||
dict = Dict{Char, Char}(a => Char(96 + findfirst(i -> i == a, lowercase(key))) for a in lowercase(key))
|
||||
encrypt_monoalphabetic(lowercase(ciphertext), dict)
|
||||
# ciphertext: string; key: string of length 26, first character is the image of 'a', etc
|
||||
# working in lowercase; key is assumed only to have each element appearing once
|
||||
# and to be in lowercase
|
||||
# so decrypt_monoalphabetic("cb", "cbade…") is "ab"
|
||||
dict = Dict{Char, Char}(a => Char(96 + findfirst(i -> i == a, lowercase(key))) for a in lowercase(key))
|
||||
return encrypt_monoalphabetic(lowercase(ciphertext), dict)
|
||||
end
|
||||
|
||||
# Cracking
|
||||
@@ -61,12 +61,12 @@ The characters are guaranteed to be at different positions, though "aa" would be
|
||||
'swapped' to "aa".
|
||||
"""
|
||||
function swap_two(str)
|
||||
indices = rand(1:length(str), 2)
|
||||
while indices[1] == indices[2]
|
||||
indices = rand(1:length(str), 2)
|
||||
end
|
||||
while indices[1] == indices[2]
|
||||
indices = rand(1:length(str), 2)
|
||||
end
|
||||
|
||||
join(Integer[i == indices[1] ? str[indices[2]] : (i == indices[2] ? str[indices[1]] : str[i]) for i in 1:length(str)])
|
||||
return join(Integer[i == indices[1] ? str[indices[2]] : (i == indices[2] ? str[indices[1]] : str[i]) for i in 1:length(str)])
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -77,98 +77,103 @@ 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
|
||||
common characters being decrypted to the most common English characters.
|
||||
starts the simulation at the given key. The default causes it to start with the most
|
||||
common characters being decrypted to the most common English characters.
|
||||
min_temp=0.0001, which is the temperature at which we stop the simulation.
|
||||
temp_factor=0.97, which is the factor by which the temperature decreases each step.
|
||||
chatty=0, which can be set to 1 to print whenever the key is updated, or 2 to print
|
||||
whenever any new key is considered.
|
||||
whenever any new key is considered.
|
||||
rounds=1, which sets the number of repetitions we perform. Each round starts with the
|
||||
best key we've found so far.
|
||||
best key we've found so far.
|
||||
acceptance_prob=((e, ep, t) -> ep>e ? 1 : exp(-(e-ep)/t)), which is the probability
|
||||
with which we accept new key of fitness ep, given that the current key has fitness e,
|
||||
at temperature t.
|
||||
with which we accept new key of fitness ep, given that the current key has fitness e,
|
||||
at temperature t.
|
||||
"""
|
||||
function crack_monoalphabetic(ciphertext; starting_key::AbstractString = "",
|
||||
min_temp::F = 0.0001, temp_factor::F = 0.97,
|
||||
acceptance_prob::F = ((e,ep,t) -> ep > e ? 1. : exp(-(e-ep)/t)),
|
||||
chatty::T = 0,
|
||||
rounds::T = 1) where {T <: Integer, F <: AbstractFloat}
|
||||
function crack_monoalphabetic(
|
||||
ciphertext;
|
||||
starting_key::AbstractString = "",
|
||||
min_temp::F = 0.0001,
|
||||
temp_factor::F = 0.97,
|
||||
acceptance_prob::F = ((e,ep,t) -> ep > e ? 1. : exp(-(e-ep)/t)),
|
||||
chatty::T = 0,
|
||||
rounds::T = 1
|
||||
) where {T <: Integer, F <: AbstractFloat}
|
||||
|
||||
if isempty(starting_key)
|
||||
# most common letters
|
||||
commonest = "ETAOINSHRDLUMCYWFGBPVKZJXQ"
|
||||
freqs = frequencies(uppercase(letters_only(ciphertext)))
|
||||
for c in 'A':'Z'
|
||||
if !haskey(freqs, c)
|
||||
freqs[c] = 0
|
||||
end
|
||||
end
|
||||
|
||||
freqs_input = sort(collect(freqs), by = tuple -> last(tuple), rev = true)
|
||||
start_key = fill('a', 26)
|
||||
for i in 1:26
|
||||
start_key[Int(commonest[i]) - 64] = first(freqs_input[i])
|
||||
end
|
||||
|
||||
key = join(start_key)
|
||||
else
|
||||
key = starting_key
|
||||
end
|
||||
|
||||
if chatty > 1
|
||||
println("Starting key: $(key)")
|
||||
end
|
||||
|
||||
stripped_ciphertext = letters_only(ciphertext)
|
||||
fitness = string_fitness(decrypt_monoalphabetic(stripped_ciphertext, key))
|
||||
total_best_fitness, total_best_key = fitness, key
|
||||
total_best_decrypt = decrypt_monoalphabetic(ciphertext, key)
|
||||
|
||||
for roundcount in 1:rounds
|
||||
temp = 10^((roundcount - 1) / rounds)
|
||||
while temp > min_temp
|
||||
for i in 1:round(Int, min(ceil(1 / temp), 10))
|
||||
neighbour = swap_two(key)
|
||||
new_fitness = string_fitness(decrypt_monoalphabetic(stripped_ciphertext, neighbour), alreadystripped = true)
|
||||
if new_fitness > total_best_fitness
|
||||
total_best_fitness = new_fitness
|
||||
total_best_key = neighbour
|
||||
total_best_decrypt = decrypt_monoalphabetic(ciphertext, total_best_key)
|
||||
if isempty(starting_key)
|
||||
# most common letters
|
||||
commonest = "ETAOINSHRDLUMCYWFGBPVKZJXQ"
|
||||
freqs = frequencies(uppercase(letters_only(ciphertext)))
|
||||
for c in 'A':'Z'
|
||||
if !haskey(freqs, c)
|
||||
freqs[c] = 0
|
||||
end
|
||||
end
|
||||
|
||||
threshold = rand()
|
||||
|
||||
if chatty >= 2
|
||||
println("Current fitness: $(fitness)")
|
||||
println("New fitness: $(new_fitness)")
|
||||
println("Acceptance probability: $(acceptance_prob(fitness, new_fitness, temp))")
|
||||
println("Threshold: $(threshold)")
|
||||
freqs_input = sort(collect(freqs), by = tuple -> last(tuple), rev = true)
|
||||
start_key = fill('a', 26)
|
||||
for i in 1:26
|
||||
start_key[Int(commonest[i]) - 64] = first(freqs_input[i])
|
||||
end
|
||||
|
||||
if acceptance_prob(fitness, new_fitness, temp) >= threshold
|
||||
if chatty >= 1
|
||||
println("$(key) -> $(neighbour), threshold $(threshold), temperature $(temp), fitness $(new_fitness), prob $(acceptance_prob(fitness, new_fitness, temp))")
|
||||
end
|
||||
fitness = new_fitness
|
||||
key = neighbour
|
||||
key = join(start_key)
|
||||
else
|
||||
key = starting_key
|
||||
end
|
||||
end
|
||||
|
||||
temp = temp * temp_factor
|
||||
if chatty > 1
|
||||
println("Starting key: $(key)")
|
||||
end
|
||||
|
||||
if chatty >= 2
|
||||
println("----")
|
||||
end
|
||||
end
|
||||
stripped_ciphertext = letters_only(ciphertext)
|
||||
fitness = string_fitness(decrypt_monoalphabetic(stripped_ciphertext, key))
|
||||
total_best_fitness, total_best_key = fitness, key
|
||||
total_best_decrypt = decrypt_monoalphabetic(ciphertext, key)
|
||||
|
||||
key, fitness = total_best_key, total_best_fitness
|
||||
temp = 1
|
||||
end
|
||||
for roundcount in 1:rounds
|
||||
temp = 10^((roundcount - 1) / rounds)
|
||||
while temp > min_temp
|
||||
for i in 1:round(Int, min(ceil(1 / temp), 10))
|
||||
neighbour = swap_two(key)
|
||||
new_fitness = string_fitness(decrypt_monoalphabetic(stripped_ciphertext, neighbour), alreadystripped = true)
|
||||
if new_fitness > total_best_fitness
|
||||
total_best_fitness = new_fitness
|
||||
total_best_key = neighbour
|
||||
total_best_decrypt = decrypt_monoalphabetic(ciphertext, total_best_key)
|
||||
end
|
||||
|
||||
if chatty >= 1
|
||||
println("Best was $(total_best_key) at $(total_best_fitness)")
|
||||
println(total_best_decrypt)
|
||||
end
|
||||
(decrypt_monoalphabetic(ciphertext, key), key)
|
||||
threshold = rand()
|
||||
|
||||
if chatty >= 2
|
||||
println("Current fitness: $(fitness)")
|
||||
println("New fitness: $(new_fitness)")
|
||||
println("Acceptance probability: $(acceptance_prob(fitness, new_fitness, temp))")
|
||||
println("Threshold: $(threshold)")
|
||||
end
|
||||
|
||||
if acceptance_prob(fitness, new_fitness, temp) >= threshold
|
||||
if chatty >= 1
|
||||
println("$(key) -> $(neighbour), threshold $(threshold), temperature $(temp), fitness $(new_fitness), prob $(acceptance_prob(fitness, new_fitness, temp))")
|
||||
end
|
||||
fitness = new_fitness
|
||||
key = neighbour
|
||||
end
|
||||
end
|
||||
|
||||
temp = temp * temp_factor
|
||||
|
||||
if chatty >= 2
|
||||
println("----")
|
||||
end
|
||||
end
|
||||
|
||||
key, fitness = total_best_key, total_best_fitness
|
||||
temp = 1
|
||||
end
|
||||
|
||||
if chatty >= 1
|
||||
println("Best was $(total_best_key) at $(total_best_fitness)")
|
||||
println(total_best_decrypt)
|
||||
end
|
||||
|
||||
return decrypt_monoalphabetic(ciphertext, key), key
|
||||
end
|
||||
|
@@ -28,7 +28,7 @@ function encrypt_playfair(plaintext, key::AbstractString; combined::AbstractPair
|
||||
# make combinations in plaintext
|
||||
plaintext_sanitised = encrypt_monoalphabetic(plaintext, D)
|
||||
|
||||
encrypt_playfair(plaintext_sanitised, keysquare, combined = combined)
|
||||
return encrypt_playfair(plaintext_sanitised, keysquare, combined = combined)
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -69,14 +69,14 @@ function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped::Bool = false
|
||||
|
||||
i = 1
|
||||
while i < length(plaintext_sanitised)
|
||||
if plaintext_sanitised[i] == plaintext_sanitised[i + 1]
|
||||
if plaintext_sanitised[i] != padding_char
|
||||
plaintext_sanitised = plaintext_sanitised[1:i] * string(padding_char) * plaintext_sanitised[(i + 1):end]
|
||||
else
|
||||
plaintext_sanitised = plaintext_sanitised[1:i] * string(backup_padding_char) * plaintext_sanitised[(i + 1):end]
|
||||
if plaintext_sanitised[i] == plaintext_sanitised[i + 1]
|
||||
if plaintext_sanitised[i] != padding_char
|
||||
plaintext_sanitised = plaintext_sanitised[1:i] * string(padding_char) * plaintext_sanitised[(i + 1):end]
|
||||
else
|
||||
plaintext_sanitised = plaintext_sanitised[1:i] * string(backup_padding_char) * plaintext_sanitised[(i + 1):end]
|
||||
end
|
||||
end
|
||||
end
|
||||
i += 2
|
||||
i += 2
|
||||
end
|
||||
|
||||
if isodd(length(plaintext_sanitised))
|
||||
@@ -115,13 +115,13 @@ function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped::Bool = false
|
||||
i += 2
|
||||
end
|
||||
|
||||
String(take!(ans))
|
||||
return String(take!(ans))
|
||||
end
|
||||
|
||||
|
||||
function decrypt_playfair(ciphertext, key::AbstractString; combined::AbstractPair{Char, Char} = ('I', 'J'))
|
||||
keysquare = playfair_key_to_square(key, combined)
|
||||
decrypt_playfair(ciphertext, keysquare, combined = combined)
|
||||
return decrypt_playfair(ciphertext, keysquare, combined = combined)
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -132,5 +132,5 @@ Does not attempt to delete X's inserted as padding for double letters.
|
||||
function decrypt_playfair(ciphertext, key::Array{Char, 2}; combined::AbstractPair{Char, Char} = ('I', 'J'))
|
||||
# to obtain the decrypting keysquare, reverse every row and every column
|
||||
keysquare = rot180(key)
|
||||
lowercase(encrypt_playfair(ciphertext, keysquare, combined = combined))
|
||||
return lowercase(encrypt_playfair(ciphertext, keysquare, combined = combined))
|
||||
end
|
||||
|
@@ -6,33 +6,33 @@ The key must be given as a string, whose characters are letters.
|
||||
Converts the text to uppercase.
|
||||
"""
|
||||
function encrypt_portas(plaintext, key_in::AbstractString)
|
||||
key = uppercase(letters_only(key_in))
|
||||
plaintext = uppercase(plaintext)
|
||||
keyarr = Int[div(Int(ch) - 65, 2) for ch in key]
|
||||
key = uppercase(letters_only(key_in))
|
||||
plaintext = uppercase(plaintext)
|
||||
keyarr = Int[div(Int(ch) - 65, 2) for ch in key]
|
||||
|
||||
keycounter = 1
|
||||
ans = IOBuffer()
|
||||
keycounter = 1
|
||||
ans = IOBuffer()
|
||||
|
||||
for i in 1:length(plaintext)
|
||||
if ('A' <= plaintext[i] <= 'Z')
|
||||
plainch = Int(plaintext[i]) # 68
|
||||
keych = keyarr[keycounter] # 4
|
||||
if 'Z' >= plaintext[i] >= 'M'
|
||||
print(ans, Char(((plainch - 65 - keych + 13) % 13) + 65))
|
||||
else
|
||||
print(ans, Char(((plainch - 65 + keych) % 13) + 65+13))
|
||||
end
|
||||
for i in 1:length(plaintext)
|
||||
if ('A' <= plaintext[i] <= 'Z')
|
||||
plainch = Int(plaintext[i]) # 68
|
||||
keych = keyarr[keycounter] # 4
|
||||
if 'Z' >= plaintext[i] >= 'M'
|
||||
print(ans, Char(((plainch - 65 - keych + 13) % 13) + 65))
|
||||
else
|
||||
print(ans, Char(((plainch - 65 + keych) % 13) + 65+13))
|
||||
end
|
||||
|
||||
keycounter += 1
|
||||
if keycounter == length(key) + 1
|
||||
keycounter = 1
|
||||
end
|
||||
else
|
||||
print(ans, plaintext[i])
|
||||
end
|
||||
end
|
||||
keycounter += 1
|
||||
if keycounter == length(key) + 1
|
||||
keycounter = 1
|
||||
end
|
||||
else
|
||||
print(ans, plaintext[i])
|
||||
end
|
||||
end
|
||||
|
||||
String(take!(ans))
|
||||
return String(take!(ans))
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -43,5 +43,5 @@ The key must be given as a string, whose characters are letters.
|
||||
Converts the text to lowercase.
|
||||
"""
|
||||
function decrypt_portas(ciphertext, key::AbstractString)
|
||||
lowercase(encrypt_portas(ciphertext, key))
|
||||
return lowercase(encrypt_portas(ciphertext, key))
|
||||
end
|
||||
|
190
src/solitaire.jl
190
src/solitaire.jl
@@ -1,88 +1,89 @@
|
||||
function next_solitaire(deckIn::AbstractVector{T}) where {T <: Integer}
|
||||
# performs one round of Solitaire on the given deck
|
||||
# first joker
|
||||
deck = deckIn
|
||||
jokerPos = findfirst(i -> i == 53, deck)
|
||||
if jokerPos != length(deck)
|
||||
inter = deck[jokerPos + 1]
|
||||
deck[jokerPos + 1] = deck[jokerPos]
|
||||
deck[jokerPos] = inter
|
||||
else
|
||||
inter = deck[end]
|
||||
deck[end] = deck[1]
|
||||
deck[1] = inter
|
||||
deck = rotateRight(deck)
|
||||
end
|
||||
# second joker
|
||||
jokerPos = findfirst(i -> i == 54, deck)
|
||||
if jokerPos <= length(deck) - 2
|
||||
inter = deck[jokerPos]
|
||||
deck[jokerPos] = deck[jokerPos + 1]
|
||||
deck[jokerPos + 1] = deck[jokerPos + 2]
|
||||
deck[jokerPos + 2] = inter
|
||||
elseif jokerPos == length(deck) - 1
|
||||
inter = deck[length(deck) - 1]
|
||||
inter1 = deck[length(deck)]
|
||||
deck[end] = deck[1]
|
||||
deck[length(deck)-1] = inter1
|
||||
deck[1] = inter
|
||||
deck = rotateRight(deck, 1)
|
||||
elseif jokerPos == length(deck)
|
||||
inter = deck[1]
|
||||
inter1 = deck[end]
|
||||
deck[1] = deck[2]
|
||||
deck[2] = inter1
|
||||
deck[end] = inter
|
||||
deck = rotateRight(deck, 1)
|
||||
end
|
||||
# triple-cut
|
||||
split_deck = splitBy(deck, x -> x > 52)
|
||||
if deck[1] > 52 && deck[end] > 52
|
||||
# do nothing
|
||||
elseif deck[1] > 52
|
||||
split_deck = rotateRight(split_deck, 1)
|
||||
elseif deck[end] > 52
|
||||
split_deck = rotateLeft(split_deck, 1)
|
||||
else
|
||||
inter = split_deck[1]
|
||||
split_deck[1] = split_deck[end]
|
||||
split_deck[end] = inter
|
||||
end
|
||||
deck = collect(Iterators.flatten(split_deck))
|
||||
# take bottom of deck and put it just above last card
|
||||
cardsToTake = deck[end] > 52 ? 0 : deck[end]
|
||||
# performs one round of Solitaire on the given deck
|
||||
# first joker
|
||||
deck = deckIn
|
||||
jokerPos = findfirst(i -> i == 53, deck)
|
||||
if jokerPos != length(deck)
|
||||
inter = deck[jokerPos + 1]
|
||||
deck[jokerPos + 1] = deck[jokerPos]
|
||||
deck[jokerPos] = inter
|
||||
else
|
||||
inter = deck[end]
|
||||
deck[end] = deck[1]
|
||||
deck[1] = inter
|
||||
deck = rotate_right(deck)
|
||||
end
|
||||
# second joker
|
||||
jokerPos = findfirst(i -> i == 54, deck)
|
||||
if jokerPos <= length(deck) - 2
|
||||
inter = deck[jokerPos]
|
||||
deck[jokerPos] = deck[jokerPos + 1]
|
||||
deck[jokerPos + 1] = deck[jokerPos + 2]
|
||||
deck[jokerPos + 2] = inter
|
||||
elseif jokerPos == length(deck) - 1
|
||||
inter = deck[length(deck) - 1]
|
||||
inter1 = deck[length(deck)]
|
||||
deck[end] = deck[1]
|
||||
deck[length(deck)-1] = inter1
|
||||
deck[1] = inter
|
||||
deck = rotate_right(deck, 1)
|
||||
elseif jokerPos == length(deck)
|
||||
inter = deck[1]
|
||||
inter1 = deck[end]
|
||||
deck[1] = deck[2]
|
||||
deck[2] = inter1
|
||||
deck[end] = inter
|
||||
deck = rotate_right(deck, 1)
|
||||
end
|
||||
# triple-cut
|
||||
split_deck = split_by(deck, x -> x > 52)
|
||||
if deck[1] > 52 && deck[end] > 52
|
||||
# do nothing
|
||||
elseif deck[1] > 52
|
||||
split_deck = rotate_right(split_deck, 1)
|
||||
elseif deck[end] > 52
|
||||
split_deck = rotate_left(split_deck, 1)
|
||||
else
|
||||
inter = split_deck[1]
|
||||
split_deck[1] = split_deck[end]
|
||||
split_deck[end] = inter
|
||||
end
|
||||
deck = collect(Iterators.flatten(split_deck))
|
||||
# take bottom of deck and put it just above last card
|
||||
cardsToTake = deck[end] > 52 ? 0 : deck[end]
|
||||
|
||||
intermediate = rotateLeft(deck[1:length(deck) - 1], cardsToTake)
|
||||
append!(intermediate, [deck[end]])
|
||||
deck = intermediate
|
||||
intermediate = rotate_left(deck[1:length(deck) - 1], cardsToTake)
|
||||
append!(intermediate, [deck[end]])
|
||||
deck = intermediate
|
||||
|
||||
return collect(deck)
|
||||
return collect(deck)
|
||||
end
|
||||
|
||||
function keychar_from_deck(deck::AbstractVector{T}) where {T <: Integer}
|
||||
# given a deck, returns an integer which is the Solitaire key value
|
||||
# output by that deck
|
||||
deck[((deck[1] == 54 ? 53 : deck[1]) % length(deck)) + 1]
|
||||
# given a deck, returns an integer which is the Solitaire key value
|
||||
# output by that deck
|
||||
return deck[((deck[1] == 54 ? 53 : deck[1]) % length(deck)) + 1]
|
||||
end
|
||||
|
||||
struct SolitaireKeyStreamStruct
|
||||
deck::AbstractVector{Integer}
|
||||
deck::AbstractVector{Integer}
|
||||
end
|
||||
|
||||
function Base.iterate(b::SolitaireKeyStreamStruct)
|
||||
(0, next_solitaire(b.deck))
|
||||
return 0, next_solitaire(b.deck)
|
||||
end
|
||||
|
||||
function Base.iterate(b::SolitaireKeyStreamStruct, state)
|
||||
curState = state::AbstractVector{Integer}
|
||||
while keychar_from_deck(curState) > 52
|
||||
curState = next_solitaire(curState)
|
||||
end
|
||||
(keychar_from_deck(curState)::Integer, next_solitaire(curState))
|
||||
curState = state::AbstractVector{Integer}
|
||||
while keychar_from_deck(curState) > 52
|
||||
curState = next_solitaire(curState)
|
||||
end
|
||||
|
||||
return keychar_from_deck(curState), next_solitaire(curState)
|
||||
end
|
||||
|
||||
function SolitaireKeyStream(initialDeck::AbstractVector{T}) where {T <: Integer}
|
||||
Iterators.filter(i -> i <= 52, Iterators.drop(SolitaireKeyStreamStruct(initialDeck), 1))
|
||||
return Iterators.filter(i -> i <= 52, Iterators.drop(SolitaireKeyStreamStruct(initialDeck), 1))
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -92,12 +93,13 @@ The key may be given either as a vector initial deck, where the cards are
|
||||
Schneier's keying algorithm is used to key the deck if the key is a string.
|
||||
"""
|
||||
function encrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T}) where {T <: Integer}
|
||||
inp = uppercase(letters_only(string))
|
||||
ans = ""
|
||||
for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp))
|
||||
ans *= encrypt_caesar(input, keyval)
|
||||
end
|
||||
return ans
|
||||
inp = uppercase(letters_only(string))
|
||||
ans = ""
|
||||
for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp))
|
||||
ans *= encrypt_caesar(input, keyval)
|
||||
end
|
||||
|
||||
return ans
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -107,33 +109,35 @@ The key may be given either as a vector initial deck, where the cards are
|
||||
Schneier's keying algorithm is used to key the deck if the key is a string.
|
||||
"""
|
||||
function decrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T}) where {T <: Integer}
|
||||
inp = uppercase(letters_only(string))
|
||||
ans = ""
|
||||
i = 0
|
||||
for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp))
|
||||
ans *= decrypt_caesar(input, keyval)
|
||||
end
|
||||
return ans
|
||||
inp = uppercase(letters_only(string))
|
||||
ans = ""
|
||||
i = 0
|
||||
for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp))
|
||||
ans *= decrypt_caesar(input, keyval)
|
||||
end
|
||||
|
||||
return ans
|
||||
end
|
||||
|
||||
function key_deck(key::AbstractString)
|
||||
# returns the Solitaire deck after it has been keyed with the given string
|
||||
deck = collect(1:54)
|
||||
for keyval in uppercase(letters_only(key))
|
||||
deck = next_solitaire(deck)
|
||||
cardsToTake = Int(keyval) - 64
|
||||
intermediate = rotateLeft(deck[1:(length(deck) - 1)], cardsToTake)
|
||||
append!(intermediate, [deck[end]])
|
||||
deck = intermediate
|
||||
end
|
||||
deck
|
||||
# returns the Solitaire deck after it has been keyed with the given string
|
||||
deck = collect(1:54)
|
||||
for keyval in uppercase(letters_only(key))
|
||||
deck = next_solitaire(deck)
|
||||
cardsToTake = Int(keyval) - 64
|
||||
intermediate = rotate_left(deck[1:(length(deck) - 1)], cardsToTake)
|
||||
append!(intermediate, [deck[end]])
|
||||
deck = intermediate
|
||||
end
|
||||
|
||||
return deck
|
||||
end
|
||||
|
||||
function encrypt_solitaire(string::AbstractString, key::AbstractString) :: AbstractString
|
||||
key = key_deck(key)
|
||||
encrypt_solitaire(string, key)
|
||||
key = key_deck(key)
|
||||
return encrypt_solitaire(string, key)
|
||||
end
|
||||
|
||||
function decrypt_solitaire(string::AbstractString, key::AbstractString)
|
||||
decrypt_solitaire(string, key_deck(key))
|
||||
return decrypt_solitaire(string, key_deck(key))
|
||||
end
|
||||
|
@@ -5,9 +5,9 @@ Encrypts the given string using the Vigenere cipher according to the given vecto
|
||||
For example, encrypt_vigenere("ab", [0, 1]) returns "AC".
|
||||
"""
|
||||
function encrypt_vigenere(plaintext, key::Array)
|
||||
# plaintext: string; key: vector of integer offsets, so [0, 1] encrypts "ab" as "ac"
|
||||
ans = String[encrypt_caesar(chr, key[(i - 1) % length(key) + 1]) for (i, chr) in enumerate(letters_only(plaintext))]
|
||||
join(ans)
|
||||
# plaintext: string; key: vector of integer offsets, so [0, 1] encrypts "ab" as "ac"
|
||||
ans = String[encrypt_caesar(chr, key[(i - 1) % length(key) + 1]) for (i, chr) in enumerate(letters_only(plaintext))]
|
||||
return join(ans)
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -15,8 +15,8 @@ Decrypts the given string using the Vigenere cipher according to the given vecto
|
||||
For example, decrypt_vigenere("ac", [0, 1]) returns "ab".
|
||||
"""
|
||||
function decrypt_vigenere(ciphertext, key::Array)
|
||||
# ciphertext: string; key: vector of integer offsets, so [0, 1] decrypts "ac" as "ab"
|
||||
lowercase(encrypt_vigenere(ciphertext, map(x -> 26 - x, key)))
|
||||
# ciphertext: string; key: vector of integer offsets, so [0, 1] decrypts "ac" as "ab"
|
||||
return lowercase(encrypt_vigenere(ciphertext, map(x -> 26 - x, key)))
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -24,8 +24,8 @@ Encrypts the given string using the Vigenere cipher according to the given keyst
|
||||
For example, encrypt_vigenere("ab", "ab") returns "AC".
|
||||
"""
|
||||
function encrypt_vigenere(ciphertext, key::AbstractString)
|
||||
# ciphertext: string; key: string, so "ab" encrypts "ab" as "AC"
|
||||
encrypt_vigenere(ciphertext, Int[Int(i) - 97 for i in lowercase(letters_only(key))])
|
||||
# ciphertext: string; key: string, so "ab" encrypts "ab" as "AC"
|
||||
return encrypt_vigenere(ciphertext, Int[Int(i) - 97 for i in lowercase(letters_only(key))])
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -33,8 +33,8 @@ Decrypts the given string using the Vigenere cipher according to the given keyst
|
||||
For example, decrypt_vigenere("ab", "ac") returns "ab".
|
||||
"""
|
||||
function decrypt_vigenere(plaintext, key::AbstractString)
|
||||
# plaintext: string; key: string, so "ab" decrypts "ac" as "ab"
|
||||
decrypt_vigenere(plaintext, Int[Int(i) - 97 for i in lowercase(letters_only(key))])
|
||||
# plaintext: string; key: string, so "ab" decrypts "ac" as "ab"
|
||||
return decrypt_vigenere(plaintext, Int[Int(i) - 97 for i in lowercase(letters_only(key))])
|
||||
end
|
||||
|
||||
"""
|
||||
@@ -44,28 +44,28 @@ 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.
|
||||
If 0, the solver will attempt to derive the key length using the index
|
||||
of coincidence.
|
||||
"""
|
||||
function crack_vigenere(plaintext; keylength::Integer = 0)
|
||||
stripped_text = letters_only(lowercase(plaintext))
|
||||
if keylength == 0
|
||||
lens = sort(collect(2:15), by = len -> mean(AbstractFloat[index_of_coincidence(stripped_text[i:len:end]) for i in 1:len]))
|
||||
keylength = lens[end]
|
||||
end
|
||||
stripped_text = letters_only(lowercase(plaintext))
|
||||
if keylength == 0
|
||||
lens = sort(collect(2:15), by = len -> mean(AbstractFloat[index_of_coincidence(stripped_text[i:len:end]) for i in 1:len]))
|
||||
keylength = lens[end]
|
||||
end
|
||||
|
||||
everyother = String[stripped_text[i:keylength:end] for i in 1:keylength]
|
||||
decr = String[crack_caesar(st)[1] for st in everyother]
|
||||
everyother = String[stripped_text[i:keylength:end] for i in 1:keylength]
|
||||
decr = String[crack_caesar(st)[1] for st in everyother]
|
||||
|
||||
ans = IOBuffer()
|
||||
for column in 1:length(decr[1])
|
||||
for j in 1:keylength
|
||||
if column <= length(decr[j])
|
||||
print(ans, decr[j][column])
|
||||
end
|
||||
end
|
||||
end
|
||||
ans = IOBuffer()
|
||||
for column in 1:length(decr[1])
|
||||
for j in 1:keylength
|
||||
if column <= length(decr[j])
|
||||
print(ans, decr[j][column])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
derived_key = join(Char[Char(65 + crack_caesar(st)[2]) for st in everyother])
|
||||
(derived_key, String(take!(ans)))
|
||||
derived_key = join(Char[Char(65 + crack_caesar(st)[2]) for st in everyother])
|
||||
return derived_key, String(take!(ans))
|
||||
end
|
||||
|
Reference in New Issue
Block a user