A C11 replacement for the Python numbers-station experiments in TimePad_Spying. It provides authenticated file encryption and a decimal one-time-pad experiment for research and education. No Python runtime or scripts are required.
Created and maintained by Renan Rocha (valinux).
Setup and how-tos · How it works · Case study · Security · Contributing · Changelog
No implementation is guaranteed “unbreakable.” The normal encode/decode
commands use libsodium's XChaCha20-Poly1305 authenticated encryption. The explicit
otp-* commands demonstrate decimal pads without authentication. Both preserve
every input byte, including UTF-8, whitespace, and binary data.
| Mode | Purpose | Authentication | Secret material |
|---|---|---|---|
encode / decode |
Encrypt and recover files, up to 64 MiB | XChaCha20-Poly1305 tag | One 32-byte key; a new random nonce is generated for each encryption. |
otp-encode / otp-decode |
Study decimal pads, up to 1 MiB | None | A new secret pad with three digits per input byte, never reused for another encryption. |
keygen creates a secret key file. encode uses that key and a fresh random
nonce to produce a binary file containing a header, ciphertext, and authentication
tag. decode verifies authenticity before writing the original bytes back to a
new file. Senders and recipients arrange key distribution separately; TimePad
does not transmit files or provide a messaging service.
See the illustrated explanation for the complete flow, the role of nonces, a worked decimal example, and the security limitations.
Requirements: macOS or Linux, a C11 compiler, CMake 3.16+, pkg-config, and libsodium 1.0.18+. Native Windows is not currently supported.
git clone https://github.com/valinux/TimePad_Spying.git
cd TimePad_Spying# macOS (Homebrew)
brew install cmake pkg-config libsodium
# Debian/Ubuntu alternative
sudo apt-get install build-essential cmake pkg-config libsodium-devcmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failureIf pkg-config cannot find a Homebrew installation, run:
export PKG_CONFIG_PATH="$(brew --prefix libsodium)/lib/pkgconfig" and configure again.
Run in a private directory. Every command takes file paths, never literal keys or message contents. Outputs must be new paths; existing files are not replaced.
umask 077
printf 'Meet me at 09:00.\n' > message.txt
./build/timepad keygen research.key
./build/timepad encode research.key message.txt message.tpd
./build/timepad decode research.key message.tpd recovered.txt
cmp message.txt recovered.txtThe key is 32 random binary bytes; it is not a password. Keep it secret and arrange
its transfer separately through an already trusted confidential channel. A lost
key cannot be recovered. encode generates a fresh random 24-byte nonce for each
message; this mode supports multiple messages per key. Use a new key after a
suspected compromise.
Decryption verifies the complete message before creating output. A wrong key, modified header/ciphertext/tag, truncation, or appended data causes failure. Inputs are limited to 64 MiB of plaintext, processed in memory. Encryption adds 48 bytes. Empty files are supported.
Files created by the program have mode 0600. Keys and pads must be owned by the
current user without group/other permission bits; use chmod 600 FILE after a
transfer if necessary. Inputs must be regular files, and final-component symlinks
are rejected. Success returns 0, operational/input failure 1, and invalid
command syntax 2. Diagnostics go to stderr; secrets are never printed.
./build/timepad otp-encode message.txt research.pad numbers.txt
./build/timepad otp-decode research.pad numbers.txt recovered-otp.txt
cmp message.txt recovered-otp.txtEach byte becomes three decimal digits: A becomes 065, a newline 010, and a
zero byte 000. A fresh secret digit is added modulo 10 to each message digit;
decoding subtracts it. Ciphertext and pad contain three ASCII digits per original
byte. Decoding accepts ASCII whitespace between digits. Plaintext is limited to
1 MiB, and each grouped numeric input to 6 MiB, containing at most 3 MiB of digits.
Unequal pad/ciphertext lengths, non-digit data, incomplete triples, and decoded
values above 255 are rejected. No newline is added or removed from plaintext.
Pad digits come from libsodium's unbiased cryptographic random generator. Pads
must remain secret and must never be reused to encrypt another message.
otp-encode always creates a new pad and refuses an existing pad path. Copies
made elsewhere cannot be tracked or prevented. Decoding does not delete a pad.
The program saves the pad before publishing ciphertext; if the second write
fails, discard the unused pad reported by the diagnostic.
This mode has no tamper detection. A wrong pad or changed ciphertext can
produce plausible, incorrect plaintext. Input validation is not authentication.
An ideal OTP's perfect-secrecy proof assumes independent truly uniform pad
symbols, a secret pad as long as the numeric message, and exactly one encryption
per pad. This program uses an operating-system-backed cryptographic generator;
it does not claim information-theoretic security for its generated pads. Use
encode/decode when message authentication matters.
cmake -S . -B build-sanitize -DCMAKE_BUILD_TYPE=Debug -DTIMEPAD_SANITIZERS=ON
cmake --build build-sanitize --parallel
ctest --test-dir build-sanitize --output-on-failureThe C tests cover binary round trips, every single-bit change in a sample encrypted envelope, wrong keys, every truncation of that envelope, extra bytes, size limits, the documented wire format, decimal arithmetic, and invalid inputs. Shell integration tests cover file permissions, symlinks, FIFOs, collisions, concurrent writers, and failure without publishing decoded output. Checks remain enabled in release builds. GitHub Actions is configured for macOS and Linux.
Optional fuzzing requires a Clang distribution with the libFuzzer runtime:
cmake -S . -B build-fuzz -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=Debug \
-DTIMEPAD_FUZZ=ON -DBUILD_TESTING=OFF
cmake --build build-fuzz --parallel
./build-fuzz/fuzz_timepad -max_total_time=30 -max_len=4096Read the case study for the original defects, format specification, design choices, and security limitations. The new formats are intentionally incompatible with the Python scripts; their source remains in Git history. The per-message word dictionary, JSON files, and Fernet dependency have been retired.
Copyright © 2026 Renan Rocha. TimePad is released under the MIT License, including its disclaimer of warranty and limitation of liability.
Users are responsible for their own conduct, compliance with applicable laws, required authorizations, and protection of their keys and data. To the fullest extent permitted by applicable law, the authors and copyright holders disclaim liability for use or misuse as stated in the license. No claim of unbreakable encryption, certification, or independent security audit is made.
Read the responsibility and liability notice. Research and education describe the project's purpose; they do not add use restrictions to the MIT License. External dependencies retain their own licenses; see third-party notices.
Maintainers preparing a release can use the GitHub setup guide for repository topics, description, security reporting, and publication steps.