2025-09-21 15:55:06 -04:00
2026-01-17 07:39:23 -04:00
2026-01-17 07:39:23 -04:00

OTP Cipher - One Time Pad Implementation

Introduction

A secure one-time pad (OTP) cipher implementation in C99.

Why One-Time Pads

Nostr and much of the web runs on public key cryptography. Public key cryptography is great, but it is vulnerable. Cryptographers know this, and they know what it takes to attack it, so what they do is just make the keys large enough such that the system is resistant to attack given computers as they are today.

There is one type of cryptography, however, that is invulnerable to any type of attack in our universe, and that is known as a one-time pad.

One-time pads rely directly on the laws of physics and what it means for a number to be truly random.

If you take your secret message and mix it with truly random numbers, and don't use those random numbers again, then that message is unbreakable by any computer, no matter how powerful, quantum or not, forever.

In fact, one-time pads are so powerful that if you have data encrypted by a one-time pad located in a distant galaxy, and that data is not kept anywhere else, then by destroying the pad used for encryption in your galaxy, the data is wiped from the universe and can never be recovered.

Advantages and Limitations

Limitations

  1. The pad must be shared between the parties wanting to use it.
  2. The pad must be as long or longer than what you want to encrypt, and it can't be used a second time.

Modern Advantages

While in the past, pad length might have been a problem, readily available USB drives in the terabytes make size less of a problem for many uses.

We are also becoming very accustomed to YubiKey authenticators in the USB ports of our computers. A small USB drive in our devices can now easily contain a key of greater length than all the text messages we would expect to send over a lifetime.

Multi-Device Coordination & Dynamic Partitioning ("Work Stealing" Protocol)

One of the core challenges of using a One-Time Pad across multiple devices or between bilateral peers is ensuring that two devices never encrypt using the same region of the pad. Reusing any section of a pad breaks its "Information Theoretically Secure" guarantee.

We solve this with Dynamic Partition Splitting (Work Stealing):

  1. Exclusive Ownership Invariant: Every unused byte of the pad has a single exclusive owner ([offset, end_offset)). Because no two devices ever share an active write range, devices can encrypt simultaneously or completely offline with zero risk of collision.
  2. One .state File Per Pad, No Device Names: Each app/device keeps exactly one <chksum>.state file. There are no device names or IDs — partitions are identified purely by their byte ranges. The .state file records the device's own partition (offset/end_offset), queued grants (next_extent), and known peer partitions (peer_extent=start:end).
  3. Start with 1 Device (or N Devices) and Add Devices On The Fly: You do not need to know in advance how many devices or users will share a pad. A single device starts owning 100% of the pad ([32, pad_size)). Whenever you want to add a new device (or user), any existing device simply splits its remaining unused space in half (aligned to 256-byte chunk boundaries), saves its new lower end_offset and a peer_extent record to disk first, and emits a Pure-OTP OTP-GRANT-V1 message giving the upper half to the new device.
  4. Pure-OTP Rebalancing: Both onboarding a new device and giving more space to a device that is running low are the exact same operation: an existing device splits its unused [offset, end_offset) in half, shrinks its own end_offset to mid locally first, and produces a Pure-OTP OTP-GRANT-V1 message containing [mid+256, old_end_offset) (the 256 bytes at mid are consumed by the grant message itself). That OTP message can be passed to the other device over any channel (USB, QR code, file, email, or inside a normal OTP conversation) — indistinguishable from any normal OTP message!
  5. Passive Peer Tracking: Every OTP message carries its Pad-Offset. When a device decrypts a message whose offset falls inside a known peer_extent, it automatically advances that peer's start — so each device passively learns how much pad space its peers have left just by exchanging normal messages.

Visualizing Dynamic Device Addition & Work-Stealing Rebalancing

Suppose you generate a 120 MB pad on Device 1 (Laptop), later add Device 2 (Phone) and Device 3 (Tablet) on the fly, and eventually rebalance when the Phone runs low:

1. INITIAL STATE: Device 1 (Laptop) owns 100% of the pad (120 MB)
+---------------------------------------------------------------------------------------------------------------------+
|                                               Device 1: Laptop (120 MB)                                             |
| [0..120M)                                                                                                           |
+---------------------------------------------------------------------------------------------------------------------+
  ^
  Laptop cursor (0M)

