Refined code style to be more in line with Blue Code Style guide

This commit is contained in:
Jake W. Ireland
2021-01-08 02:48:44 +13:00
parent b3acd440f1
commit 09f71792f3
11 changed files with 378 additions and 368 deletions

View File

@@ -1,6 +1,4 @@
[![Build Status](https://travis-ci.org/Smaug123/ClassicalCiphers.jl.svg?branch=master)](https://travis-ci.org/Smaug123/ClassicalCiphers.jl) [![Build Status](https://travis-ci.org/Smaug123/ClassicalCiphers.jl.svg?branch=master)](https://travis-ci.org/Smaug123/ClassicalCiphers.jl) [![Coverage Status](https://coveralls.io/repos/Smaug123/ClassicalCiphers.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/Smaug123/ClassicalCiphers.jl?branch=master) [![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle)
[![Coverage Status](https://coveralls.io/repos/Smaug123/ClassicalCiphers.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/Smaug123/ClassicalCiphers.jl?branch=master)
# ClassicalCiphers # ClassicalCiphers
@@ -185,7 +183,7 @@ are stripped out before use.
Decrypt the same text: Decrypt the same text:
```julia ```julia
decrypt_portas("URYYB, JBEYQ!", "ab") decrypt_portas("URYYB, JBEYQ!", "ab")
# outputs "hello, world!" # outputs "hello, world!"
``` ```

View File

@@ -16,7 +16,7 @@ function encrypt_affine(plaintext, mult::T, add::T; offset::T = 0) where {T <: I
end end
keystr = join(Char[Char(((mult * i + add) % 26) + 97 - offset) for i in offset:(25 + offset)]) 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 end
""" """
@@ -37,7 +37,7 @@ function decrypt_affine(ciphertext, mult::T, add::T; offset=0) where {T <: Integ
end end
keystr = join(Char[Char(((mult*i + add) % 26) + 97 - offset) for i in offset:25+offset], "") 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 end
function max_by(arr::AbstractArray, f::Function) function max_by(arr::AbstractArray, f::Function)
@@ -57,7 +57,7 @@ function max_by(arr::AbstractArray, f::Function)
end end
end end
end end
currAns return currAns
end 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} 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)] 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) adds = add != -1 ? Int[add] : (0:25)
possible_keys = Iterators.product(mults, adds) 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 end

View File

@@ -6,10 +6,10 @@ so encrypt_caesar("abc", 1) == "BCD".
Converts the input to uppercase. Converts the input to uppercase.
""" """
function encrypt_caesar(plaintext, key::T) where {T <: Integer} function encrypt_caesar(plaintext, key::T) where {T <: Integer}
# plaintext: string; key: integer offset, so k=1 sends "a" to "b" # plaintext: string; key: integer offset, so k=1 sends "a" to "b"
key = ((key - 1) % 26) + 1 key = ((key - 1) % 26) + 1
keystr = join(vcat(collect(Char(97 + key):'z'), collect('a':Char(97 + key - 1)))) keystr = join(vcat(collect(Char(97 + key):'z'), collect('a':Char(97 + key - 1))))
encrypt_monoalphabetic(plaintext, keystr) return encrypt_monoalphabetic(plaintext, keystr)
end end
""" """
@@ -20,9 +20,9 @@ so decrypt_caesar("abcd", 1) == "zabc".
Converts the input to lowercase. Converts the input to lowercase.
""" """
function decrypt_caesar(ciphertext, key::T) where {T <: Integer} function decrypt_caesar(ciphertext, key::T) where {T <: Integer}
# ciphertext: string; key: integer offset, so k=1 decrypts "B" as "a" # ciphertext: string; key: integer offset, so k=1 decrypts "B" as "a"
key = ((key - 1) % 26) + 1 key = ((key - 1) % 26) + 1
lowercase(encrypt_caesar(ciphertext, 26 - key)) return lowercase(encrypt_caesar(ciphertext, 26 - key))
end end
""" """
@@ -36,11 +36,12 @@ With cleverness=1, maximises the string's total fitness.
Converts the input to lowercase. Converts the input to lowercase.
""" """
function crack_caesar(ciphertext; cleverness::T = 1) where {T <: Integer} function crack_caesar(ciphertext; cleverness::T = 1) where {T <: Integer}
texts = Tuple{String, Int}[(decrypt_caesar(ciphertext,key), key) for key in 0:25] texts = Tuple{String, Int}[(decrypt_caesar(ciphertext,key), key) for key in 0:25]
if cleverness == 1 if cleverness == 1
texts = sort(texts, by=(x -> string_fitness(first(x)))) texts = sort(texts, by = (x -> string_fitness(first(x))))
else else
texts = sort(texts, by=(x -> length(collect(filter(i -> (i == 'e'), first(x)))))) texts = sort(texts, by = (x -> length(collect(filter(i -> (i == 'e'), first(x))))))
end end
texts[end]
return texts[end]
end end

View File

@@ -1,62 +1,63 @@
function letters_only(text::AbstractString) function letters_only(text::AbstractString)
# text: string; removes all non-alphabetic characters # text: string; removes all non-alphabetic characters
filter(x -> ('A' <= x <= 'Z' || 'a' <= x <= 'z'), text) return filter(x -> ('A' <= x <= 'Z' || 'a' <= x <= 'z'), text)
end end
function rotateRight(arr::AbstractVector, n::T) where {T <: Integer} function rotate_right(arr::AbstractVector, n::T) where {T <: Integer}
# implementation of the Mathematica function RotateRight - or you could try circshift()? # implementation of the Mathematica function rotate_right - or you could try circshift()?
ans = copy(arr) ans = copy(arr)
for i in 1:length(arr) for i in 1:length(arr)
ans[i] = arr[((2*length(ans)+i-n-1) % length(ans)) + 1] 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]])
end 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 end
function get_trigram_fitnesses(datafile::AbstractString) function get_trigram_fitnesses(datafile::AbstractString)
# The quadgrams file we use is licensed MIT, as per # The quadgrams file we use is licensed MIT, as per
# http://practicalcryptography.com/cryptanalysis/text-characterisation/quadgrams/#comment-2007984751 # http://practicalcryptography.com/cryptanalysis/text-characterisation/quadgrams/#comment-2007984751
dict = Dict{AbstractString, Int32}() dict = Dict{AbstractString, Int32}()
open(datafile) do io open(datafile) do io
while ! eof(io) while ! eof(io)
(ngram, fitness) = split(readline(io)) (ngram, fitness) = split(readline(io))
dict[ngram] = parse(Int32, fitness) dict[ngram] = parse(Int32, fitness)
end
end end
end
dict return dict
end end
trigram_fitnesses = get_trigram_fitnesses(joinpath(dirname(Base.source_path()), "english_trigrams.txt")) 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. and assigns a score based on the frequency of the trigrams in true English.
""" """
function string_fitness(input::AbstractString; alreadystripped::Bool = false) function string_fitness(input::AbstractString; alreadystripped::Bool = false)
if !alreadystripped if alreadystripped
str = letters_only(input) str = input
else else
str = input str = letters_only(input)
end end
str = uppercase(str) str = uppercase(str)
ans = 0 ans = 0
for i in 1:(length(str)-2) for i in 1:(length(str) - 2)
ans += get(trigram_fitnesses, str[i:(i + 2)], 0) ans += get(trigram_fitnesses, str[i:(i + 2)], 0)
end end
log(ans / length(str)) return log(ans / length(str))
end 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. of 'a' => 4, for instance. Uppercase characters are considered distinct from lowercase.
""" """
function frequencies(input::AbstractString) function frequencies(input::AbstractString)
ans = Dict{Char, Int}() ans = Dict{Char, Int}()
for c in input for c in input
index = Base.ht_keyindex2!(ans, c) index = Base.ht_keyindex2!(ans, c)
if index > 0 if index > 0
@inbounds ans.vals[index] += 1 @inbounds ans.vals[index] += 1
else else
@inbounds Base._setindex!(ans, 0, c, -index) @inbounds Base._setindex!(ans, 0, c, -index)
end end
end end
ans return ans
end end
""" """
@@ -105,13 +106,13 @@ Finds the index of coincidence of the input string. Uppercase characters are con
equal to their lowercase counterparts. equal to their lowercase counterparts.
""" """
function index_of_coincidence(input::AbstractString) function index_of_coincidence(input::AbstractString)
freqs = frequencies(lowercase(letters_only(input))) freqs = frequencies(lowercase(letters_only(input)))
len = length(lowercase(letters_only(input))) len = length(lowercase(letters_only(input)))
ans = 0 ans = 0
for i in 'a':'z' for i in 'a':'z'
ans += map(x -> x * (x - 1), get(freqs, i, 0)) ans += map(x -> x * (x - 1), get(freqs, i, 0))
end end
ans /= (len * (len - 1) / 26) return ans /= (len * (len - 1) / 26)
end end

