Refined docstrings and added documentation

This commit is contained in:
Jake W. Ireland
2021-01-13 00:39:37 +13:00
parent 46cf9ca9fa
commit 6b9a54fdd9
21 changed files with 743 additions and 95 deletions

106
.github/workflows/CI.yml vendored Normal file
View File

@@ -0,0 +1,106 @@
name: CI
# Run on master, tags, or any pull request
on:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM UTC (8 PM CST)
push:
branches: [master]
tags: ["*"]
pull_request:
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- "1.0" # LTS
- "1.1"
- "1.5"
- "nightly"
os:
- ubuntu-latest
- macOS-latest
- windows-latest
arch:
- x64
- x86
exclude:
# Test 32-bit only on Linux
- os: macOS-latest
arch: x86
- os: windows-latest
arch: x86
include:
# Add a 1.3 job because that's what Invenia actually uses
- os: ubuntu-latest
version: 1.3
arch: x64
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v1
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/julia-buildpkg@latest
- run: |
git config --global user.name Tester
git config --global user.email te@st.er
- uses: julia-actions/julia-runtest@latest
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
with:
file: lcov.info
slack:
name: Notify Slack Failure
needs: test
runs-on: ubuntu-latest
if: github.event == 'schedule'
steps:
- uses: technote-space/workflow-conclusion-action@v2
- uses: voxmedia/github-action-slack-notify-build@v1
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
channel: nightly-dev
status: FAILED
color: danger
env:
SLACK_BOT_TOKEN: ${{ secrets.DEV_SLACK_BOT_TOKEN }}
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: '1'
- run: |
git config --global user.name name
git config --global user.email email
git config --global github.user username
- run: |
julia --project=docs -e '
using Pkg;
Pkg.develop(PackageSpec(path=pwd()));
Pkg.instantiate();'
- run: |
julia --project=docs -e '
using Documenter: doctest
using PkgTemplates
doctest(PkgTemplates)'
- run: julia --project=docs docs/make.jl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

16
.github/workflows/CompatHelper.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: CompatHelper
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
jobs:
CompatHelper:
runs-on: ubuntu-latest
steps:
- name: Pkg.add("CompatHelper")
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
- name: CompatHelper.main()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
run: julia -e 'using CompatHelper; CompatHelper.main()'

13
.github/workflows/TagBot.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: TagBot
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
jobs:
TagBot:
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh: ${{ secrets.DOCUMENTER_KEY }}

24
.gitignore vendored
View File