2. ON-THE-FLY ONBOARDING (Adding Device 2: Phone)
   - Laptop has used 10 MB [0..10M), leaving 110 MB unused [10M..120M).
   - You run `./otp split` on Laptop to add your Phone.
   - Laptop cuts its 110 MB unused space in half at 65M:
     * Laptop shrinks its own end_offset from 120M -> 65M and records
       peer_extent=65000256:120M in its single .state file (saved FIRST).
     * Laptop prints an encrypted OTP-GRANT-V1 message for [65000256..120M).
     * Phone (with a copy of the same .pad file) decrypts that message once
       and its own single .state file adopts [65000256..120M).

+---------+-----------------------------------------------+-----------------------------------------------------------+
|#########|         Device 1: Laptop [10M..65M)           |               Device 2: Phone [65M..120M)                 |
| (10M)   |               (55 MB remaining)               |                    (55 MB remaining)                      |
+---------+-----------------------------------------------+-----------------------------------------------------------+
          ^                                               ^
          Laptop cursor (10M)                             Phone cursor (65M)

3. ON-THE-FLY ONBOARDING AGAIN (Adding Device 3: Tablet)
   - Later, you want to add a Tablet. You split from the Phone (currently at 70M of [65M..120M), so 50 MB unused).
   - Phone shrinks its end_offset from 120M -> 95M, records the peer extent,
     and emits an OTP-GRANT-V1 message giving [95000256..120M) to the Tablet:

+---------+-----------------------------------------------+-----+-------------------------+---------------------------+
|#########|         Device 1: Laptop [10M..65M)           |#####|  Device 2: Phone        |  Device 3: Tablet         |
|         |                                               |(5M) |  [70M..95M) (25 MB)     |  [95M..120M) (25 MB)      |
+---------+-----------------------------------------------+-----+-------------------------+---------------------------+
          ^                                                     ^                         ^
          Laptop cursor (10M)                                   Phone cursor (70M)        Tablet cursor (95M)

4. LATER: Phone is a heavy sender and reaches 94M of 95M (only 1 MB left!).
   Laptop barely sent anything and is still at 15M of 65M (50 MB unused).
+---------+----+------------------------------------------+------------------------------+--+-------------------------+
|##############|     Laptop [15M..65M = 50M UNUSED]       |#############################.|##|  Tablet [97M..120M)     |
+---------+----+------------------------------------------+------------------------------+--+-------------------------+
               ^                                                                        ^
               Laptop cursor (15M)                                                      Phone cursor (94M)

5. WORK-STEALING REBALANCE (Pure OTP — indistinguishable from normal messages):
   - Laptop runs `split` on its 50 MB unused space [15M..65M), cutting it in half at 40M.
   - Laptop updates its local .state end_offset to 40M and records the peer extent FIRST.
   - Laptop outputs a Pure-OTP OTP-GRANT-V1 message: "You now own [40000256..65M)!"
   - Phone decrypts/imports the grant, queues it as its next_extent, and seamlessly jumps there once 95M is reached:

+--------------+-------------------------+-------------------------+------------------------+-------------------------+
|##############|   Laptop [15M..40M)     |   NOW PHONE'S!          |########################|  Tablet [97M..120M)     |
|              |   (25 MB remaining)     |   Phone [40M..65M)      | (Phone 65M..95M used)  |  (23 MB remaining)      |
+--------------+-------------------------+-------------------------+------------------------+-------------------------+
               ^                         ^
               Laptop (15M)              Phone jumps here (40M) and keeps encrypting!

Extended State File Format (.state)

Each device keeps exactly one .state file per pad, tracking its own partition, queued grants, and known peer partitions — all identified namelessly by byte range:

offset=10485760
end_offset=65000000
next_extent=95000000:120000000
peer_extent=40000256:65000000
peer_extent=70002048:95000000
  • offset: Current write cursor (starts at 32, after the 32-byte checksum encryption key).
  • end_offset: Upper bound of this device's owned partition. If omitted (legacy .state files), it defaults to pad_size (100% single-owner pad, backward-compatible).
  • next_extent: Optional start:end range(s) received from a split/rebalance grant. When offset + chunk_size > end_offset, the encryptor automatically pops next_extent and sets offset = next_start, end_offset = next_end.
  • peer_extent: Optional start:end range(s) known to belong to other devices (recorded when this device splits off space). Automatically advanced when decrypting a message whose Pad-Offset falls inside the range — passive, nameless tracking of how much pad space each peer has consumed.

Additional Benefits

One-time pads can be trivially encrypted and decrypted using pencil and paper, making them accessible even without electronic devices.

Features

  • Perfect Security: Implements true one-time pad encryption with information-theoretic security
  • Traffic Analysis Resistance: Exponential bucketing with ISO/IEC 9797-1 Method 2 (Padmé) padding hides message lengths
  • Text & File Encryption: Supports both inline text and file encryption
  • Multiple Output Formats: Binary (.otp) and ASCII armored (.otp.asc) file formats
  • Hardware RNG Support: Direct entropy collection from TrueRNG USB devices with automatic detection
  • Keyboard Entropy: Optional keyboard entropy collection for enhanced randomness
  • Modular Architecture: Clean separation of concerns across multiple source modules
  • Short Command Flags: Convenient single-character flags for all operations
  • Automatic Versioning: Built-in semantic versioning with automatic patch increment
  • Multiple Build Options: Standard and static linking builds
  • Cross-Platform: Works on Linux and other UNIX-like systems

Quick Start

Download Pre-Built Binaries

Download Current Linux x86

Download Current Raspberry Pi 64

After downloading:

# Rename for convenience, then make executable
mv otp-v0.3.53-linux-x86_64 otp
chmod +x otp

# Run it
./otp

First Steps

  1. Generate your first pad:

    ./otp generate 1GB
    
  2. Encrypt a message:

    ./otp encrypt
    # Follow the interactive prompts
    
  3. Decrypt a message:

    ./otp decrypt
    # Paste the encrypted message
    

Building from Source

Prerequisites

  • GCC compiler
  • Make

Build Commands

make            # Build for current architecture
make static     # Static linking (standalone binary)
make clean      # Clean build artifacts
make install    # Install to /usr/local/bin/otp
make uninstall  # Remove from system

Output: build/otp-$(ARCH) (e.g., build/otp-x86_64)

After building, run with:

./build/otp-x86_64

Usage

The OTP Cipher operates in two modes:

Interactive Mode: Run without arguments to access a menu-driven interface. Best for exploring features, managing pads, and performing operations step-by-step with prompts and guidance.

Command Line Mode: Provide arguments to execute specific operations directly. Ideal for scripting, automation, and quick one-off tasks.

Interactive Mode

Launch the menu-driven interface:

./otp

Navigate through menus to generate pads, encrypt/decrypt messages, manage pads, and configure settings.

Command Line Mode

Execute operations directly with arguments:

# Generate a new pad
./otp generate 1GB

# Encrypt text (will prompt for input)
./otp encrypt <pad_hash_or_prefix>

# Decrypt message (will prompt for input)
./otp decrypt <pad_hash_or_prefix>

# List available pads
./otp list

Version System

Centralized Version Management

Version is defined in a single location: src/main.h

#define OTP_VERSION "v0.3.24"

All code references this constant, ensuring consistency across:

  • Main menu display
  • ASCII armor output
  • Help/usage text

Automatic Version Increment

The build.sh script automatically:

  1. Increments patch version (v0.3.24 → v0.3.25)
  2. Updates OTP_VERSION in src/main.h
  3. Creates git commit and tag
  4. Pushes to remote repository

Increment and Push Script

The increment_and_push.sh script handles versioned commits and releases:

./increment_and_push.sh "commit message"        # patch bump, commit, tag, push
./increment_and_push.sh -m "commit message"     # minor bump
./increment_and_push.sh -M "commit message"     # major bump
./increment_and_push.sh -r "commit message"     # release: build x86_64 + arm64,
                                                # create Gitea release, upload
                                                # binaries + source tarball

Manual Version Control