View File

@@ -15,7 +15,7 @@ function parse_stecker(stecker::AbstractString)
sp = collect(stecker) sp = collect(stecker)
steck_parsed = Tuple{Char, Char}[(sp[i][1], sp[i + 1][1]) for i in 1:2:length(sp)] steck_parsed = Tuple{Char, Char}[(sp[i][1], sp[i + 1][1]) for i in 1:2:length(sp)]
end end
steck_parsed return steck_parsed
end end
function parse_stecker(stecker::Array{Tuple{Char, Char}}) function parse_stecker(stecker::Array{Tuple{Char, Char}})
@@ -24,6 +24,7 @@ function parse_stecker(stecker::Array{Tuple{Char, Char}})
else else
return stecker return stecker
end end
return nothing
end end
function parse_reflector(reflector::Char) function parse_reflector(reflector::Char)
@@ -36,6 +37,7 @@ function parse_reflector(reflector::Char)
else else
error("Reflector $(reflector) unrecognised.") error("Reflector $(reflector) unrecognised.")
end end
return nothing
end end
function parse_reflector(reflector::AbstractString) 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.") error("Reflector must not contain any character used more than once.")
end end
ans return ans
end end
""" """
@@ -238,9 +240,8 @@ function encrypt_enigma(plaintext,
print(ans, working_ch) print(ans, working_ch)
end end
uppercase(String(take!(ans))) return uppercase(String(take!(ans)))
end end
function decrypt_enigma(args1...; args2...) decrypt_enigma(args1...; args2...) =
lowercase(encrypt_enigma(args1...; args2...)) lowercase(encrypt_enigma(args1...; args2...))
end

View File

@@ -36,7 +36,8 @@ function encrypt_hill(plaintext::AbstractString, key::AbstractArray{T, 2}) where
print(ans, Char(x)) print(ans, Char(x))
end end
end end
String(take!(ans))
return String(take!(ans))
end end
@@ -50,12 +51,12 @@ function encrypt_hill(plaintext::AbstractString, key::AbstractString)
key_matrix = reshape(keys, matrix_dim, matrix_dim) key_matrix = reshape(keys, matrix_dim, matrix_dim)
encrypt_hill(plaintext, transpose(key_matrix)) return encrypt_hill(plaintext, transpose(key_matrix))
end end
function minor(mat::AbstractArray{T, 2}, i::K, j::K) where {T <: Integer, K <: Integer} 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)]) 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 end
""" """
@@ -64,7 +65,7 @@ Computes the adjugate matrix for given matrix.
function adjugate(mat::AbstractArray{T, 2}) where {T <: Integer} 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))] 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)) ans = reshape(arr, size(mat))
Array{Integer, 2}(transpose(ans)) return Array{Integer, 2}(transpose(ans))
end end
function decrypt_hill(ciphertext, key::AbstractArray{T, 2}) where {T<:Integer} 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
inverse_mat = (inverse_mat .+ (26 * 26)) .% 26 inverse_mat = (inverse_mat .+ (26 * 26)) .% 26
lowercase(encrypt_hill(ciphertext, inverse_mat)) return lowercase(encrypt_hill(ciphertext, inverse_mat))
end end
function decrypt_hill(ciphertext, key::AbstractString) 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)))) keys = map(x -> Int(x) - 65, collect(uppercase(letters_only(key))))
key_matrix = reshape(keys, matrix_dim, matrix_dim) key_matrix = reshape(keys, matrix_dim, matrix_dim)
decrypt_hill(ciphertext, transpose(key_matrix)) return decrypt_hill(ciphertext, transpose(key_matrix))
end end

