ClassicCiphers.jl
A Julia package implementing classical cryptographic ciphers with configurable alphabet and case handling.
Overview
ClassicCiphers.jl provides implementations of several classical ciphers:
- ROT13: A special case of Caesar cipher with shift=13
- Caesar cipher: Shifts each letter by a fixed amount
- Affine cipher: Combines multiplication and addition operations, using formula E(x) = (ax + b) mod m, where a and m are coprime
- Simple substitution cipher: Maps each letter to another using a provided mapping
- Vigenère cipher: Uses a keyword to determine shifting patterns
- Vernam cipher: Also known as the one-time pad
All ciphers support configurable:
- Case sensitivity
- Case preservation/conversion in output
- Unknown symbol handling
- Custom alphabets
Installation
using Pkg
Pkg.add("ClassicCiphers")
Quick Start
String API
Lets demonstrates the string processing API for the ClassicCiphers package by showing examples of both the Caesar cipher and Vigenère cipher implementations.
using ClassicCiphers
# Create a Caesar cipher with default settings
cipher = CaesarCipher(shift=3)
plaintext = "HELLO"
## Cipher message
ciphertext = cipher(plaintext) # Returns "KHOOR"
## Decipher message
recovered_plaintext = inv(cipher)(ciphertext) # Returns "HELLO"
## Custom shift value
cipher = CaesarCipher(shift=5)
The first example shows the basic usage of the Caesar cipher, which is one of the simplest substitution ciphers. It creates a cipher with a shift of 3 positions (the traditional shift used by Julius Caesar). When applied to the plaintext "HELLO"
, it shifts each letter forward by 3 positions in the alphabet, producing "KHOOR"
. The inverse operation (inv(cipher)
) creates a decryption cipher that shifts letters backward by 3 positions, recovering the original message.
The code then shows how to customize the Caesar cipher by using a different shift value (5 instead of 3), demonstrating the configurable nature of the implementation.
Create a Vigenère cipher with custom case handling
params = AlphabetParameters(output_case_mode=PRESERVE_CASE)
cipher = VigenereCipher("SECRET", alphabet_params=params)
ciphertext = cipher("Hello World!") # Returns "Zincs Pgvnu!"
recovered_plaintext = inv(cipher)(ciphertext) # Returns "HELLO WORLD!"
This second example introduces the more complex Vigenère cipher, which uses a keyword ("SECRET"
in this case) to create a polyalphabetic substitution. This example also demonstrates the case handling capabilities of the package through the AlphabetParameters
configuration. By setting output_case_mode=PRESERVE_CASE
, the cipher maintains the original case pattern of the input text - notice how "Hello World!"
maintains its capitalization pattern in the encrypted output "Zincs Pgvnu!"
.
Both examples showcase the consistent API design where:
- Ciphers are created with their specific parameters
- Encryption is performed by calling the cipher as a function
- Decryption is achieved using the inv function to create a decryption cipher
- Configuration options are passed through
Stream API
The Stream API allows processing text one character at a time, making it ideal for:
- Large files that shouldn't be loaded entirely into memory
- Real-time encryption/decryption of streaming data
- Chaining multiple ciphers together in a processing pipeline
Basic Usage
# Create cipher and stream wrapper
cipher = CaesarCipher(shift=3)
stream_cipher = StreamCipher(cipher)
# Process individual characters
fit!(stream_cipher, 'H')
value(stream_cipher) # Returns 'K'
Cipher Chaining
# Create encryption and decryption streams
cipher = CaesarCipher(shift=3)
decipher = inv(cipher)
stream_cipher = StreamCipher(cipher)
stream_decipher = StreamCipher(decipher)
# Connect them into a pipeline
connect!(stream_decipher, stream_cipher)
File Processing Example
# Process a file character by character
open("input.txt", "r") do input
open("output.txt", "w") do output
for c in readeach(io, Char)
fit!(stream_decipher, c)
write(out, value(stream_decipher))
end
end
end
The Stream API is built on Julia's OnlineStats.jl framework, providing:
- Memory efficiency through incremental processing
- Composability through cipher chaining
- Integration with Julia's IO system
This makes it particularly useful for processing large texts or implementing real-time encryption systems.
Core Types
AlphabetParameters
Controls how the cipher handles alphabets and character transformations.
struct AlphabetParameters{CASE_SENSITIVITY, CASE_MEMORIZATION, UNKNOWN_SYMBOL_HANDLING}
alphabet
case_sensitivity::CASE_SENSITIVITY
case_memorization::CASE_MEMORIZATION
unknown_symbol_handling::UNKNOWN_SYMBOL_HANDLING
end
Parameters:
alphabet
: Set of valid characters (default: A-Z)case_sensitivity
: Whether to distinguish between upper/lowercasecase_memorization
: How to handle case in outputunknown_symbol_handling
: How to handle characters not in alphabet
Case Handling Options
Input case sensitivity:
NOT_CASE_SENSITIVE
: Treat upper/lowercase as same (default)CASE_SENSITIVE
: Distinguish between upper/lowercase
Output case modes:
PRESERVE_CASE
: Keep original case from inputLOWERCASE_CASE
: Convert all to lowercaseUPPERCASE_CASE
: Convert all to uppercaseCCCASE
: Crypto convention case (lowercase for encryption, uppercase for decryption)DEFAULT_CASE
: Use case from output alphabet (default)
Unknown symbol modes:
IGNORE_SYMBOL
: Keep unknown symbols as-is (default)REMOVE_SYMBOL
: Remove unknown symbolsREPLACE_SYMBOL
: Replace with '?'
Cipher Implementations
CaesarCipher
Classic shift cipher that moves each letter a fixed number of positions.
# Basic usage
cipher = CaesarCipher(shift=3)
cipher("HELLO") # Returns "KHOOR"
# With custom alphabet parameters
params = AlphabetParameters(case_sensitive=true)
cipher = CaesarCipher(shift=5, alphabet_params=params)
ROT13Cipher
Special case of Caesar cipher with fixed shift of 13, making it self-inverse.
cipher = ROT13Cipher()
plaintext = "HELLO"
ciphertext = cipher(plaintext) # Returns "URYYB"
recovered_plaintext = cipher(ciphertext) # Returns "HELLO"
SubstitutionCipher
Maps each character to another using a provided dictionary.
mapping = Dict('A'=>'X', 'B'=>'Y', 'C'=>'Z')
cipher = SubstitutionCipher(mapping)
cipher("ABC") # Returns "XYZ"
VigenereCipher
Polyalphabetic substitution cipher using a keyword to determine shifts.
cipher = VigenereCipher("SECRET")
cipher("HELLO") # Returns "ZINCS"
# With case preservation
params = AlphabetParameters(output_case_mode=PRESERVE_CASE)
cipher = VigenereCipher("SECRET", alphabet_params=params)
cipher("Hello") # Returns "Zincs"
Utility Functions
All ciphers support:
inv(cipher)
: Create inverse cipher for decryption- String and character-level operations:
cipher("ABC")
orcipher('A')
Best Practices
Case Handling: Consider your use case when configuring case sensitivity:
- For maximum security: Use
CASE_SENSITIVE
- For traditional crypto: Use
CCCASE
- For user-friendly output: Use
PRESERVE_CASE
- For maximum security: Use
Unknown Symbols: Choose appropriate handling:
- For maximum compatibility: Use
IGNORE_SYMBOL
- For clean output: Use
REMOVE_SYMBOL
- For error detection: Use
REPLACE_SYMBOL
- For maximum compatibility: Use
Custom Alphabets: Ensure they:
- Contain all required characters
- Have unique characters (checked with warning)
- Match your case sensitivity settings
Examples
Basic Encryption/Decryption
# Caesar cipher with default settings
caesar = CaesarCipher(shift=3)
plaintext = "THE QUICK BROWN FOX"
ciphertext = caesar(message)
recovered_plaintext = inv(caesar)(ciphertext)
@assert recovered_plaintext == uppercase(plaintext)
# Vigenère cipher with case preservation
params = AlphabetParameters(output_case_mode=PRESERVE_CASE)
vigenere = VigenereCipher("SECRET", alphabet_params=params)
plaintext = "Hello World!"
ciphertext = vigenere(plaintext)
recovered_plaintext = inv(vigenere)(ciphertext)
@assert recovered_plaintext == uppercase(message)
Custom Alphabet
# Create custom alphabet with only uppercase letters A-M
params = AlphabetParameters(alphabet=collect('A':'M'))
cipher = CaesarCipher(shift=3, alphabet_params=params)
# Letters N-Z will be handled according to unknown_symbol_handling
Case Sensitivity
# Case sensitive substitution
params = AlphabetParameters(case_sensitive=true)
mapping = Dict('A'=>'X', 'a'=>'x', 'B'=>'Y', 'b'=>'y')
cipher = SubstitutionCipher(mapping, alphabet_params=params)
cipher("AaBb") # Returns "XxYy"
cipher("AABB") # Returns "XXYY"
Testing
The package includes comprehensive tests for all ciphers and configurations:
using Test
using ClassicCiphers
# Run all tests
@testset "ClassicCiphers.jl" begin
include("test/runtests.jl")
end
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
This project is licensed under the MIT License.