For major/minor releases, create tags manually:

# Feature release (minor bump)
git tag v0.4.0    # Next build: v0.4.1

# Breaking change (major bump)
git tag v1.0.0    # Next build: v1.0.1

Security Features

  • Uses /dev/urandom for cryptographically secure random number generation
  • Optional keyboard entropy mixing using simple XOR operations
  • Custom 256-bit XOR checksum for pad identification (encrypted with pad data)
  • Read-only pad files to prevent accidental modification
  • State tracking to prevent pad reuse
  • Message Length Hiding: Exponential bucketing (256B, 512B, 1KB, 2KB, 4KB...) prevents traffic analysis
  • ISO/IEC 9797-1 Method 2 Padding: Standard-compliant Padmé padding with 0x80 marker
  • Zero external crypto dependencies - completely self-contained implementation

Message Padding

All encrypted messages and files are automatically padded using exponential bucketing to resist traffic analysis attacks:

  • Minimum size: 256 bytes
  • Bucket sizes: 256B → 512B → 1KB → 2KB → 4KB → 8KB → ...
  • Padding method: ISO/IEC 9797-1 Method 2 (Padmé padding)
    • Appends 0x80 byte after message
    • Fills remaining space with 0x00 bytes
    • Unambiguous padding removal during decryption

Example: A 10-byte message is padded to 256 bytes, while a 300-byte message is padded to 512 bytes. This provides strong protection for small messages where length leakage matters most, with logarithmic overhead for larger messages.

Project Structure

otp/
├── run.sh                  # Build-if-needed launcher (./run.sh, ./run.sh test)
├── build.sh                # Build script with automatic versioning
├── increment_and_push.sh   # Version increment, commit, tag, push, release
├── Makefile                # Traditional make build system
├── README.md               # This file
├── .gitignore              # Git ignore rules
├── src/
│   ├── main.h           # Main header with all prototypes and OTP_VERSION
│   ├── main.c           # Application entry point and command line handling
│   ├── ui.c             # Interactive user interface and menu system
│   ├── state.c          # Global state management (pads directory, preferences)
│   ├── crypto.c         # Core cryptographic operations (XOR, base64)
│   ├── padding.c        # Message padding (exponential bucketing, Padmé padding)
│   ├── pads.c           # Pad management and file operations
│   ├── entropy.c        # Entropy collection from various sources
│   ├── trng.c           # Hardware RNG device detection and collection
│   ├── util.c           # Utility functions and helpers
│   ├── nostr_chacha20.c # ChaCha20 implementation for entropy expansion
│   └── nostr_chacha20.h # ChaCha20 header
├── build/
│   ├── otp-x86_64       # Native x86_64 binary (created by build)
│   └── otp-arm64        # ARM64 binary (created by cross-compilation)
├── plans/                  # Design documents and protocol specifications
├── pads/                   # OTP pad storage directory (created at runtime)
├── files/                  # Encrypted file storage (created at runtime)
└── tests/                  # Test scripts and utilities
    ├── test_padding.sh     # Padding implementation tests
    └── test_partition.sh   # Dynamic partition splitting tests

Architecture

The OTP cipher uses a modular architecture with clean separation of concerns:

  • main.c: Application entry point, command line parsing, and mode selection
  • ui.c: Interactive user interface, menus, and terminal management
  • state.c: Global state management (pads directory, terminal dimensions, preferences)
  • crypto.c: Core cryptographic operations (XOR encryption, base64 encoding)
  • padding.c: Message padding implementation (exponential bucketing, ISO/IEC 9797-1 Method 2)
  • pads.c: Pad file management, checksums, and state tracking
  • entropy.c: Entropy collection from keyboard, dice, files, and hardware RNG
  • trng.c: Hardware RNG device detection and entropy collection from USB devices
  • util.c: Utility functions, file operations, and helper routines
  • nostr_chacha20.c: ChaCha20 stream cipher for entropy expansion

All modules share a common header (src/main.h) that defines the public API, data structures, and version constant.

Hardware RNG Device Support

The OTP cipher includes comprehensive support for hardware random number generators (RNGs) to enhance entropy quality for pad generation and entropy addition operations.