View File

@@ -1,5 +1,5 @@
function keystr_to_dict(keystr::AbstractString) 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 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. in particular, the string is not converted to lowercase automatically.
""" """
function encrypt_monoalphabetic(plaintext, key::Dict{Char, Char}) function encrypt_monoalphabetic(plaintext, key::Dict{Char, Char})
# plaintext: string; key: dictionary of {'a' => 'b'}, etc, for replacing 'a' with 'b' # 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]) return join(eltype(keys(key))[get(key, i, i) for i in plaintext])
end 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. in particular, the string is not converted to lowercase automatically.
""" """
function decrypt_monoalphabetic(ciphertext, key::Dict{Char, Char}) function decrypt_monoalphabetic(ciphertext, key::Dict{Char, Char})
# ciphertext: string; key: dictionary of {'a' => 'b'}, etc, where the plaintext 'a' was # ciphertext: string; key: dictionary of {'a' => 'b'}, etc, where the plaintext 'a' was
# replaced by ciphertext 'b'. No character should appear more than once # replaced by ciphertext 'b'. No character should appear more than once
# as a value in {key}. # as a value in {key}.
encrypt_monoalphabetic(ciphertext, Dict{Char, Char}([reverse(a) for a in key])) return encrypt_monoalphabetic(ciphertext, Dict{Char, Char}([reverse(a) for a in key]))
end end
function encrypt_monoalphabetic(plaintext, key::AbstractString) function encrypt_monoalphabetic(plaintext, key::AbstractString)
# plaintext: string; key: string of length 26, first character is the image of 'a', etc # 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 # working in lowercase; key is assumed only to have each element appearing once
encrypt_monoalphabetic(uppercase(plaintext), keystr_to_dict(key)) return encrypt_monoalphabetic(uppercase(plaintext), keystr_to_dict(key))
end end
function decrypt_monoalphabetic(ciphertext, key::AbstractString) function decrypt_monoalphabetic(ciphertext, key::AbstractString)
# ciphertext: string; key: string of length 26, first character is the image of 'a', etc # 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 # working in lowercase; key is assumed only to have each element appearing once
# and to be in lowercase # and to be in lowercase
# so decrypt_monoalphabetic("cb", "cbade…") is "ab" # 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)) dict = Dict{Char, Char}(a => Char(96 + findfirst(i -> i == a, lowercase(key))) for a in lowercase(key))
encrypt_monoalphabetic(lowercase(ciphertext), dict) return encrypt_monoalphabetic(lowercase(ciphertext), dict)
end end
# Cracking # Cracking
@@ -61,12 +61,12 @@ The characters are guaranteed to be at different positions, though "aa" would be
'swapped' to "aa". 'swapped' to "aa".
""" """
function swap_two(str) function swap_two(str)
indices = rand(1:length(str), 2)
while indices[1] == indices[2]
indices = rand(1:length(str), 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 end
""" """
@@ -77,98 +77,103 @@ 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
common characters being decrypted to the most common English characters. common characters being decrypted to the most common English characters.
min_temp=0.0001, which is the temperature at which we stop the simulation. 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. 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 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 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 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, with which we accept new key of fitness ep, given that the current key has fitness e,
at temperature t. at temperature t.
""" """
function crack_monoalphabetic(ciphertext; starting_key::AbstractString = "", function crack_monoalphabetic(
min_temp::F = 0.0001, temp_factor::F = 0.97, ciphertext;
acceptance_prob::F = ((e,ep,t) -> ep > e ? 1. : exp(-(e-ep)/t)), starting_key::AbstractString = "",
chatty::T = 0, min_temp::F = 0.0001,
rounds::T = 1) where {T <: Integer, F <: AbstractFloat} 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) if isempty(starting_key)
# most common letters # most common letters
commonest = "ETAOINSHRDLUMCYWFGBPVKZJXQ" commonest = "ETAOINSHRDLUMCYWFGBPVKZJXQ"
freqs = frequencies(uppercase(letters_only(ciphertext))) freqs = frequencies(uppercase(letters_only(ciphertext)))
for c in 'A':'Z' for c in 'A':'Z'
if !haskey(freqs, c) if !haskey(freqs, c)
freqs[c] = 0 freqs[c] = 0
end 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)
end end
threshold = rand() freqs_input = sort(collect(freqs), by = tuple -> last(tuple), rev = true)
start_key = fill('a', 26)
if chatty >= 2 for i in 1:26
println("Current fitness: $(fitness)") start_key[Int(commonest[i]) - 64] = first(freqs_input[i])
println("New fitness: $(new_fitness)")
println("Acceptance probability: $(acceptance_prob(fitness, new_fitness, temp))")
println("Threshold: $(threshold)")
end end
if acceptance_prob(fitness, new_fitness, temp) >= threshold key = join(start_key)
if chatty >= 1 else
println("$(key) -> $(neighbour), threshold $(threshold), temperature $(temp), fitness $(new_fitness), prob $(acceptance_prob(fitness, new_fitness, temp))") key = starting_key
end
fitness = new_fitness
key = neighbour
end end
end
temp = temp * temp_factor if chatty > 1
println("Starting key: $(key)")
end
if chatty >= 2 stripped_ciphertext = letters_only(ciphertext)
println("----") fitness = string_fitness(decrypt_monoalphabetic(stripped_ciphertext, key))
end total_best_fitness, total_best_key = fitness, key
end total_best_decrypt = decrypt_monoalphabetic(ciphertext, key)
key, fitness = total_best_key, total_best_fitness for roundcount in 1:rounds
temp = 1 temp = 10^((roundcount - 1) / rounds)
end 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 threshold = rand()
println("Best was $(total_best_key) at $(total_best_fitness)")
println(total_best_decrypt) if chatty >= 2
end println("Current fitness: $(fitness)")
(decrypt_monoalphabetic(ciphertext, key), key) 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 end

