From 09f71792f3a3cee365ebb49dec8bec06e3053fb5 Mon Sep 17 00:00:00 2001 From: "Jake W. Ireland" Date: Fri, 8 Jan 2021 02:48:44 +1300 Subject: [PATCH] Refined code style to be more in line with Blue Code Style guide --- README.md | 6 +- src/affine.jl | 11 ++- src/caesar.jl | 29 +++--- src/common.jl | 159 ++++++++++++++++----------------- src/enigma.jl | 11 +-- src/hill.jl | 13 +-- src/monoalphabetic.jl | 201 ++++++++++++++++++++++-------------------- src/playfair.jl | 22 ++--- src/portas.jl | 48 +++++----- src/solitaire.jl | 190 ++++++++++++++++++++------------------- src/vigenere.jl | 56 ++++++------ 11 files changed, 378 insertions(+), 368 deletions(-) diff --git a/README.md b/README.md index 32d1d5a..377b70c 100644 --- a/README.md +++ b/README.md @@ -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!" ``` diff --git a/src/affine.jl b/src/affine.jl index 0510df8..5e6f7b9 100644 --- a/src/affine.jl +++ b/src/affine.jl @@ -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 diff --git a/src/caesar.jl b/src/caesar.jl index d620180..fa98961 100644 --- a/src/caesar.jl +++ b/src/caesar.jl @@ -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 diff --git a/src/common.jl b/src/common.jl index 3682250..4d3fa8c 100644 --- a/src/common.jl +++ b/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 diff --git a/src/enigma.jl b/src/enigma.jl index 6c671f9..14ea115 100644 --- a/src/enigma.jl +++ b/src/enigma.jl @@ -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 diff --git a/src/hill.jl b/src/hill.jl index a7cf1ed..8998a8f 100644 --- a/src/hill.jl +++ b/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 diff --git a/src/monoalphabetic.jl b/src/monoalphabetic.jl index db18dd3..d774b19 100644 --- a/src/monoalphabetic.jl +++ b/src/monoalphabetic.jl @@ -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 diff --git a/src/playfair.jl b/src/playfair.jl index cbe6b97..94f2903 100644 --- a/src/playfair.jl +++ b/src/playfair.jl @@ -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 diff --git a/src/portas.jl b/src/portas.jl index 275eaaf..45b2086 100644 --- a/src/portas.jl +++ b/src/portas.jl @@ -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 diff --git a/src/solitaire.jl b/src/solitaire.jl index aa4afbf..8c0145a 100644 --- a/src/solitaire.jl +++ b/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 diff --git a/src/vigenere.jl b/src/vigenere.jl index b2f3ef0..7ea736d 100644 --- a/src/vigenere.jl +++ b/src/vigenere.jl @@ -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