Supported Devices

The system automatically detects and supports the following hardware RNG devices:

Device VID:PID Status Notes
TrueRNG 04d8:f5fe ✅ Working Original TrueRNG device
TrueRNG (Alt) 1fc9:8111 ✅ Working Alternative VID/PID combination
TrueRNG Pro 04d8:f5fe ✅ Working Professional version
TrueRNG Pro V2 04d8:f5fe ✅ Working Latest professional version

Device Detection

The system automatically scans /dev/ttyACM* ports and identifies hardware RNG devices by:

  1. USB VID/PID Detection: Reading vendor and product IDs from sysfs
  2. Device Type Classification: Identifying specific device variants
  3. Port Configuration: Applying device-specific serial port settings
  4. Interactive Selection: Presenting available devices for user selection

Testing Hardware Devices

A comprehensive test script is included to verify hardware RNG functionality:

# Run hardware device tests
./test.sh

Testing Partition Splitting

A test script covering the full multi-device partition workflow is included:

# Run partition splitting tests (pad generation, split, grants,
# extent rollover, boundary enforcement, cross-tool compatibility)
./tests/test_partition.sh    # or: ./run.sh test

Splitting a Pad Interactively (Multi-Device Setup)

To divide a pad among multiple devices from the interactive menu:

  1. Launch ./otp and choose Pads
  2. Select sPlit pad (add device / rebalance)
  3. Pick the pad to split (by checksum prefix)

The split halves the donor's remaining unused space, records the donated range as a peer_extent in the donor's single .state file, and prints an encrypted OTP-GRANT-V1 message. Deliver that message to the other device (paste into its decrypt, send over Nostr/email/QR, or carry on USB) — the recipient decrypts it once and its own .state file adopts the new partition. Repeat any time a new device joins.

Current Test Results

Based on testing with actual hardware devices:

✅ Working Devices:

  • TrueRNG (Type 1): Full functionality confirmed

  • TrueRNG Pro V2 (Type 3): Full functionality confirmed

    • Device is detected and identified correctly
    • Serial port configuration may need adjustment for this device variant

Usage in Entropy Collection

When generating pads or adding entropy, the system will:

  1. Auto-detect all connected hardware RNG devices
  2. Present a menu of available devices if multiple are found
  3. Test connectivity before beginning entropy collection
  4. Estimate completion time based on device speed testing
  5. Collect entropy with progress indicators and quality metrics

Device Configuration

Each device type uses optimized serial port settings:

  • TrueRNG devices: 3Mbps baud rate, 8N1, no flow control
  • Automatic timeout protection: Prevents hanging on unresponsive devices
  • Error recovery: Graceful handling of device disconnection during operation

Troubleshooting

If hardware RNG devices are not detected:

  1. Check USB connections: Ensure devices are properly connected
  2. Verify permissions: User must have access to /dev/ttyACM* devices
  3. Check device enumeration: Use lsusb to verify USB device recognition
  4. Review sysfs entries: Ensure VID/PID information is available in /sys/bus/usb/devices/

File Formats

.otp File Format (Binary)

Binary encrypted files use a structured header format:

Offset | Size | Field               | Description
-------|------|-------------------|----------------------------------
0      | 4    | Magic             | "OTP\0" - File type identifier
4      | 2    | Version           | Format version (currently 1)
6      | 32   | Pad Checksum      | Binary pad checksum (32 bytes)
38     | 8    | Pad Offset        | Offset in pad file (uint64_t)
46     | 4    | File Mode         | Original file permissions (uint32_t)
50     | 8    | File Size         | Original file size (uint64_t)
58     | var  | Encrypted Data    | XOR-encrypted file contents

.otp.asc File Format (ASCII Armored)

ASCII armored files use the same format as encrypted text messages:

-----BEGIN OTP MESSAGE-----
Version: v0.2.15
Pad-ChkSum: <64-character-hex-checksum>
Pad-Offset: <decimal-offset-value>

<base64-encoded-encrypted-data>
-----END OTP MESSAGE-----

Note: ASCII armored files do not preserve original file permissions metadata.

Usage Examples

Short Command Flags

