Security Disclaimer: This project is for educational purposes. The implemented ciphers are not secure for real-world cryptographic use.
TL;DR: A modular C library of classical and modern ciphers. Fully data-driven, CLI-ready, and easy to extend. Heap-allocated strings, explicit memory rules, and no giant if/else chains.
A modular, extensible collection of classical and modern cipher implementations written in C, with a clean registry-based architecture.
This project is me exploring low-level systems design applied to cryptography:
- manual memory management
- function pointers
- data-driven dispatch
- clear ABI-style contracts
No frameworks. No magic. Just C.
- Plugin-style cipher registry
- Data-driven dispatch (no giant
if/elsechains) - Clean separation between:
- cipher implementations
- registry
- user interface
- Each cipher is self-contained
- Explicit memory ownership rules
- Easy to extend with new algorithms
- On failure, cipher functions return
NULLand do not modify global state.
I ran into a lack of a clean, easy-to-use system for managing ciphers when I was making a cipher codex in C++.
.
├── include/
│ ├── ciphers/ # Cipher specific headers
│ │ └── *.h
│ └── cipher.h # Core cipher interface and registry API
├── src/
│ ├── ciphers/ # Cipher implementations
│ │ └── *.c
│ ├── cipher.c # Core registry and API implementation
│ └── main.c # CLI interface
├── tests/
├── .clang-format # Code formatting rules
├── justfile # Build, format, lint recipes
└── Makefile # Build system- Unix-like OS (Linux/macOS) for
ccusage - C11-compatible compiler
make(comes with your system, runmake --versionto check)- Optional: just for the extra recipes
- Optional:
clang-format,clang-tidy,bearfor formatting, linting, and generating compile commands
Run from project root:
# Build (includes sanitizers and debug info by default)
make
# Run
./cipher
# Clean
make cleanOr if using just:
# List available recipes
just --list
# Only build
just build
# Format all source files
just format
# Run clang-tidy on everything
just lint
# Generate compile_commands.json (requires bear)
just build-db
# Build and run
just run
# Clean
just cleanDemo run of the CLI:
$ ./cipher
Operations:
1. Encrypt
2. Decrypt
Choose the operation: 1
Available Ciphers:
[1] Caesar
[2] ROT13
[3] Atbash
[4] Affine
[5] Scytale
[6] Polybius Square
[7] Vigenere
[8] Beaufort
[9] Gronsfeld
[10] Rail Fence
Enter the cipher id: 1
Enter your message:
This is my message.
Do you have a problem with it?
Enter the key: 3
Result:
Wklv lv pb phvvdjh.
Gr brx kdyh d sureohp zlwk lw?All ciphers conform to this function signature:
char *encrypt(const char *input, const cipher_params_t *params);
char *decrypt(const char *input, const cipher_params_t *params);Rules:
inputandparamsare read-only- Returned string is heap-allocated
- Caller is responsible for
free()
This mirrors real-world C library ABI design.
Ciphers are registered centrally:
static cipher_t cipher_registry[] = {
{"Caesar", PARAM_NUMBER, caesar_encrypt, caesar_decrypt},
{"ROT13", PARAM_NONE, rot13_encrypt, rot13_decrypt},
{"Atbash", PARAM_NONE, atbash_encrypt, atbash_decrypt},
{"Affine", PARAM_2_NUMBERS, affine_encrypt, affine_decrypt},
{"Scytale", PARAM_NUMBER, scytale_encrypt, scytale_decrypt},
{"Vigenere", PARAM_STRING, vigenere_encrypt, vigenere_decrypt},
{"Rail Fence", PARAM_NUMBER, rail_fence_encrypt, rail_fence_decrypt},
...
};The CLI discovers available ciphers dynamically.
Adding a new cipher does not require modifying main.c.
This is the key feature of this project which was the reason for me abandoning my previous project in C++.
Also abandoned it because C++ wasn’t the best fit for this kind of modular registry-based design.
See main.c file for full example.
const cipher_t *cipher = get_cipher(0);
cipher_params_t params = { .number.value = 3 };
char *result = cipher->encrypt("Hello", ¶ms);- Affine
- Atbash
- Caesar
- ROT13
- Beaufort
- Gronsfeld
- Polybius Square
- Scytale
- Vigenere
- Rail Fence
- File-based input
- File-based output
MIT License