mirror of
https://github.com/Smaug123/ClassicalCiphers.jl
synced 2025-10-05 01:18:48 +00:00
Refined docstrings and added documentation
This commit is contained in:
106
.github/workflows/CI.yml
vendored
Normal file
106
.github/workflows/CI.yml
vendored
Normal 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
16
.github/workflows/CompatHelper.yml
vendored
Normal 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
13
.github/workflows/TagBot.yml
vendored
Normal 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
24
.gitignore
vendored
@@ -1,5 +1,27 @@
|
||||
# Files generated by invoking Julia with --code-coverage
|
||||
*.jl.cov
|
||||
*.jl.*.cov
|
||||
|
||||
# Files generated by invoking Julia with --track-allocation
|
||||
*.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
|
||||
.vscode/*
|
||||
|
||||
# Silly macOS stuff
|
||||
.DS_Store
|
||||
|
21
README.md
21
README.md
@@ -1,10 +1,17 @@
|
||||
[](https://travis-ci.org/Smaug123/ClassicalCiphers.jl) [](https://coveralls.io/github/Smaug123/ClassicalCiphers.jl?branch=master) [](https://github.com/invenia/BlueStyle)
|
||||
<h1 align="center">
|
||||
ClassicalCiphers.jl
|
||||
</h1>
|
||||
|
||||
# ClassicalCiphers
|
||||
<!-- [](https://username.github.io/MyCoolPackage.jl/stable) -->
|
||||
[](https://username.github.io/MyCoolPackage.jl/dev)
|
||||
[](https://github.com/username/MyCoolPackage.jl/actions?query=workflow%3ACI)
|
||||
[](https://coveralls.io/github/Smaug123/ClassicalCiphers.jl?branch=master)
|
||||
[](https://github.com/invenia/BlueStyle)
|
||||
|
||||
## 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.
|
||||
|
||||
## Currently Implemented
|
||||
@@ -20,6 +27,7 @@ The Solitaire cipher is included for completeness, though it is perhaps not stri
|
||||
* [Solitaire]
|
||||
* [Rail Fence]
|
||||
* [Substitution]
|
||||
* [Atbash]
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -398,6 +406,12 @@ julia> decrypt_atbash("HLNV GVCG", "abcdefghijklmnopqrstuvwxyz")
|
||||
"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
|
||||
[Affine]: https://en.wikipedia.org/wiki/Affine_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
|
||||
[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
|
||||
|
3
docs/Project.toml
Normal file
3
docs/Project.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[deps]
|
||||
ClassicalCiphers = "ecf26e93-935c-5e64-9b21-bc8ac81b4723"
|
||||
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
|
20
docs/make.jl
Normal file
20
docs/make.jl
Normal 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
41
docs/src/index.md
Normal 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
5
docs/src/usage.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## Usage
|
||||
|
||||
```@autodocs
|
||||
Modules = [ClassicalCiphers]
|
||||
```
|
@@ -1,4 +1,8 @@
|
||||
"""
|
||||
```julia
|
||||
encrypt_affine(plaintext, mult::Integer, add::Integer; offset::Integer = 0)
|
||||
```
|
||||
|
||||
Encrypts the given plaintext according to the Affine cipher.
|
||||
The key is given as a pair of integers: first the multiplier, then
|
||||
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.
|
||||
|
||||
Optional argument: offset=0, which specifies what number 'a' should be
|
||||
Optional argument: `offset=0`, which specifies what number 'a' should be
|
||||
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
|
||||
error("Multiplier must be coprime to 26.")
|
||||
end
|
||||
@@ -20,6 +33,10 @@ function encrypt_affine(plaintext, mult::T, add::T; offset::T = 0) where {T <: I
|
||||
end
|
||||
|
||||
"""
|
||||
```julia
|
||||
decrypt_affine(ciphertext, mult::Integer, add::Integer; offset::Integer=0)
|
||||
```
|
||||
|
||||
Decrypts the given ciphertext according to the Affine cipher.
|
||||
The key is given as a pair of integers: first the multiplier, then
|
||||
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.
|
||||
|
||||
Optional argument: offset=0, which specifies what number 'a' should be
|
||||
Optional argument: `offset=0`, which specifies what number 'a' should be
|
||||
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
|
||||
error("Multiplier must be coprime to 26.")
|
||||
end
|
||||
@@ -61,15 +87,28 @@ function max_by(arr::AbstractArray, f::Function)
|
||||
end
|
||||
|
||||
"""
|
||||
```julia
|
||||
crack_affine(ciphertext; mult::Integer = 0, add::Integer = -1)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Optional arguments: mult=0, which specifies the multiplier if known;
|
||||
add=-1, which specifies the additive constant if known.
|
||||
Optional arguments: `mult=0`, which specifies the multiplier 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)]
|
||||
adds = add != -1 ? Int[add] : (0:25)
|
||||
possible_keys = Iterators.product(mults, adds)
|
||||
|
@@ -1,14 +1,28 @@
|
||||
"""
|
||||
```julia
|
||||
encrypt_caesar(plaintext, key::Integer)
|
||||
encrypt_caesar(plaintext)
|
||||
```
|
||||
|
||||
Encrypts the given plaintext according to the Caesar cipher.
|
||||
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
|
||||
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"
|
||||
key = ((key - 1) % 26) + 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)
|
||||
|
||||
"""
|
||||
```julia
|
||||
decrypt_caesar(ciphertext, key::Integer)
|
||||
decrypt_caesar(ciphertext)
|
||||
```
|
||||
|
||||
Decrypts the given ciphertext according to the Caesar cipher.
|
||||
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
|
||||
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"
|
||||
key = ((key - 1) % 26) + 1
|
||||
return lowercase(encrypt_caesar(ciphertext, 26 - key))
|
||||
@@ -34,22 +62,36 @@ end
|
||||
decrypt_caesar(plaintext) = decrypt_caesar(plaintext, 3)
|
||||
|
||||
"""
|
||||
```julia
|
||||
crack_caesar(ciphertext; cleverness::Integer = 1)
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
With cleverness=0, simply does the shift that maximises e's frequency.
|
||||
With cleverness=1, maximises the string's total fitness.
|
||||
With `cleverness=0`, simply does the shift that maximises e's frequency.
|
||||
With `cleverness=1`, maximises the string's total fitness.
|
||||
|
||||
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]
|
||||
|
||||
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]
|
||||
return last(texts)
|
||||
end
|
||||
|
@@ -3,7 +3,7 @@ function letters_only(text::AbstractString)
|
||||
return filter(x -> ('A' <= x <= 'Z' || 'a' <= x <= 'z'), text)
|
||||
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()?
|
||||
ans = copy(arr)
|
||||
for i in 1:length(arr)
|
||||
@@ -13,7 +13,7 @@ function rotate_right(arr::AbstractVector, n::T) where {T <: Integer}
|
||||
return ans
|
||||
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
|
||||
ans = copy(arr)
|
||||
for i in 1:length(arr)
|
||||
@@ -23,9 +23,9 @@ function rotate_left(arr::AbstractVector, n::T) where {T <: Integer}
|
||||
return ans
|
||||
end
|
||||
|
||||
rotate_left_str(st::AbstractString, n::T) where {T <: Integer} =
|
||||
rotate_left_str(st::AbstractString, n::Integer) =
|
||||
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))
|
||||
|
||||
function split_by(arr::AbstractVector, func::Function)
|
||||
@@ -85,8 +85,12 @@ function string_fitness(input::AbstractString; alreadystripped::Bool = false)
|
||||
end
|
||||
|
||||
"""
|
||||
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.
|
||||
```julia
|
||||
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)
|
||||
ans = Dict{Char, Int}()
|
||||
@@ -102,6 +106,10 @@ function frequencies(input::AbstractString)
|
||||
end
|
||||
|
||||
"""
|
||||
```julia
|
||||
index_of_coincidence(input::AbstractString)
|
||||
```
|
||||
|
||||
Finds the index of coincidence of the input string. Uppercase characters are considered to be
|
||||
equal to their lowercase counterparts.
|
||||
"""
|
||||
|
@@ -55,30 +55,57 @@ function parse_reflector(reflector::AbstractString)
|
||||
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).
|
||||
|
||||
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.
|
||||
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.
|
||||
Key is a string of three letters, indicating the starting positions of the rotors.
|
||||
|
||||
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.
|
||||
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
|
||||
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.
|
||||
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,
|
||||
rotors::Array{T, 1}, key::AbstractString;
|
||||
reflector_id='B', ring::AbstractString = "AAA",
|
||||
stecker = Tuple{Char, Char}[],
|
||||
skip_stecker_check = false) where {T <: Integer}
|
||||
skip_stecker_check = false) where T <: Integer
|
||||
|
||||
parsed_stecker = parse_stecker(stecker)
|
||||
# validate stecker settings
|
||||
if !skip_stecker_check
|
||||
@@ -243,5 +270,8 @@ function encrypt_enigma(plaintext,
|
||||
return uppercase(String(take!(ans)))
|
||||
end
|
||||
|
||||
"""
|
||||
See `encrypt_enigma` as this function uses identical arguments.
|
||||
"""
|
||||
decrypt_enigma(args1...; args2...) =
|
||||
lowercase(encrypt_enigma(args1...; args2...))
|
||||
|
45
src/hill.jl
45
src/hill.jl
@@ -1,8 +1,12 @@
|
||||
using LinearAlgebra
|
||||
|
||||
"""
|
||||
```julia
|
||||
encrypt_hill(plaintext::AbstractString, key::AbstractArray{Integer, 2})
|
||||
```
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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}
|
||||
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
|
||||
|
||||
"""
|
||||
```julia
|
||||
adjugate(mat::AbstractArray{Integer, 2})
|
||||
```
|
||||
|
||||
Computes the adjugate matrix for given matrix.
|
||||
"""
|
||||
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))
|
||||
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}
|
||||
if ndims(key) != 2
|
||||
error("Key must be a two-dimensional matrix.")
|
||||
|
@@ -3,15 +3,31 @@ function keystr_to_dict(keystr::AbstractString)
|
||||
end
|
||||
|
||||
"""
|
||||
```julia
|
||||
encrypt_monoalphabetic(plaintext, key::Dict{Char, Char})
|
||||
```
|
||||
|
||||
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
|
||||
{'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
|
||||
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;
|
||||
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})
|
||||
# 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
|
||||
|
||||
"""
|
||||
```julia
|
||||
decrypt_monoalphabetic(ciphertext, key::Dict{Char, Char})
|
||||
```
|
||||
|
||||
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
|
||||
{'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
|
||||
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;
|
||||
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})
|
||||
# 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]))
|
||||
return encrypt_monoalphabetic(ciphertext, Dict{Char, Char}(reverse(a) for a in key))
|
||||
end
|
||||
|
||||
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
|
||||
# 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))
|
||||
dict = Dict{Char, Char}(a => Char(96 + findfirst(==(a), lowercase(key))) for a in lowercase(key))
|
||||
return encrypt_monoalphabetic(lowercase(ciphertext), dict)
|
||||
end
|
||||
|
||||
@@ -56,6 +85,10 @@ end
|
||||
# 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.
|
||||
The characters are guaranteed to be at different positions, though "aa" would be
|
||||
'swapped' to "aa".
|
||||
@@ -70,34 +103,55 @@ function swap_two(str)
|
||||
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
|
||||
substitution cipher.
|
||||
|
||||
Returns (the derived key, decrypted plaintext).
|
||||
Returns `(the derived key, decrypted plaintext)`.
|
||||
|
||||
Possible arguments include:
|
||||
starting_key="", which when specified (for example, as "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
|
||||
The various optional arguments to `crack_monoalphabetic` are:
|
||||
* `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.
|
||||
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
|
||||
* `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.
|
||||
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.
|
||||
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,
|
||||
at temperature t.
|
||||
|
||||
---
|
||||
|
||||
### Examples
|
||||
|
||||
```julia
|
||||
julia> crack_monoalphabetic(str, chatty=0, rounds=10)
|
||||
(decrypted_string, key)
|
||||
```
|
||||
"""
|
||||
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}
|
||||
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
|
||||
)
|
||||
|
||||
if isempty(starting_key)
|
||||
# most common letters
|
||||
|
@@ -1,11 +1,23 @@
|
||||
"""
|
||||
```julia
|
||||
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) =
|
||||
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.
|
||||
|
||||
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
|
||||
be present in the keysquare.
|
||||
"""
|
||||
@@ -32,16 +44,40 @@ function encrypt_playfair(plaintext, key::AbstractString; combined::AbstractPair
|
||||
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.
|
||||
Throws an error if the second entry in the `combined` tuple is present in the key.
|
||||
|
||||
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
|
||||
combining characters which are to be combined in the key.
|
||||
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.
|
||||
`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`.
|
||||
|
||||
---
|
||||
|
||||
### 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'))
|
||||
if !stripped
|
||||
@@ -125,9 +161,22 @@ function decrypt_playfair(ciphertext, key::AbstractString; combined::AbstractPai
|
||||
end
|
||||
|
||||
"""
|
||||
```julia
|
||||
decrypt_playfair(ciphertext, key::Array{Char, 2}; combined::AbstractPair{Char, Char} = ('I', 'J'))
|
||||
```
|
||||
|
||||
Decrypts the given ciphertext according to the Playfair cipher.
|
||||
|
||||
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'))
|
||||
# to obtain the decrypting keysquare, reverse every row and every column
|
||||
|
@@ -1,9 +1,22 @@
|
||||
"""
|
||||
```julia
|
||||
encrypt_portas(plaintext, key_in::AbstractString)
|
||||
```
|
||||
|
||||
Encrypts the given plaintext with the Portas cipher.
|
||||
|
||||
The key must be given as a string, whose characters are letters.
|
||||
|
||||
Converts the text to uppercase.
|
||||
|
||||
---
|
||||
|
||||
### Examples
|
||||
|
||||
```julia
|
||||
julia> encrypt_portas("Hello, World!", "ab")
|
||||
"URYYB, JBEYQ!"
|
||||
```
|
||||
"""
|
||||
function encrypt_portas(plaintext, key_in::AbstractString)
|
||||
key = uppercase(letters_only(key_in))
|
||||
@@ -36,11 +49,24 @@ function encrypt_portas(plaintext, key_in::AbstractString)
|
||||
end
|
||||
|
||||
"""
|
||||
```julia
|
||||
decrypt_portas(ciphertext, key::AbstractString)
|
||||
```
|
||||
|
||||
Decrypts the given ciphertext with the Portas cipher.
|
||||
|
||||
The key must be given as a string, whose characters are letters.
|
||||
|
||||
Converts the text to lowercase.
|
||||
|
||||
---
|
||||
|
||||
### Examples
|
||||
|
||||
```julia
|
||||
julia> decrypt_portas("URYYB, JBEYQ!", "ab")
|
||||
"hello, world!"
|
||||
```
|
||||
"""
|
||||
function decrypt_portas(ciphertext, key::AbstractString)
|
||||
return lowercase(encrypt_portas(ciphertext, key))
|
||||
|
@@ -8,13 +8,25 @@ function construct_railfence(input, fence::AbstractArray, n_rails::Integer)
|
||||
return fence
|
||||
end
|
||||
|
||||
"""
|
||||
@doc raw"""
|
||||
```julia
|
||||
construct_railfence(input::AbstractString, n_rails::Integer)
|
||||
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)
|
||||
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)
|
||||
end
|
||||
|
||||
"""
|
||||
@doc raw"""
|
||||
```julia
|
||||
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)
|
||||
return join(Char[c for rail in eachrow(construct_railfence(input, n_rails)) for c in rail if c != '□'])
|
||||
end
|
||||
|
||||
"""
|
||||
@doc raw"""
|
||||
```julia
|
||||
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)
|
||||
char_positions = Int[n for row in eachrow(construct_railfence(1:length(input), n_rails)) for n in row if n != 0]
|
||||
|
@@ -87,10 +87,23 @@ function SolitaireKeyStream(initialDeck::AbstractVector{T}) where {T <: Integer}
|
||||
end
|
||||
|
||||
"""
|
||||
```julia
|
||||
encrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T}) where {T <: Integer}
|
||||
```
|
||||
|
||||
Encrypts the given plaintext according to the Solitaire cipher.
|
||||
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.
|
||||
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}
|
||||
inp = uppercase(letters_only(string))
|
||||
@@ -103,10 +116,23 @@ function encrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T
|
||||
end
|
||||
|
||||
"""
|
||||
```julia
|
||||
decrypt_solitaire(string::AbstractString, initialDeck::AbstractVector{T}) where {T <: Integer}
|
||||
```
|
||||
|
||||
Decrypts the given ciphertext according to the Solitaire cipher.
|
||||
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.
|
||||
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}
|
||||
inp = uppercase(letters_only(string))
|
||||
|
@@ -26,7 +26,22 @@ encrypt_substitution(plaintext, "zyxwvutsrqponmlkjihgfedcba") # this will create
|
||||
|
||||
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} =
|
||||
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.
|
||||
|
||||
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}
|
||||
sub_dict = reverse_dict ? reverse(sub_dict) : sub_dict
|
||||
|
@@ -1,51 +1,81 @@
|
||||
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.
|
||||
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)
|
||||
# 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
|
||||
|
||||
"""
|
||||
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)
|
||||
# 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
|
||||
|
||||
"""
|
||||
Decrypts the given string using the Vigenere cipher according to the given keystring.
|
||||
For example, decrypt_vigenere("ab", "ac") returns "ab".
|
||||
```julia
|
||||
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)
|
||||
# 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
|
||||
|
||||
"""
|
||||
```julia
|
||||
crack_vigenere(plaintext; keylength::Integer = 0)
|
||||
```
|
||||
|
||||
Cracks the given text encrypted with the Vigenere cipher.
|
||||
|
||||
Returns (derived key, decrypted plaintext).
|
||||
Returns `(derived key, decrypted plaintext)`.
|
||||
|
||||
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
|
||||
of coincidence.
|
||||
|
||||
---
|
||||
|
||||
### Examples
|
||||
|
||||
```julia
|
||||
julia> crack_vigenere(str)
|
||||
```
|
||||
"""
|
||||
function crack_vigenere(plaintext; keylength::Integer = 0)
|
||||
stripped_text = letters_only(lowercase(plaintext))
|
||||
|
Reference in New Issue
Block a user