# Quick commands using short flags
./otp -g 1GB                    # Generate 1GB pad
./otp -l                        # List available pads
./otp -e 1a2b "Hello world"     # Encrypt text inline
./otp -d "-----BEGIN OTP..."    # Decrypt message inline

# File operations
./otp -f document.pdf 1a2b                # Encrypt file (binary)
./otp -f document.pdf 1a2b -a             # Encrypt file (ASCII)
./otp -f document.pdf 1a2b -o secret.otp  # Custom output name

Text Encryption

# Interactive text encryption
./otp encrypt 1a2b3c
Enter text to encrypt: This is my secret message
# Outputs ASCII armored message

# Inline text encryption
./otp -e 1a2b3c "This is my secret message"
# Outputs ASCII armored message immediately

File Encryption

# Binary format (preserves metadata)
./otp -f sensitive.doc a1b2c3

# ASCII armored format (text-safe)
./otp -f sensitive.doc a1b2c3 -a

# Custom output filename
./otp -f sensitive.doc a1b2c3 -o encrypted_document.otp

Decryption

# Auto-detect format and pad from message/file
./otp -d encrypted.otp.asc
./otp -d "-----BEGIN OTP MESSAGE-----..."

# Interactive mode
./otp decrypt
# Prompts for encrypted message input

Build and Version Tracking

$ ./build.sh build
[INFO] Incrementing version...
[INFO] Current version: v0.2.14
[INFO] New version: v0.2.15
[SUCCESS] Created new version tag: v0.2.15
[SUCCESS] Build completed successfully

$ ./otp --help
OTP Cipher - One Time Pad Implementation v0.2.15
Built on 2025-08-10 at 14:07:58 from commit ae0afcf on branch master

Advanced Features

# Generate pad with keyboard entropy
./otp generate 5GB
# Follow prompts for keyboard entropy collection

# Check pad usage
./otp -l
Available pads:
No.  ChkSum (first 16 chars) Size         Used         % Used
---  -------------------   ----------   ----------   ------
1    97d9d82b5414a943      1.00GB       156B         0.0%
2    0c8e19fde996e683      1000B        248B         24.8%

# Show detailed pad information
./otp
# Select "S" for show pad info, enter checksum or prefix

Important Notes

Size Units: Decimal (SI) vs Binary (IEC)

This program uses decimal (SI) units for all size specifications, matching the behavior of most system tools like ls -lh, df -h, and file managers:

  • 1 KB = 1,000 bytes (not 1,024)
  • 1 MB = 1,000,000 bytes (not 1,048,576)
  • 1 GB = 1,000,000,000 bytes (not 1,073,741,824)
  • 1 TB = 1,000,000,000,000 bytes (not 1,099,511,627,776)

Why decimal units?

  • Consistency with system tools (ls, df, file managers)
  • Matches storage device marketing (a "1TB" USB drive has ~1,000,000,000,000 bytes)
  • Avoids confusion when comparing sizes across different tools
  • Industry standard for storage devices and file systems

Example: When you request a 100GB pad, the program creates exactly 100,000,000,000 bytes, which will display as "100GB" in ls -lh and your file manager.

Note: Some technical tools may use binary units (GiB, MiB) where 1 GiB = 1,024³ bytes. This program intentionally uses decimal units for user-friendliness and consistency with common tools.

License

This project includes automatic versioning system based on the Generic Automatic Version Increment System.

State Files

Pad state files (.state) use a human-readable text format:

offset=1234567890

This tracks how many bytes of each pad have been used. The format is:

  • Human-readable: Can inspect with cat checksum.state
  • Backward compatible: Automatically reads old binary format
  • Easy to debug: Can manually edit if needed

Contributing

When contributing:

  1. The version will automatically increment on builds via build.sh
  2. Version is centralized in src/main.h as OTP_VERSION
  3. For major features, manually create minor/major version tags
  4. Build artifacts in build/ and object files are auto-cleaned
S
Description
A secure one-time pad (OTP) cipher implementation in C99.
Readme
3.5 MiB
v0.3.53
Latest
2026-04-29 15:20:21 +00:00
Languages
C 85.9%
Shell 13.7%
Makefile 0.4%