View File

@@ -28,7 +28,7 @@ function encrypt_playfair(plaintext, key::AbstractString; combined::AbstractPair
# make combinations in plaintext # make combinations in plaintext
plaintext_sanitised = encrypt_monoalphabetic(plaintext, D) plaintext_sanitised = encrypt_monoalphabetic(plaintext, D)
encrypt_playfair(plaintext_sanitised, keysquare, combined = combined) return encrypt_playfair(plaintext_sanitised, keysquare, combined = combined)
end end
""" """
@@ -69,14 +69,14 @@ function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped::Bool = false
i = 1 i = 1
while i < length(plaintext_sanitised) while i < length(plaintext_sanitised)
if plaintext_sanitised[i] == plaintext_sanitised[i + 1] if plaintext_sanitised[i] == plaintext_sanitised[i + 1]
if plaintext_sanitised[i] != padding_char if plaintext_sanitised[i] != padding_char
plaintext_sanitised = plaintext_sanitised[1:i] * string(padding_char) * plaintext_sanitised[(i + 1):end] plaintext_sanitised = plaintext_sanitised[1:i] * string(padding_char) * plaintext_sanitised[(i + 1):end]
else else
plaintext_sanitised = plaintext_sanitised[1:i] * string(backup_padding_char) * plaintext_sanitised[(i + 1):end] plaintext_sanitised = plaintext_sanitised[1:i] * string(backup_padding_char) * plaintext_sanitised[(i + 1):end]
end
end end
end i += 2
i += 2
end end
if isodd(length(plaintext_sanitised)) if isodd(length(plaintext_sanitised))
@@ -115,13 +115,13 @@ function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped::Bool = false
i += 2 i += 2
end end
String(take!(ans)) return String(take!(ans))
end end
function decrypt_playfair(ciphertext, key::AbstractString; combined::AbstractPair{Char, Char} = ('I', 'J')) function decrypt_playfair(ciphertext, key::AbstractString; combined::AbstractPair{Char, Char} = ('I', 'J'))
keysquare = playfair_key_to_square(key, combined) keysquare = playfair_key_to_square(key, combined)
decrypt_playfair(ciphertext, keysquare, combined = combined) return decrypt_playfair(ciphertext, keysquare, combined = combined)
end 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')) 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 # to obtain the decrypting keysquare, reverse every row and every column
keysquare = rot180(key) keysquare = rot180(key)
lowercase(encrypt_playfair(ciphertext, keysquare, combined = combined)) return lowercase(encrypt_playfair(ciphertext, keysquare, combined = combined))
end end

View File

@@ -6,33 +6,33 @@ The key must be given as a string, whose characters are letters.
Converts the text to uppercase. Converts the text to uppercase.
""" """
function encrypt_portas(plaintext, key_in::AbstractString) function encrypt_portas(plaintext, key_in::AbstractString)
key = uppercase(letters_only(key_in)) key = uppercase(letters_only(key_in))
plaintext = uppercase(plaintext) plaintext = uppercase(plaintext)
keyarr = Int[div(Int(ch) - 65, 2) for ch in key] keyarr = Int[div(Int(ch) - 65, 2) for ch in key]
keycounter = 1 keycounter = 1
ans = IOBuffer() ans = IOBuffer()
for i in 1:length(plaintext) for i in 1:length(plaintext)
if ('A' <= plaintext[i] <= 'Z') if ('A' <= plaintext[i] <= 'Z')
plainch = Int(plaintext[i]) # 68 plainch = Int(plaintext[i]) # 68
keych = keyarr[keycounter] # 4 keych = keyarr[keycounter] # 4
if 'Z' >= plaintext[i] >= 'M' if 'Z' >= plaintext[i] >= 'M'
print(ans, Char(((plainch - 65 - keych + 13) % 13) + 65)) print(ans, Char(((plainch - 65 - keych + 13) % 13) + 65))
else else
print(ans, Char(((plainch - 65 + keych) % 13) + 65+13)) print(ans, Char(((plainch - 65 + keych) % 13) + 65+13))
end end
keycounter += 1 keycounter += 1
if keycounter == length(key) + 1 if keycounter == length(key) + 1
keycounter = 1 keycounter = 1
end end
else else
print(ans, plaintext[i]) print(ans, plaintext[i])
end end
end end
String(take!(ans)) return String(take!(ans))
end end
""" """
@@ -43,5 +43,5 @@ The key must be given as a string, whose characters are letters.
Converts the text to lowercase. Converts the text to lowercase.
""" """
function decrypt_portas(ciphertext, key::AbstractString) function decrypt_portas(ciphertext, key::AbstractString)
lowercase(encrypt_portas(ciphertext, key)) return lowercase(encrypt_portas(ciphertext, key))
end end

View File

@@ -1,88 +1,89 @@
function next_solitaire(deckIn::AbstractVector{T}) where {T <: Integer} function next_solitaire(deckIn::AbstractVector{T}) where {T <: Integer}
# performs one round of Solitaire on the given deck # performs one round of Solitaire on the given deck
# first joker # first joker
deck = deckIn deck = deckIn
jokerPos = findfirst(i -> i == 53, deck) jokerPos = findfirst(i -> i == 53, deck)
if jokerPos != length(deck) if jokerPos != length(deck)
inter = deck[jokerPos + 1] inter = deck[jokerPos + 1]
deck[jokerPos + 1] = deck[jokerPos] deck[jokerPos + 1] = deck[jokerPos]
deck[jokerPos] = inter deck[jokerPos] = inter
else else
inter = deck[end] inter = deck[end]
deck[end] = deck[1] deck[end] = deck[1]
deck[1] = inter deck[1] = inter
deck = rotateRight(deck) deck = rotate_right(deck)
end end
# second joker # second joker
jokerPos = findfirst(i -> i == 54, deck) jokerPos = findfirst(i -> i == 54, deck)
if jokerPos <= length(deck) - 2 if jokerPos <= length(deck) - 2
inter = deck[jokerPos] inter = deck[jokerPos]
deck[jokerPos] = deck[jokerPos + 1] deck[jokerPos] = deck[jokerPos + 1]
deck[jokerPos + 1] = deck[jokerPos + 2] deck[jokerPos + 1] = deck[jokerPos + 2]
deck[jokerPos + 2] = inter deck[jokerPos + 2] = inter
elseif jokerPos == length(deck) - 1 elseif jokerPos == length(deck) - 1
inter = deck[length(deck) - 1] inter = deck[length(deck) - 1]
inter1 = deck[length(deck)] inter1 = deck[length(deck)]
deck[end] = deck[1] deck[end] = deck[1]
deck[length(deck)-1] = inter1 deck[length(deck)-1] = inter1
deck[1] = inter deck[1] = inter
deck = rotateRight(deck, 1) deck = rotate_right(deck, 1)
elseif jokerPos == length(deck) elseif jokerPos == length(deck)
inter = deck[1] inter = deck[1]
inter1 = deck[end] inter1 = deck[end]
deck[1] = deck[2] deck[1] = deck[2]
deck[2] = inter1 deck[2] = inter1
deck[end] = inter deck[end] = inter
deck = rotateRight(deck, 1) deck = rotate_right(deck, 1)
end end
# triple-cut # triple-cut
split_deck = splitBy(deck, x -> x > 52) split_deck = split_by(deck, x -> x > 52)
if deck[1] > 52 && deck[end] > 52 if deck[1] > 52 && deck[end] > 52
# do nothing # do nothing
elseif deck[1] > 52 elseif deck[1] > 52
split_deck = rotateRight(split_deck, 1) split_deck = rotate_right(split_deck, 1)
elseif deck[end] > 52 elseif deck[end] > 52
split_deck = rotateLeft(split_deck, 1) split_deck = rotate_left(split_deck, 1)
else else
inter = split_deck[1] inter = split_deck[1]
split_deck[1] = split_deck[end] split_deck[1] = split_deck[end]
split_deck[end] = inter split_deck[end] = inter
end end
deck = collect(Iterators.flatten(split_deck)) deck = collect(Iterators.flatten(split_deck))
# take bottom of deck and put it just above last card # take bottom of deck and put it just above last card
cardsToTake = deck[end] > 52 ? 0 : deck[end] cardsToTake = deck[end] > 52 ? 0 : deck[end]
intermediate = rotateLeft(deck[1:length(deck) - 1], cardsToTake) intermediate = rotate_left(deck[1:length(deck) - 1], cardsToTake)
append!(intermediate, [deck[end]]) append!(intermediate, [deck[end]])
deck = intermediate deck = intermediate
return collect(deck) return collect(deck)
end end
function keychar_from_deck(deck::AbstractVector{T}) where {T <: Integer} function keychar_from_deck(deck::AbstractVector{T}) where {T <: Integer}
# given a deck, returns an integer which is the Solitaire key value # given a deck, returns an integer which is the Solitaire key value
# output by that deck # output by that deck
deck[((deck[1] == 54 ? 53 : deck[1]) % length(deck)) + 1] return deck[((deck[1] == 54 ? 53 : deck[1]) % length(deck)) + 1]
end end
struct SolitaireKeyStreamStruct struct SolitaireKeyStreamStruct
deck::AbstractVector{Integer} deck::AbstractVector{Integer}
end end
function Base.iterate(b::SolitaireKeyStreamStruct) function Base.iterate(b::SolitaireKeyStreamStruct)
(0, next_solitaire(b.deck)) return 0, next_solitaire(b.deck)
end end
function Base.iterate(b::SolitaireKeyStreamStruct, state) function Base.iterate(b::SolitaireKeyStreamStruct, state)
curState = state::AbstractVector{Integer} curState = state::AbstractVector{Integer}
while keychar_from_deck(curState) > 52 while keychar_from_deck(curState) > 52
curState = next_solitaire(curState) curState = next_solitaire(curState)
end end
(keychar_from_deck(curState)::Integer, next_solitaire(curState))
return keychar_from_deck(curState), next_solitaire(curState)
end end
function SolitaireKeyStream(initialDeck::AbstractVector{T}) where {T <: Integer} 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 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. 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} function encrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T}) where {T <: Integer}
inp = uppercase(letters_only(string)) inp = uppercase(letters_only(string))
ans = "" ans = ""
for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp)) for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp))
ans *= encrypt_caesar(input, keyval) ans *= encrypt_caesar(input, keyval)
end end
return ans
return ans
end 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. 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} function decrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T}) where {T <: Integer}
inp = uppercase(letters_only(string)) inp = uppercase(letters_only(string))
ans = "" ans = ""
i = 0 i = 0
for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp)) for (keyval, input) in zip(SolitaireKeyStream(initialDeck), collect(inp))
ans *= decrypt_caesar(input, keyval) ans *= decrypt_caesar(input, keyval)
end end
return ans
return ans
end end
function key_deck(key::AbstractString) function key_deck(key::AbstractString)
# returns the Solitaire deck after it has been keyed with the given string # returns the Solitaire deck after it has been keyed with the given string
deck = collect(1:54) deck = collect(1:54)
for keyval in uppercase(letters_only(key)) for keyval in uppercase(letters_only(key))
deck = next_solitaire(deck) deck = next_solitaire(deck)
cardsToTake = Int(keyval) - 64 cardsToTake = Int(keyval) - 64
intermediate = rotateLeft(deck[1:(length(deck) - 1)], cardsToTake) intermediate = rotate_left(deck[1:(length(deck) - 1)], cardsToTake)
append!(intermediate, [deck[end]]) append!(intermediate, [deck[end]])
deck = intermediate deck = intermediate
end end
deck
return deck
end end
function encrypt_solitaire(string::AbstractString, key::AbstractString) :: AbstractString function encrypt_solitaire(string::AbstractString, key::AbstractString) :: AbstractString
key = key_deck(key) key = key_deck(key)
encrypt_solitaire(string, key) return encrypt_solitaire(string, key)
end end
function decrypt_solitaire(string::AbstractString, key::AbstractString) function decrypt_solitaire(string::AbstractString, key::AbstractString)
decrypt_solitaire(string, key_deck(key)) return decrypt_solitaire(string, key_deck(key))
end end