@@ -1,5 +1,27 @@
# Files generated by invoking Julia with --code-coverage
*.jl.cov *.jl.cov
*.jl.*.cov *.jl.*.cov
# Files generated by invoking Julia with --track-allocation
*.jl.mem *.jl.mem
# System-specific files and directories generated by the BinaryProvider and BinDeps packages
# They contain absolute paths specific to the host computer, and so should not be committed
deps/deps.jl
deps/build.log
deps/downloads/
deps/usr/
deps/src/
# Build artifacts for creating documentation generated by the Documenter package
docs/build/
docs/site/
# File generated by Pkg, the package manager, based on a corresponding Project.toml
# It records a fixed state of all packages used by the project. As such, it should not be
# committed for packages, but should be committed for applications that require a static
# environment.
Manifest.toml Manifest.toml
.vscode/*
# Silly macOS stuff
.DS_Store

View File

@@ -1,10 +1,17 @@
[![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) <h1 align="center">
ClassicalCiphers.jl
</h1>
# ClassicalCiphers <!-- [![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://username.github.io/MyCoolPackage.jl/stable) -->
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://username.github.io/MyCoolPackage.jl/dev)
[![CI](https://github.com/invenia/PkgTemplates.jl/workflows/CI/badge.svg)](https://github.com/username/MyCoolPackage.jl/actions?query=workflow%3ACI)
[![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)
## Main Features ## Main Features
Provides access to encryption and decryption of strings according to a variety of classical algorithms. Provides access to encryption and decryption of strings according to a variety of classical algorithms. Classical ciphers were created before computers, and thus work on letters rather than bits.
The Solitaire cipher is included for completeness, though it is perhaps not strictly classical. The Solitaire cipher is included for completeness, though it is perhaps not strictly classical.
## Currently Implemented ## Currently Implemented
@@ -20,6 +27,7 @@ The Solitaire cipher is included for completeness, though it is perhaps not stri
* [Solitaire] * [Solitaire]
* [Rail Fence] * [Rail Fence]
* [Substitution] * [Substitution]
* [Atbash]
## Gotchas ## Gotchas
@@ -398,6 +406,12 @@ julia> decrypt_atbash("HLNV GVCG", "abcdefghijklmnopqrstuvwxyz")
"some text" "some text"
``` ```
### Atbash
```julia
encrypt_atbash("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz") == encrypt_substitution("hello this is plaintext", "abcdefghijklmnopqrstuvwxyz", "zyxwvutsrqponmlkjihgfedcba")
```
[Caesar]: https://en.wikipedia.org/wiki/Caesar_cipher [Caesar]: https://en.wikipedia.org/wiki/Caesar_cipher
[Affine]: https://en.wikipedia.org/wiki/Affine_cipher [Affine]: https://en.wikipedia.org/wiki/Affine_cipher
[Vigenère]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher [Vigenère]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
@@ -409,3 +423,4 @@ julia> decrypt_atbash("HLNV GVCG", "abcdefghijklmnopqrstuvwxyz")
[Enigma]: https://en.wikipedia.org/wiki/Enigma_machine [Enigma]: https://en.wikipedia.org/wiki/Enigma_machine
[Rail Fence]: https://en.wikipedia.org/wiki/Rail_fence_cipher [Rail Fence]: https://en.wikipedia.org/wiki/Rail_fence_cipher
[Substitution]: https://en.wikipedia.org/wiki/Substitution_cipher [Substitution]: https://en.wikipedia.org/wiki/Substitution_cipher
[Atbash]: https://en.wikipedia.org/wiki/Atbash

3
docs/Project.toml Normal file
View File

@@ -0,0 +1,3 @@
[deps]
ClassicalCiphers = "ecf26e93-935c-5e64-9b21-bc8ac81b4723"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"

20
docs/make.jl Normal file
View File

@@ -0,0 +1,20 @@
include(joinpath(dirname(@__DIR__), "src", "ClassicalCiphers.jl"))
using Documenter, .ClassicalCiphers
Documenter.makedocs(
clean = true,
doctest = true,
modules = Module[ClassicalCiphers],
repo = "",
highlightsig = true,
sitename = "ClassicalCiphers Documentation",
expandfirst = [],
pages = [
"Index" => "index.md",
"Usage" => "usage.md"
]
)
deploydocs(;
repo = "github.com/Smaug123/ClassicalCiphers.jl.git",
)

41
docs/src/index.md Normal file
View File

@@ -0,0 +1,41 @@
# Home
This package provides access to encryption and decryption of strings according to a variety of classical algorithms. Classical ciphers were created before computers, and thus work on letters rather than bits.
The Solitaire cipher is included for completeness, though it is perhaps not strictly classical.
Currently implemented ciphers:
- [Caesar](https://en.wikipedia.org/wiki/Caesar_cipher)
- [Affine](https://en.wikipedia.org/wiki/Affine_cipher)
- [Monoalphabetic substitution](https://en.wikipedia.org/wiki/Substitution_cipher)
- [Vigenère](https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher)
- [Portas](http://practicalcryptography.com/ciphers/porta-cipher/)
- [Hill](https://en.wikipedia.org/wiki/Hill_cipher)
- [Playfair](https://en.wikipedia.org/wiki/Playfair_cipher)
- [Enigma (M3 Army)](https://en.wikipedia.org/wiki/Enigma_machine)
- [Solitaire](https://en.wikipedia.org/wiki/Solitaire_(cipher))
- [Rail Fence](https://en.wikipedia.org/wiki/Rail_fence_cipher)
- [Substitution](https://en.wikipedia.org/wiki/Substitution_cipher)
- [Atbash](https://en.wikipedia.org/wiki/Atbash)
## Contents
```@contents
```
```@meta
CurrentModule = ClassicalCiphers
DocTestSetup = quote
using ClassicalCiphers
end
```
## Installing ClassicalCiphers.jl
```@repl
using Pkg
Pkg.add("ClassicalCiphers")
```
## Index
```@index
```

5
docs/src/usage.md Normal file
View File

@@ -0,0 +1,5 @@
## Usage
```@autodocs
Modules = [ClassicalCiphers]
```

View File

@@ -1,4 +1,8 @@
""" """
```julia
encrypt_affine(plaintext, mult::Integer, add::Integer; offset::Integer = 0)
```
Encrypts the given plaintext according to the Affine cipher. Encrypts the given plaintext according to the Affine cipher.
The key is given as a pair of integers: first the multiplier, then The key is given as a pair of integers: first the multiplier, then
the additive constant. the additive constant.
@@ -7,10 +11,19 @@ The multiplier must be coprime to 26. If it is not, an error is thrown.
Converts the input to uppercase, but retains symbols. Converts the input to uppercase, but retains symbols.
Optional argument: offset=0, which specifies what number 'a' should be Optional argument: `offset=0`, which specifies what number 'a' should be
considered as. considered as.
---
### Examples
```julia
julia> encrypt_affine("Hello, World!", 3, 4)
"ZQLLU, SUDLN!"
```
""" """
function encrypt_affine(plaintext, mult::T, add::T; offset::T = 0) where {T <: Integer} function encrypt_affine(plaintext, mult::Integer, add::Integer; offset::Integer = 0)
if mult % 2 == 0 || mult % 13 == 0 if mult % 2 == 0 || mult % 13 == 0
error("Multiplier must be coprime to 26.") error("Multiplier must be coprime to 26.")
end end
@@ -20,6 +33,10 @@ function encrypt_affine(plaintext, mult::T, add::T; offset::T = 0) where {T <: I
end end
""" """
```julia
decrypt_affine(ciphertext, mult::Integer, add::Integer; offset::Integer=0)
```
Decrypts the given ciphertext according to the Affine cipher. Decrypts the given ciphertext according to the Affine cipher.
The key is given as a pair of integers: first the multiplier, then The key is given as a pair of integers: first the multiplier, then
the additive constant. the additive constant.
@@ -28,10 +45,19 @@ The multiplier must be coprime to 26. If it is not, an error is thrown.
Converts the input to lowercase, but retains symbols. Converts the input to lowercase, but retains symbols.
Optional argument: offset=0, which specifies what number 'a' should be Optional argument: `offset=0`, which specifies what number 'a' should be
considered as. considered as.
---
### Examples
```julia
julia> decrypt_affine("ZQLLU, SUDLN!", 3, 4)
"hello, world!"
```
""" """
function decrypt_affine(ciphertext, mult::T, add::T; offset=0) where {T <: Integer} function decrypt_affine(ciphertext, mult::Integer, add::Integer; offset::Integer = 0)
if mult % 2 == 0 || mult % 13 == 0 if mult % 2 == 0 || mult % 13 == 0
error("Multiplier must be coprime to 26.") error("Multiplier must be coprime to 26.")
end end
@@ -61,15 +87,28 @@ function max_by(arr::AbstractArray, f::Function)
end end
""" """
```julia
crack_affine(ciphertext; mult::Integer = 0, add::Integer = -1)
```
Cracks the given ciphertext according to the Affine cipher. Cracks the given ciphertext according to the Affine cipher.
Returns ((multiplier, additive constant), decrypted string). Returns `((multiplier, additive constant), decrypted string)`.
Converts the input to lowercase, but retains symbols. Converts the input to lowercase, but retains symbols.
Optional arguments: mult=0, which specifies the multiplier if known; Optional arguments: `mult=0`, which specifies the multiplier if known;
add=-1, which specifies the additive constant if known. `add=-1`, which specifies the additive constant if known.
---
### Examples
```julia
julia> crack_affine("ZQLLU, SUDLN!")
("hello, world!", (3, 4))
```
""" """
function crack_affine(ciphertext; mult::T = 0, add::T = -1) where {T <: Integer} function crack_affine(ciphertext; mult::Integer = 0, add::Integer = -1)
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)

View File

@@ -1,14 +1,28 @@
""" """
```julia
encrypt_caesar(plaintext, key::Integer)
encrypt_caesar(plaintext)
```
Encrypts the given plaintext according to the Caesar cipher. Encrypts the given plaintext according to the Caesar cipher.
The key is given as an integer, being the offset of each character; The key is given as an integer, being the offset of each character;
so encrypt_caesar("abc", 1) == "BCD". so `encrypt_caesar("abc", 1) == "BCD"`.
Converts the input to uppercase. Converts the input to uppercase, but retains symbols.
Traditionally, the Caesar cipher was used with a shift of 3, so this is the method it will Traditionally, the Caesar cipher was used with a shift of 3, so this is the method it will
fall back to if only given plaintext. fall back to if only given plaintext.
---
### Examples
```julia
julia> encrypt_caesar("Hello, World!", 3)
"KHOOR, ZRUOG!"
```
""" """
function encrypt_caesar(plaintext, key::T) where {T <: Integer} function encrypt_caesar(plaintext, key::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))))
@@ -17,16 +31,30 @@ end
encrypt_caesar(plaintext) = encrypt_caesar(plaintext, 3) encrypt_caesar(plaintext) = encrypt_caesar(plaintext, 3)
""" """
```julia
decrypt_caesar(ciphertext, key::Integer)
decrypt_caesar(ciphertext)
```
Decrypts the given ciphertext according to the Caesar cipher. Decrypts the given ciphertext according to the Caesar cipher.
The key is given as an integer, being the offset of each character; The key is given as an integer, being the offset of each character;
so decrypt_caesar("abcd", 1) == "zabc". so `decrypt_caesar("abcd", 1) == "zabc"`.
Converts the input to lowercase. Converts the input to lowercase, but retains symbols.
Traditionally, the Caesar cipher was used with a shift of 3, so this is the method it will Traditionally, the Caesar cipher was used with a shift of 3, so this is the method it will
fall back to if only given plaintext. fall back to if only given plaintext.
---
### Examples
```julia
julia> decrypt_caesar("Khoor, Zruog!", 3)
"hello, world!"
```
""" """
function decrypt_caesar(ciphertext, key::T) where {T <: Integer} function decrypt_caesar(ciphertext, key::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
return lowercase(encrypt_caesar(ciphertext, 26 - key)) return lowercase(encrypt_caesar(ciphertext, 26 - key))
@@ -34,22 +62,36 @@ end
decrypt_caesar(plaintext) = decrypt_caesar(plaintext, 3) decrypt_caesar(plaintext) = decrypt_caesar(plaintext, 3)
""" """
```julia
crack_caesar(ciphertext; cleverness::Integer = 1)
```
Cracks the given ciphertext according to the Caesar cipher. Cracks the given ciphertext according to the Caesar cipher.
Returns (plaintext, key::Integer), such that encrypt_caesar(plaintext, key) Returns `(plaintext, key::Integer)`, such that `encrypt_caesar(plaintext, key)`
would return ciphertext. would return ciphertext.
With cleverness=0, simply does the shift that maximises e's frequency. With `cleverness=0`, simply does the shift that maximises e's frequency.
With cleverness=1, maximises the string's total fitness. With `cleverness=1`, maximises the string's total fitness.
Converts the input to lowercase. Converts the input to lowercase.
---
### Examples
```julia
julia> crack_caesar("Khoor, Zruog!")
("hello, world!", 3)
```
""" """
function crack_caesar(ciphertext; cleverness::T = 1) where {T <: Integer} function crack_caesar(ciphertext; cleverness::Integer = 1)
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
return texts[end] return last(texts)
end end

View File

@@ -3,7 +3,7 @@ function letters_only(text::AbstractString)
return filter(x -> ('A' <= x <= 'Z' || 'a' <= x <= 'z'), text) return filter(x -> ('A' <= x <= 'Z' || 'a' <= x <= 'z'), text)
end end
function rotate_right(arr::AbstractVector, n::T) where {T <: Integer} function rotate_right(arr::AbstractVector, n::Integer)
# implementation of the Mathematica function rotate_right - 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)
@@ -13,7 +13,7 @@ function rotate_right(arr::AbstractVector, n::T) where {T <: Integer}
return ans return ans
end end
function rotate_left(arr::AbstractVector, n::T) where {T <: Integer} function rotate_left(arr::AbstractVector, n::Integer)
# implementation of the Mathematica function rotate_left # implementation of the Mathematica function rotate_left
ans = copy(arr) ans = copy(arr)
for i in 1:length(arr) for i in 1:length(arr)
@@ -23,9 +23,9 @@ function rotate_left(arr::AbstractVector, n::T) where {T <: Integer}
return ans return ans
end end
rotate_left_str(st::AbstractString, n::T) where {T <: Integer} = rotate_left_str(st::AbstractString, n::Integer) =
join(rotate_left(collect(st), n)) join(rotate_left(collect(st), n))
rotate_right_str(st::AbstractString, n::T) where {T <: Integer} = rotate_right_str(st::AbstractString, n::Integer) =
join(rotate_right(collect(st), n)) join(rotate_right(collect(st), n))
function split_by(arr::AbstractVector, func::Function) function split_by(arr::AbstractVector, func::Function)
@@ -85,8 +85,12 @@ function string_fitness(input::AbstractString; alreadystripped::Bool = false)
end end
""" """
Finds the frequencies of all characters in the input string, returning a Dict ```julia
of 'a' => 4, for instance. Uppercase characters are considered distinct from lowercase. frequencies(input::AbstractString)
```
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) function frequencies(input::AbstractString)
ans = Dict{Char, Int}() ans = Dict{Char, Int}()
@@ -102,6 +106,10 @@ function frequencies(input::AbstractString)
end end
""" """
```julia
index_of_coincidence(input::AbstractString)
```
Finds the index of coincidence of the input string. Uppercase characters are considered to be Finds the index of coincidence of the input string. Uppercase characters are considered to be
equal to their lowercase counterparts. equal to their lowercase counterparts.
""" """

View File

@@ -55,30 +55,57 @@ function parse_reflector(reflector::AbstractString)
end end
""" """
```julia
function encrypt_enigma(plaintext,
rotors::Array{Integer, 1}, key::AbstractString;
reflector_id='B', ring::AbstractString = "AAA",
stecker = Tuple{Char, Char}[],
skip_stecker_check = false)
```
Encrypts the given plaintext according to the Enigma (M3, army version). Encrypts the given plaintext according to the Enigma (M3, army version).
Arguments are in the order: plaintext, stecker, rotors, ring, key. Arguments are in the order: `plaintext, stecker, rotors, ring, key.`
Plaintext is a string; punctuation is stripped out and it is made lowercase. Plaintext is a string; punctuation is stripped out and it is made lowercase.
Rotors is an array - for example, [1,2,3] - being the order of the rotors. Rotors is an array - for example, `[1,2,3]` - being the order of the rotors.
Each entry should be a distinct integer between 1 and 5 inclusive. Each entry should be a distinct integer between 1 and 5 inclusive.
Key is a string of three letters, indicating the starting positions of the rotors. Key is a string of three letters, indicating the starting positions of the rotors.
Optional: Optional:
reflector_id='B', which sets whether to use reflector A, B or C. * `reflector_id='B'`, which sets whether to use reflector A, B or C.
Can also be specified as a 26-char string. Can also be specified as a 26-char string.
Stecker is either an array - for example, [('A','B'), ('D', 'E')] specifying * Stecker is either an array - for example,` [('A','B'), ('D', 'E')]` specifying
that A, B are swapped and D, E are swapped - or a string ("ABDE" accomplishing that A, B are swapped and D, E are swapped - or a string ("ABDE" accomplishing
the same thing). No letter may appear more than once. the same thing). No letter may appear more than once.
Ring is a string - for example, "AAA" - being the offset applied to each rotor. * Ring is a string - for example, "AAA" - being the offset applied to each rotor.
"AAA", for example, signifies no offset. The string must be three letters. "AAA", for example, signifies no offset. The string must be three letters.
skip_stecker_check=false, which when `true` skips validation of stecker settings. * `skip_stecker_check=false`, which when `true` skips validation of stecker settings.
---
### Examples
```julia
julia> encrypt_enigma("AAA", [1,2,3], "ABC")
"CXT"
julia> encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker="") # synonymous with above
"CXT"
julia> encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id="YRUHQSLDPXNGOKMIEBFZCWVJAT", stecker="") # synonymous with above
"CXT"
julia> encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker=Tuple{Char, Char}[]) # synonymous with above
"CXT"
```
""" """
function encrypt_enigma(plaintext, function encrypt_enigma(plaintext,
rotors::Array{T, 1}, key::AbstractString; rotors::Array{T, 1}, key::AbstractString;
reflector_id='B', ring::AbstractString = "AAA", reflector_id='B', ring::AbstractString = "AAA",
stecker = Tuple{Char, Char}[], stecker = Tuple{Char, Char}[],
skip_stecker_check = false) where {T <: Integer} skip_stecker_check = false) where T <: Integer
parsed_stecker = parse_stecker(stecker) parsed_stecker = parse_stecker(stecker)
# validate stecker settings # validate stecker settings
if !skip_stecker_check if !skip_stecker_check
@@ -243,5 +270,8 @@ function encrypt_enigma(plaintext,
return uppercase(String(take!(ans))) return uppercase(String(take!(ans)))
end end
"""
See `encrypt_enigma` as this function uses identical arguments.
"""
decrypt_enigma(args1...; args2...) = decrypt_enigma(args1...; args2...) =
lowercase(encrypt_enigma(args1...; args2...)) lowercase(encrypt_enigma(args1...; args2...))

View File

@@ -1,8 +1,12 @@
using LinearAlgebra using LinearAlgebra
""" """
```julia
encrypt_hill(plaintext::AbstractString, key::AbstractArray{Integer, 2})
```
Encrypts the given plaintext according to the Hill cipher. Encrypts the given plaintext according to the Hill cipher.
The key may be given as a matrix (that is, two-dimensional Array{Int}) or as a string. The key may be given as a matrix (that is, two-dimensional `Array{Int}`) or as a string.
If the key is given as a string, the string is converted to uppercase before use, and If the key is given as a string, the string is converted to uppercase before use, and
symbols are removed. It is assumed to be of square integer length, and the matrix entries symbols are removed. It is assumed to be of square integer length, and the matrix entries
@@ -11,6 +15,21 @@ to bottom-left to bottom-right. If the string is not of square integer length, a
is thrown. is thrown.
The matrix must be invertible modulo 26. If it is not, an error is thrown. The matrix must be invertible modulo 26. If it is not, an error is thrown.
---
### Examples
```julia
julia> encrypt_hill("Hello, World!", [1 2; 5 7]) # Encrypt the text "Hello, World!" with a Hill key of matrix `[1 2; 5 7]`
"PHHRGUWQRV"
julia> encrypt_hill("Hello, World!", "bcfh")
"PLHCGQWHRY"
julia> encrypt_hill("Hello", "bcfh") # If the plaintext-length is not a multiple of the dimension of the key matrix, it is padded with X
"PLHCIX"
```
""" """
function encrypt_hill(plaintext::AbstractString, key::AbstractArray{T, 2}) where {T <: Integer} function encrypt_hill(plaintext::AbstractString, key::AbstractArray{T, 2}) where {T <: Integer}
if round(Integer, det(key)) % 26 == 0 if round(Integer, det(key)) % 26 == 0
@@ -60,6 +79,10 @@ function minor(mat::AbstractArray{T, 2}, i::K, j::K) where {T <: Integer, K <: I
end end
""" """
```julia
adjugate(mat::AbstractArray{Integer, 2})
```
Computes the adjugate matrix for given matrix. 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}
@@ -68,6 +91,26 @@ function adjugate(mat::AbstractArray{T, 2}) where {T <: Integer}
return Array{Integer, 2}(transpose(ans)) return Array{Integer, 2}(transpose(ans))
end end
"""
```julia
decrypt_hill(ciphertext, key::AbstractArray{T, 2}) where {T <: Integer}
```
---
### Examples
```julia
julia> decrypt_hill("PLHCGQWHRY", [1 2; 5 7]) # Decrypt the text "PLHCGQWHRY" with key of `[1 2; 5 7]`
"helloworld"
julia> decrypt_hill("PLHCGQWHRY", "bcfh")
"helloworld"
julia> decrypt_hill("PLHCIX", "bcfh") # If the plaintext-length is not a multiple of the dimension of the key matrix, it is padded with X
"hellox"
```
"""
function decrypt_hill(ciphertext, key::AbstractArray{T, 2}) where {T <: Integer} function decrypt_hill(ciphertext, key::AbstractArray{T, 2}) where {T <: Integer}
if ndims(key) != 2 if ndims(key) != 2
error("Key must be a two-dimensional matrix.") error("Key must be a two-dimensional matrix.")

View File

@@ -3,15 +3,31 @@ function keystr_to_dict(keystr::AbstractString)
end end
""" """
```julia
encrypt_monoalphabetic(plaintext, key::Dict{Char, Char})
```
Encrypts the given plaintext according to the monoalphabetic substitution cipher. Encrypts the given plaintext according to the monoalphabetic substitution cipher.
The key may be given as a Dict of replacements {'a' => 'b', 'c' => 'd'}, etc, The key may be given as a Dict of replacements `Dict('a' => 'b', 'c' => 'd')`, etc,
or as a 26-length string "keystringbcdfhjlmopqruvwxz", which is shorthand for or as a 26-length string "keystringbcdfhjlmopqruvwxz", which is shorthand for
{'a' => 'k', 'e' => 'b', …} `Dict('a' => 'k', 'e' => 'b', ...)`
If the key is given as a string, it is assumed that each character occurs only If the key is given as a string, it is assumed that each character occurs only
once, and the string is converted to lowercase. once, and the string is converted to lowercase.
If the key is given as a Dict, the only substitutions made are those in the Dict; 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.
---
### Examples
```julia
julia> encrypt_monoalphabetic("Hello, World!", "DEFGHIJKLMNOPQRSTUVWXYZABC")
"KHOOR, ZRUOG!"
julia> encrypt_monoalphabetic("aBcbDd", Dict{Char, Char}('a' => '5', 'B' => '@', 'b' => 'o'))
"5@coDd"
```
""" """
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'
@@ -19,21 +35,34 @@ function encrypt_monoalphabetic(plaintext, key::Dict{Char, Char})
end end
""" """
```julia
decrypt_monoalphabetic(ciphertext, key::Dict{Char, Char})
```
Decrypts the given ciphertext according to the monoalphabetic substitution cipher. Decrypts the given ciphertext according to the monoalphabetic substitution cipher.
The key may be given as a Dict of replacements {'a' => 'b', 'c' => 'd'}, etc, The key may be given as a Dict of replacements `Dict('a' => 'b', 'c' => 'd')`, etc,
or as a 26-length string "keystringbcdfhjlmopqruvwxz", which is shorthand for or as a 26-length string "keystringbcdfhjlmopqruvwxz", which is shorthand for
{'a' => 'k', 'e' => 'b', …} `Dict('a' => 'k', 'e' => 'b', ...)`
If the key is given as a string, it is assumed that each character occurs only If the key is given as a string, it is assumed that each character occurs only
once, and the string is converted to lowercase. once, and the string is converted to lowercase.
If the key is given as a Dict, the only substitutions made are those in the Dict; 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.
---
### Examples
```julia
julia> decrypt_monoalphabetic("Khoor, Zruog!", "DEFGHIJKLMNOPQRSTUVWXYZABC")
"hello, world!"
```
""" """
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}.
return 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)
@@ -47,7 +76,7 @@ function decrypt_monoalphabetic(ciphertext, key::AbstractString)
# 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(==(a), lowercase(key))) for a in lowercase(key))
return encrypt_monoalphabetic(lowercase(ciphertext), dict) return encrypt_monoalphabetic(lowercase(ciphertext), dict)
end end
@@ -56,6 +85,10 @@ end
# The method we use for cracking is simulated annealing. # The method we use for cracking is simulated annealing.
""" """
```julia
swap_two(str)
```
swap_two(string) swaps two of the characters of the input string, at random. swap_two(string) swaps two of the characters of the input string, at random.
The characters are guaranteed to be at different positions, though "aa" would be The characters are guaranteed to be at different positions, though "aa" would be
'swapped' to "aa". 'swapped' to "aa".
@@ -70,34 +103,55 @@ function swap_two(str)
end end
""" """
```julia
crack_monoalphabetic(
ciphertext;
starting_key::AbstractString = "",
min_temp::AbstractFloat = 0.0001,
temp_factor::AbstractFloat = 0.97,
acceptance_prob::AbstractFloat = ((e,ep,t) -> ep > e ? 1. : exp(-(e-ep)/t)),
chatty::Integer = 0,
rounds::Integer = 1
)
```
crack_monoalphabetic cracks the given ciphertext which was encrypted by the monoalphabetic crack_monoalphabetic cracks the given ciphertext which was encrypted by the monoalphabetic
substitution cipher. substitution cipher.
Returns (the derived key, decrypted plaintext). Returns `(the derived key, decrypted plaintext)`.
Possible arguments include: The various optional arguments to `crack_monoalphabetic` are:
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.
---
### Examples
```julia
julia> crack_monoalphabetic(str, chatty=0, rounds=10)
(decrypted_string, key)
```
""" """
function crack_monoalphabetic( function crack_monoalphabetic(
ciphertext; ciphertext;
starting_key::AbstractString = "", starting_key::AbstractString = "",
min_temp::F = 0.0001, min_temp::AbstractFloat = 0.0001,
temp_factor::F = 0.97, temp_factor::AbstractFloat = 0.97,
acceptance_prob::F = ((e,ep,t) -> ep > e ? 1. : exp(-(e-ep)/t)), acceptance_prob::AbstractFloat = ((e,ep,t) -> ep > e ? 1. : exp(-(e-ep)/t)),
chatty::T = 0, chatty::Integer = 0,
rounds::T = 1 rounds::Integer = 1
) where {T <: Integer, F <: AbstractFloat} )
if isempty(starting_key) if isempty(starting_key)
# most common letters # most common letters

View File

@@ -1,11 +1,23 @@
"""
```julia
AbstractPair{F, S} = Union{Tuple{F, S}, Pair{F, S}} AbstractPair{F, S} = Union{Tuple{F, S}, Pair{F, S}}
```
A simple wrapper for a `Pair`, or a `Tuple` representing a pair of objects.
"""
AbstractPair{F, S} = Union{Tuple{F, S}, Pair{F, S}}
parse_abstract_pair(P::AbstractPair) = parse_abstract_pair(P::AbstractPair) =
P isa Tuple{Char, Char} ? Dict(reverse(Pair(P...))) : Dict(reverse(P)) P isa Tuple{Char, Char} ? Dict(reverse(Pair(P...))) : Dict(reverse(P))
""" """
```julia
playfair_key_to_square(key::AbstractString, replacement::AbstractPair{Char, Char})
```
Converts the given key-string to a Playfair key square. Converts the given key-string to a Playfair key square.
Parameter `replacement` is a pair, such as ('I', 'J') or 'I' => 'J', containing Parameter `replacement` is a pair, such as `('I', 'J')` or `'I' => 'J'`, containing
the two letters which are combined. Only the first of these letters will the two letters which are combined. Only the first of these letters will
be present in the keysquare. be present in the keysquare.
""" """
@@ -32,16 +44,40 @@ function encrypt_playfair(plaintext, key::AbstractString; combined::AbstractPair
end end
""" """
```julia
encrypt_playfair(plaintext, key::Array{Char, 2}; stripped::Bool = false, combined::AbstractPair{Char, Char} = ('I', 'J'))
```
Encrypts the given plaintext according to the Playfair cipher. Encrypts the given plaintext according to the Playfair cipher.
Throws an error if the second entry in the `combined` tuple is present in the key. Throws an error if the second entry in the `combined` tuple is present in the key.
Optional parameters: Optional parameters:
stripped=false. When set to true, encrypt_playfair skips `stripped=false`. When set to true, `encrypt_playfair` skips
converting the plaintext to uppercase, removing punctuation, and converting the plaintext to uppercase, removing punctuation, and
combining characters which are to be combined in the key. combining characters which are to be combined in the key.
combined=('I', 'J'), marks the characters which are to be combined in the text. `combined=('I', 'J')`, marks the characters which are to be combined in the text.
Only the first of these two may be present in the output of encrypt_playfair. Only the first of these two may be present in the output of `encrypt_playfair`.
---
### Examples
```julia
julia> encrypt_playfair("Hello, World!", "playfair example")
"DMYRANVQCRGE"
julia> arr = ['P' 'L' 'A' 'Y' 'F'; 'I' 'R' 'E' 'X' 'M'; 'B' 'C' 'D' 'G' 'H'; 'K' 'N' 'O' 'Q' 'S'; 'T' 'U' 'V' 'W' 'Z'];
julia> encrypt_playfair("Hello, World!", arr) # Encrypt the same text using an explicitly specified keysquare
"DMYRANVQCRGE"
julia> encrypt_playfair("IJXYZA", "PLAYFIREXM", combined=('I', 'J')) # Optionally specify the two letters which are to be combined (default 'I','J')
"RMRMFWYE"
julia> encrypt_playfair("IJXYZA", "PLAYFIREXM", combined=('X', 'Z'))
"BSGXEY"
```
""" """
function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped::Bool = false, combined::AbstractPair{Char, Char} = ('I', 'J')) function encrypt_playfair(plaintext, key::Array{Char, 2}; stripped::Bool = false, combined::AbstractPair{Char, Char} = ('I', 'J'))
if !stripped if !stripped
@@ -125,9 +161,22 @@ function decrypt_playfair(ciphertext, key::AbstractString; combined::AbstractPai
end end
""" """
```julia
decrypt_playfair(ciphertext, key::Array{Char, 2}; combined::AbstractPair{Char, Char} = ('I', 'J'))
```
Decrypts the given ciphertext according to the Playfair cipher. Decrypts the given ciphertext according to the Playfair cipher.
Does not attempt to delete X's inserted as padding for double letters. Does not attempt to delete X's inserted as padding for double letters.
---
### Examples
```julia
julia> decrypt_playfair("RMRMFWYE", "playfair example")
"ixixyzax"
```
""" """
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

View File

@@ -1,9 +1,22 @@
""" """
```julia
encrypt_portas(plaintext, key_in::AbstractString)
```
Encrypts the given plaintext with the Portas cipher. Encrypts the given plaintext with the Portas cipher.
The key must be given as a string, whose characters are letters. The key must be given as a string, whose characters are letters.
Converts the text to uppercase. Converts the text to uppercase.
---
### Examples
```julia
julia> encrypt_portas("Hello, World!", "ab")
"URYYB, JBEYQ!"
```
""" """
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))
@@ -36,11 +49,24 @@ function encrypt_portas(plaintext, key_in::AbstractString)
end end
""" """
```julia
decrypt_portas(ciphertext, key::AbstractString)
```
Decrypts the given ciphertext with the Portas cipher. Decrypts the given ciphertext with the Portas cipher.
The key must be given as a string, whose characters are letters. The key must be given as a string, whose characters are letters.
Converts the text to lowercase. Converts the text to lowercase.
---
### Examples
```julia
julia> decrypt_portas("URYYB, JBEYQ!", "ab")
"hello, world!"
```
""" """
function decrypt_portas(ciphertext, key::AbstractString) function decrypt_portas(ciphertext, key::AbstractString)
return lowercase(encrypt_portas(ciphertext, key)) return lowercase(encrypt_portas(ciphertext, key))

View File

@@ -8,13 +8,25 @@ function construct_railfence(input, fence::AbstractArray, n_rails::Integer)
return fence return fence
end end
""" @doc raw"""
```julia ```julia
construct_railfence(input::AbstractString, n_rails::Integer) construct_railfence(input::AbstractString, n_rails::Integer)
construct_railfence(input::AbstractArray{T}, n_rails::Integer) where {T <: Number} construct_railfence(input::AbstractArray{T}, n_rails::Integer) where {T <: Number}
``` ```
See https://en.wikipedia.org/wiki/Rail_fence_cipher. See [`https://en.wikipedia.org/wiki/Rail_fence_cipher`](https://en.wikipedia.org/wiki/Rail_fence_cipher).
---
### Examples
```julia
julia> construct_railfence("WE ARE DISCOVERED. FLEE AT ONCE", 3)
3×26 Array{Char,2}:
'W' '□' '□' '□' 'E' '□' '□' '□' 'C' '□' '□' '□' 'R' … '□' '□' 'F' '□' '□' '□' 'A' '□' '□' '□' 'C' '□'
'□' 'E' '□' 'R' '□' 'D' '□' 'S' '□' 'O' '□' 'E' '□' '□' '.' '□' 'L' '□' 'E' '□' 'T' '□' 'N' '□' 'E'
'□' '□' 'A' '□' '□' '□' 'I' '□' '□' '□' 'V' '□' '□' 'D' '□' '□' '□' 'E' '□' '□' '□' 'O' '□' '□' '□'
```
""" """
function construct_railfence(input::AbstractString, n_rails::Integer) function construct_railfence(input::AbstractString, n_rails::Integer)
input = uppercase(replace(input, " " => "")) input = uppercase(replace(input, " " => ""))
@@ -25,23 +37,41 @@ function construct_railfence(input::AbstractArray{T}, n_rails::Integer) where {T
return construct_railfence(input, zeros(T, n_rails, length(input)), n_rails) return construct_railfence(input, zeros(T, n_rails, length(input)), n_rails)
end end
""" @doc raw"""
```julia ```julia
encrypt_railfence(input::AbstractString, n_rails::Integer) encrypt_railfence(input::AbstractString, n_rails::Integer)
``` ```
See https://en.wikipedia.org/wiki/Rail_fence_cipher. See [`https://en.wikipedia.org/wiki/Rail_fence_cipher`](https://en.wikipedia.org/wiki/Rail_fence_cipher).
---
### Examples
```julia
julia> encrypt_railfence("WE ARE DISCOVERED. FLEE AT ONCE", 3) # this reads the above matrix row by row
"WECRFACERDSOEE.LETNEAIVDEO"
```
""" """
function encrypt_railfence(input::AbstractString, n_rails::Integer) function encrypt_railfence(input::AbstractString, n_rails::Integer)
return join(Char[c for rail in eachrow(construct_railfence(input, n_rails)) for c in rail if c != '□']) return join(Char[c for rail in eachrow(construct_railfence(input, n_rails)) for c in rail if c != '□'])
end end
""" @doc raw"""
```julia ```julia
encrypt_railfence(input::AbstractString, n_rails::Integer) encrypt_railfence(input::AbstractString, n_rails::Integer)
``` ```
See https://en.wikipedia.org/wiki/Rail_fence_cipher. See [`https://en.wikipedia.org/wiki/Rail_fence_cipher`](https://en.wikipedia.org/wiki/Rail_fence_cipher).
---
### Examples
```julia
julia> decrypt_railfence("WECRFACERDSOEE.LETNEAIVDEO", 3)
"wearediscovered.fleeatonce"
```
""" """
function decrypt_railfence(input::AbstractString, n_rails::Integer) function decrypt_railfence(input::AbstractString, n_rails::Integer)
char_positions = Int[n for row in eachrow(construct_railfence(1:length(input), n_rails)) for n in row if n != 0] char_positions = Int[n for row in eachrow(construct_railfence(1:length(input), n_rails)) for n in row if n != 0]

View File

@@ -87,10 +87,23 @@ function SolitaireKeyStream(initialDeck::AbstractVector{T}) where {T <: Integer}
end end
""" """
```julia
encrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T}) where {T <: Integer}
```
Encrypts the given plaintext according to the Solitaire cipher. Encrypts the given plaintext according to the Solitaire cipher.
The key may be given either as a vector initial deck, where the cards are The key may be given either as a vector initial deck, where the cards are
1 through 54 (the two jokers being 53, 54), or as a string. 1 through 54 (the two jokers being 53, 54), or as a string.
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.
---
### Examples
```julia
julia> encrypt_solitaire("Hello, World!", "crypto")
"GRNNQISRYA"
```
""" """
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))
@@ -103,10 +116,23 @@ function encrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T
end end
""" """
```julia
decrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T}) where {T <: Integer}
```
Decrypts the given ciphertext according to the Solitaire cipher. Decrypts the given ciphertext according to the Solitaire cipher.
The key may be given either as a vector initial deck, where the cards are The key may be given either as a vector initial deck, where the cards are
1 through 54 (the two jokers being 53, 54), or as a string. 1 through 54 (the two jokers being 53, 54), or as a string.
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.
---
### Examples
```julia
julia> decrypt_solitaire("EXKYI ZSGEH UNTIQ", collect(1:54)) # as per https://www.schneier.com/code/sol-test.txt
"aaaaaaaaaaaaaaa"
```
""" """
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))

View File

@@ -26,7 +26,22 @@ encrypt_substitution(plaintext, "zyxwvutsrqponmlkjihgfedcba") # this will create
As per convention, the output will always be uppercase. As per convention, the output will always be uppercase.
For more information, see https://en.wikipedia.org/wiki/Substitution_cipher. For more information, see [`https://en.wikipedia.org/wiki/Substitution_cipher`](https://en.wikipedia.org/wiki/Substitution_cipher).
---
### Examples
```julia
julia> encrypt_substitution("Hello, this is plaintext", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm")
"ITSSG, ZIOL OL HSQOFZTBZ"
julia> encrypt_substitution("Hello, this is plaintext", "qwertyuiopasdfghjklzxcvbnm")
"ITSSG, ZIOL OL HSQOFZTBZ"
julia> encrypt_substitution("xyz", Dict('x' => 'd', 'y' => 'e', 'z' => 't'))
"DET"
```
""" """
encrypt_substitution(plaintext, sub_dict::Dict{T, S}) where {T, S} = encrypt_substitution(plaintext, sub_dict::Dict{T, S}) where {T, S} =
uppercase(join(something_crypt_substitution(lowercase(plaintext), sub_dict))) uppercase(join(something_crypt_substitution(lowercase(plaintext), sub_dict)))
@@ -58,7 +73,22 @@ decrypt_substitution(ciphertext, "zyxwvutsrqponmlkjihgfedcba"; reverse_dict = tr
As per convention, the output will always be lowercase. As per convention, the output will always be lowercase.
For more information, see https://en.wikipedia.org/wiki/Substitution_cipher. For more information, see [`https://en.wikipedia.org/wiki/Substitution_cipher`](https://en.wikipedia.org/wiki/Substitution_cipher).
---
### Examples
```julia
julia> decrypt_substitution("ITSSG, ZIOL OL HSQOFZTBZ", "abcdefghijklmnopqrstuvwxyz", "qwertyuiopasdfghjklzxcvbnm", reverse_dict = true)
"hello, this is plaintext"
julia> encrypt_atbash("some text", "abcdefghijklmnopqrstuvwxyz")
"HLNV GVCG"
julia> decrypt_atbash("HLNV GVCG", "abcdefghijklmnopqrstuvwxyz")
"some text"
```
""" """
function decrypt_substitution(ciphertext, sub_dict::Dict{T, S}; reverse_dict::Bool = true) where {T, S} function decrypt_substitution(ciphertext, sub_dict::Dict{T, S}; reverse_dict::Bool = true) where {T, S}
sub_dict = reverse_dict ? reverse(sub_dict) : sub_dict sub_dict = reverse_dict ? reverse(sub_dict) : sub_dict

View File

@@ -1,51 +1,81 @@
using Statistics using Statistics
""" """
```julia
encrypt_vigenere(plaintext, key::Array)
encrypt_vigenere(ciphertext, key::AbstractString)
```
Encrypts the given string using the Vigenere cipher according to the given vector of offsets. Encrypts the given string using the Vigenere cipher according to the given vector of offsets.
For example, encrypt_vigenere("ab", [0, 1]) returns "AC". For example, `encrypt_vigenere("ab", [0, 1])` returns `"AC"`.
---
### Examples
```julia
julia> encrypt_vigenere("Hello, World!", "ab")
"HFLMOXOSLE"
```
""" """
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))]
return join(ans) return join(ans)
end end
"""
Decrypts the given string using the Vigenere cipher according to the given vector of offsets.
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"
return lowercase(encrypt_vigenere(ciphertext, map(x -> 26 - x, key)))
end
"""
Encrypts the given string using the Vigenere cipher according to the given keystring.
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"
return 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
""" """
Decrypts the given string using the Vigenere cipher according to the given keystring. ```julia
For example, decrypt_vigenere("ab", "ac") returns "ab". decrypt_vigenere(ciphertext, key::Array)
decrypt_vigenere(plaintext, key::AbstractString)
```
Decrypts the given string using the Vigenere cipher according to the given vector of offsets.
For example, `decrypt_vigenere("ac", [0, 1])` returns `"ab"`.
---
### Examples
```julia
julia> decrypt_vigenere("HFLMOXOSLE", [0, 1]) # Notice that the offset `0` corresponds to the key `a`.
"helloworld"
```
""" """
function decrypt_vigenere(ciphertext, key::Array)
# 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
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"
return 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
""" """
```julia
crack_vigenere(plaintext; keylength::Integer = 0)
```
Cracks the given text encrypted with the Vigenere cipher. Cracks the given text encrypted with the Vigenere cipher.
Returns (derived key, decrypted plaintext). 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.
---
### Examples
```julia
julia> crack_vigenere(str)
```
""" """
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))