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)
[![Coverage Status](https://coveralls.io/repos/Smaug123/ClassicalCiphers.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/Smaug123/ClassicalCiphers.jl?branch=master)
[![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)
# 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!"
```

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

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".
"""
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