View File

@@ -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". For example, encrypt_vigenere("ab", [0, 1]) returns "AC".
""" """
function encrypt_vigenere(plaintext, key::Array) function encrypt_vigenere(plaintext, key::Array)
# plaintext: string; key: vector of integer offsets, so [0, 1] encrypts "ab" as "ac" # 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))] ans = String[encrypt_caesar(chr, key[(i - 1) % length(key) + 1]) for (i, chr) in enumerate(letters_only(plaintext))]
join(ans) return join(ans)
end 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". For example, decrypt_vigenere("ac", [0, 1]) returns "ab".
""" """
function decrypt_vigenere(ciphertext, key::Array) function decrypt_vigenere(ciphertext, key::Array)
# ciphertext: string; key: vector of integer offsets, so [0, 1] decrypts "ac" as "ab" # ciphertext: string; key: vector of integer offsets, so [0, 1] decrypts "ac" as "ab"
lowercase(encrypt_vigenere(ciphertext, map(x -> 26 - x, key))) return lowercase(encrypt_vigenere(ciphertext, map(x -> 26 - x, key)))
end 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". For example, encrypt_vigenere("ab", "ab") returns "AC".
""" """
function encrypt_vigenere(ciphertext, key::AbstractString) function encrypt_vigenere(ciphertext, key::AbstractString)
# ciphertext: string; key: string, so "ab" encrypts "ab" as "AC" # ciphertext: string; key: string, so "ab" encrypts "ab" as "AC"
encrypt_vigenere(ciphertext, Int[Int(i) - 97 for i in lowercase(letters_only(key))]) return encrypt_vigenere(ciphertext, Int[Int(i) - 97 for i in lowercase(letters_only(key))])
end 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". For example, decrypt_vigenere("ab", "ac") returns "ab".
""" """
function decrypt_vigenere(plaintext, key::AbstractString) 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[Int(i) - 97 for i in lowercase(letters_only(key))]) return decrypt_vigenere(plaintext, Int[Int(i) - 97 for i in lowercase(letters_only(key))])
end end
""" """
@@ -44,28 +44,28 @@ Returns (derived key, decrypted plaintext).
Optional parameters: Optional parameters:
keylength=0: if the key length is known, specifying it may help the solver. 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 If 0, the solver will attempt to derive the key length using the index
of coincidence. of coincidence.
""" """
function crack_vigenere(plaintext; keylength::Integer = 0) function crack_vigenere(plaintext; keylength::Integer = 0)
stripped_text = letters_only(lowercase(plaintext)) stripped_text = letters_only(lowercase(plaintext))
if keylength == 0 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])) 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] keylength = lens[end]
end end
everyother = String[stripped_text[i:keylength:end] for i in 1:keylength] everyother = String[stripped_text[i:keylength:end] for i in 1:keylength]
decr = String[crack_caesar(st)[1] for st in everyother] decr = String[crack_caesar(st)[1] for st in everyother]
ans = IOBuffer() ans = IOBuffer()
for column in 1:length(decr[1]) for column in 1:length(decr[1])
for j in 1:keylength for j in 1:keylength
if column <= length(decr[j]) if column <= length(decr[j])
print(ans, decr[j][column]) print(ans, decr[j][column])
end end
end end
end end
derived_key = join(Char[Char(65 + crack_caesar(st)[2]) for st in everyother]) derived_key = join(Char[Char(65 + crack_caesar(st)[2]) for st in everyother])
(derived_key, String(take!(ans))) return derived_key, String(take!(ans))
end end