Skip to main content

secure-string-cipher

CI License: MIT Python

A Beta AES-256-GCM encryption CLI with a passphrase vault, managed keys, and modern cryptographic defaults. Beta means the current v5/v2 format is usable and compatibility-sensitive, but the project is not yet stable or independently audited.

Beta Scope

  • File APIs treat regular files as opaque bytes. MAX_FILE_SIZE is an inclusive 100 MiB plaintext-payload limit; an SSC file may be larger because its magic, metadata, salt, nonce, and authentication tag are framing overhead. Directories and other special files are not supported.
  • Active and candidate vault representations, plus legacy key files, separately use the same 100 MiB value as a raw-input cap. Vault text is counted by UTF-8 bytes, and a value that could not be read back within the cap is not written.
  • original_filename is bounded metadata, not a path. The current version 5 and version 2 readers authenticate it before sanitizing it for automatic output selection; an explicit output remains authoritative. Version 4 names are never used to select an output.
  • Large framed SSC2 objects and a third-party security audit remain future work. Cross-process vault locking is implemented (advisory file locks with a bounded timeout); it has not been tested against real concurrent processes on every supported platform.

CI enforces a minimum 85% test coverage threshold on Python 3.14. The workflow also defines focused file and portable vault validation/transaction gates on Ubuntu, macOS, and Windows with Python 3.12; real OS keychain services are not tested there. The workflows are the current source of truth; dated acceptance records are clearly separated in the documentation archive.

Features

  • AES-256-GCM encryption for text and files with authenticated encryption
  • Argon2id key derivation – memory-hard, GPU/ASIC resistant
  • Key commitment scheme – binds each ciphertext to its derived key
  • Combined authentication (V2) – encrypt so that a single grant requires both a password and a managed key together (--with password --with key:ID --require all); each object still carries exactly one access grant, not independent multi-credential access
  • Legacy key-file mode – hashes any file's bytes into a symmetric passphrase (SHA-256 → Argon2id); this is not public-key or recipient encryption
  • Managed Keys (V2) – .ssckey create/import/export/show/list/rename are functional — see ROADMAP.md for the deferred ssc start interactive-menu parity item. archive/revoke/destroy change the vault record's status; by default encryption/decryption don't check it (a key file you hold still works, by design), but passing --vault LABEL alongside a key source now rejects a revoked or destroyed key that this vault tracks
  • OS Keychain integration – store vault in macOS Keychain, Windows Credential Vault, or Linux Secret Service
  • Hidden password input – passwords hidden in interactive terminals, visible for scripts/tests
  • Inline passphrase generation – type /gen at an interactive encryption/decryption passphrase prompt
  • Encrypted passphrase vault with HMAC-SHA256 integrity verification
  • Best-effort memory clearing via mutable buffers and libsodium when available
  • Timing-aware comparisons – designated secret equality checks use constant-time primitives
  • Local CLI rate limiting – exponential backoff on failed decrypt/vault attempts; it cannot prevent offline password guessing
  • Best-effort shred – overwrite then unlink; unreliable on SSDs, COW, snapshots, and journaled filesystems
  • Chunked file streaming for low memory usage (256 KiB for legacy files; V2 .ssc files currently use 64 KiB chunks, fixed and not CLI-configurable)
  • Automatic file-backend vault backups (last 5 kept)

Documentation

Quick Start

# Install from PyPI
pip install secure-string-cipher

# Run interactive CLI
ssc start

# Or use non-interactive CLI
ssc --help

Installation

# Recommended: install with pipx
pipx install secure-string-cipher

# Or with pip
pip install secure-string-cipher

# Or from source (dev/install with uv)
git clone https://github.com/TheRedTower/secure-string-cipher.git
cd secure-string-cipher
uv sync --extra dev --locked

# Run tooling with the locked environment
uv run --locked ssc --help

Requires Python 3.12+

PyPI provides the latest published release. The protected main branch may contain accepted changes that have not yet been included in a release.

Usage

Non-Interactive CLI (ssc)

For scripting and automation, use the ssc command:

# Encrypt text
ssc encrypt -t "Secret message"

# Encrypt a file
ssc encrypt -f document.pdf

# Encrypt using a key file
ssc encrypt -f document.pdf --key-file /path/to/key.pem

# Decrypt a file (restores original filename by default)
ssc decrypt -f document.pdf.enc

# Decrypt with an explicit output path
ssc decrypt -f document.pdf.enc --output document.pdf

# Decrypt without restoring the stored filename
ssc decrypt -f document.pdf.enc --no-restore-filename

# Decrypt using a vault password
ssc decrypt -f document.pdf.enc --vault my-server

# Decrypt using a key file
ssc decrypt -f document.pdf.enc --key-file /path/to/key.pem

# Encrypt a file requiring both a password and a managed key (single combined grant)
ssc encrypt -f document.pdf --with password --with key:my-key --require all

# Decrypt a V2 file (no --with/--require on decrypt: the credential type is
# read from the file's header and the CLI prompts for what it needs)
ssc decrypt -f document.pdf.ssc

# Store a password in vault
ssc store my-server

# Auto-generate and store a password
ssc store backup-key --generate

# Vault management
ssc vault list
ssc vault delete old-key
ssc vault export > backup.txt
ssc vault import backup.txt  # authenticates before confirmation/replacement
ssc vault backups             # list exact stable backup identifiers
ssc vault restore BACKUP_ID   # authenticate and transactionally restore

Exit codes: 0=success, 1=input error, 2=auth error, 3=vault error, 4=file error

Security: Passwords are never passed via command line arguments (prevents shell history exposure). All passwords are prompted interactively or retrieved from the vault (or from managed keys).

ssc vault export writes canonical six-line UTF-8 bytes with no BOM or terminal newline. The CLI importer also recovers files created by older redirected exports with exactly one terminal LF or CRLF; direct vault APIs remain byte-strict and no other whitespace is normalized.

For -f -, encryption accepts at most 100 MiB of plaintext from stdin. Decryption bounds the encoded token from that maximum frame, accepts at most one terminal LF or CRLF added by shell transport, and rechecks the decoded plaintext limit before writing stdout.

Oversize and non-regular filesystem inputs use file error code 4; they are not reported as wrong-password or corrupted-ciphertext failures.

--force permits final atomic replacement only after encryption completes or decryption authenticates. It never pre-deletes the existing destination.

Interactive CLI (ssc start)

For interactive use, run:

ssc start

You'll see this menu:

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                       AVAILABLE OPERATIONS                       ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃                                                                  ┃
┃  TEXT & FILE ENCRYPTION                                          ┃
┃                                                                  ┃
┃    [1] Encrypt Text       ->  Encrypt a message (base64)         ┃
┃    [2] Decrypt Text       ->  Decrypt an encrypted message       ┃
┃    [3] Encrypt File       ->  Encrypt a file (creates .enc)      ┃
┃    [4] Decrypt File       ->  Decrypt an encrypted file          ┃
┃                                                                  ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃  PASSPHRASE VAULT (Optional)                                     ┃
┃                                                                  ┃
┃    [5] Generate Passphrase  ->  Create random password           ┃
┃    [6] Store in Vault       ->  Save passphrase securely         ┃
┃    [7] Retrieve from Vault  ->  Get stored passphrase            ┃
┃    [8] List Vault Entries   ->  View all stored labels           ┃
┃    [9] Manage Vault         ->  Update, delete, export           ┃
┃                                                                  ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃  SECURITY TOOLS                                                  ┃
┃                                                                  ┃
┃   [10] Secure Shred       ->  Best-effort overwrite and delete   ┃
┃   [11] Use Key File       ->  Encrypt/decrypt w/ key file        ┃
┃                                                                  ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃    [0] Exit                ->  Quit application                  ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Choose an option and follow the prompts.

Quick Passphrase Generation

When prompted for a password during encryption, you can type /gen (or /generate or /g) to instantly generate a strong passphrase:

Enter passphrase: /gen

🔑 Auto-Generating Secure Passphrase...

✅ Generated secure passphrase (hidden)

💾 Save generated passphrase to vault
Enter a label for this passphrase (e.g., 'project-x'): backup-2025
Enter master password to encrypt vault: ••••••••••••
✅ Passphrase stored in vault!
Vault location: ~/.secure-cipher/passphrase_vault.enc

✅ Using stored passphrase for current operation...

The generated secret is never printed. Inline generation uses the 24-character alphanumeric-with-symbols strategy and requires successful vault storage before the current operation continues.

Passphrase Vault

The vault stores passphrases encrypted with your master password at ~/.secure-cipher/passphrase_vault.enc:

  • Generate & store – Option 5 or /gen during encryption
  • Manual storage – Option 6 for existing passphrases
  • Retrieve/manage – Options 7-9 for lookup, listing, and deletion

Vault candidates are fully decoded and authenticated before import or restore. The transaction service snapshots the active raw vault, safely publishes a collision-resistant backup, writes through the configured file/keychain backend, reads back and revalidates, and rolls back on post-write failure. Filesystem writes use atomic replacement; native credential-store rollback is best effort because those stores do not expose a multi-record transaction. Vault mutations hold a cooperative, advisory cross-process file lock (bounded timeout, re-entrant per thread) to serialize concurrent writers; it has not been exercised against real multi-process contention on every supported platform, and it does nothing for readers or for non-lock-aware external tools touching the same file.

OS Keychain Integration

Optionally store your vault in the OS keychain for added security:

# Existing pipx install: add keychain support to the pipx venv
pipx inject secure-string-cipher keyring

# New pipx install with keychain support
pipx install 'secure-string-cipher[keychain]'

# Or for a normal pip/venv install
python -m pip install 'secure-string-cipher[keychain]'

# Migrate existing vault to keychain
ssc vault migrate --to keychain

# Migrate back to file if needed
ssc vault migrate --to file

See docs/KEYCHAIN.md for full setup instructions per platform.

Docker

Use the pre-built image (Python 3.14-alpine based):

# Pull and run
docker pull ghcr.io/theredtower/secure-string-cipher:latest
docker run --rm -it --network none \
  ghcr.io/theredtower/secure-string-cipher:latest

# Or with Docker Compose
git clone https://github.com/TheRedTower/secure-string-cipher.git
cd secure-string-cipher
docker compose up -d
docker compose exec cipher ssc start

To encrypt files in your current directory:

docker run --rm -it \
  --network none \
  -v "$PWD:/data" \
  ghcr.io/theredtower/secure-string-cipher:latest

With persistent vault and backups:

docker run --rm -it \
  --network none \
  -v "$PWD/data:/data" \
  -v "$PWD/vault:/vault" \
  -v "$PWD/backups:/backups" \
  ghcr.io/theredtower/secure-string-cipher:latest

Container details: The Alpine-based image runs as non-root UID 1000. The commands above disable networking with --network none, and the supplied Compose service applies the equivalent network_mode: none. Network isolation is a runtime policy; the image does not impose it when started without one of those settings.

Programmatic API

Use secure-string-cipher as a library in your Python projects:

Text Encryption

from secure_string_cipher import encrypt_text, decrypt_text

# Encrypt a message
ciphertext = encrypt_text("Secret message", "MySecurePass123!")
print(ciphertext)  # Base64-encoded string

# Decrypt it back
plaintext = decrypt_text(ciphertext, "MySecurePass123!")
print(plaintext)  # "Secret message"

File Encryption

from secure_string_cipher import encrypt_file, decrypt_file

# Encrypt a file (explicit output path)
encrypt_file("document.pdf", "document.pdf.enc", "MySecurePass123!")

# Replace only after complete encryption and sync
encrypt_file("document.pdf", "document.pdf.enc", "MySecurePass123!", overwrite=True)

# Decrypt it (explicit output path)
output_path, metadata = decrypt_file(
    "document.pdf.enc",
    "document.pdf",
    "MySecurePass123!",
    overwrite=True,
)

File operations apply a best-effort symlink preflight (with a narrow system-path allowance such as /var). This does not close hostile path races.

Passphrase Generation

from secure_string_cipher import generate_passphrase

# Default: six-word passphrase. Keep the returned secret out of logs.
passphrase, entropy_bits = generate_passphrase()

# Alternative: 24 characters drawn from letters, digits, and symbols.
token, token_entropy_bits = generate_passphrase(
    "alphanumeric", length=24, include_symbols=True
)

Vault Operations

from secure_string_cipher import PassphraseVault

# Create or open vault
vault = PassphraseVault()

# Store a passphrase
vault.store_passphrase("my-server", "MySecurePass123!", master_password="VaultMaster456!")

# Retrieve it
password = vault.retrieve_passphrase("my-server", master_password="VaultMaster456!")

# List all labels (requires master password)
labels = vault.list_labels(master_password="VaultMaster456!")

# Update an entry
vault.update_passphrase("my-server", "NewPass789!", master_password="VaultMaster456!")

# Delete an entry
vault.delete_passphrase("my-server", master_password="VaultMaster456!")

Security Utilities

from secure_string_cipher import (
    check_password_strength,
    constant_time_compare,
    has_secure_memory,
)

# Validate password strength
is_strong, issues = check_password_strength("weak")
if not is_strong:
    print(f"Password issues: {issues}")

# Constant-time primitive for a timing-sensitive equality check
if constant_time_compare(user_input, stored_hash):
    print("Match!")

# Check if libsodium secure memory is available
if has_secure_memory():
    print("Using libsodium for secure memory zeroing")

Security

Component Implementation Details
Encryption AES-256-GCM Authenticated encryption, 128-bit tags
Key Derivation Argon2id 64 MiB memory, 3 iterations, parallelism 4
Key Commitment HMAC-SHA256 Prevents partitioning oracle attacks
Vault Integrity HMAC-SHA256 Detects tampering before decryption
Memory Clearing Best effort Mutable buffers/libsodium where available; Python may retain copies
Timing Safety Constant-time primitives Commitment, HMAC, and designated digest equality checks
Rate Limiting Local exponential backoff CLI throttle only; offline guessing remains possible
Vault Import/Restore Full HMAC, decrypt, JSON-schema, read-back verification Filesystem atomic publication; credential-store rollback is best effort

Additional protections: best-effort symlink preflight checks, opened-input regular-file and size validation, cumulative streaming limits, atomic final publication, user-only file permissions (0o600), and strength validation for passwords created by the interactive encryption menu or manually entered by ssc store. Atomic publication protects against ordinary operation failures; hostile concurrent path replacement still requires descriptor-relative opening and is not claimed to be prevented. The temporary file is synced before replacement; parent-directory sync after replacement is best effort on supported platforms. Other CLI password prompts, including decryption and vault-master prompts, accept existing credentials for compatibility; low-level programmatic encryption functions accept caller-provided passphrases directly.

Password input: When running interactively, passwords are hidden (using getpass). When stdin is piped or redirected (scripts, automation, tests), passwords are visible. This allows both secure interactive use and scriptable automation.

Python memory limitations: Memory clearing is best-effort. Even with libsodium, Python strings are immutable and the runtime may copy objects. Use has_secure_memory() to check libsodium availability.

Local records and deletion limitations: The audit/event log is editable local JSON and is not tamper-evident. Overwrite-based deletion cannot reliably erase data from SSD wear levelling, copy-on-write storage, snapshots, backups, or journaled filesystems.

Legacy key-file limitation: Anyone with identical key-file bytes can derive the same symmetric passphrase and decrypt. PEM or public-key-shaped input does not create RSA, recipient, or public-key encryption. Treat this mode as legacy pending the secret .ssckey design.

Development

git clone https://github.com/TheRedTower/secure-string-cipher.git
cd secure-string-cipher
uv sync --extra dev --locked

# Run the non-mutating local acceptance gate
make ci

See DEVELOPER.md for detailed development workflow and CONTRIBUTING.md for contribution guidelines.

License

MIT License. See LICENSE for details.

Metadata

Release files for secure-string-cipher 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for secure-string-cipher 2.0.0
File Size Uploaded
secure_string_cipher-2.0.0.tar.gz 3.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for secure-string-cipher 2.0.0
File Interpreter ABI Platform
secure_string_cipher-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 3.7 MB

Release files / secure_string_cipher-2.0.0.tar.gz

Download URL secure_string_cipher-2.0.0.tar.gz
Size 3.6 MB
Tags Source
SHA-256 checksum
How to use checksums
96d5bfb01e36b5f2bab6f74370e6f02f30a230815d2466d905fe97cbd7ba7a04
BLAKE2b-256 checksum
How to use checksums
652345057f4441b82c6cee284391b55b4246dd5393c701a210bc478a9c52823b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release files / secure_string_cipher-2.0.0-py3-none-any.whl

Download URL secure_string_cipher-2.0.0-py3-none-any.whl
Size 147.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ad3ce6e4eb8ac6e49503646ad00f27f3455644e0b455f90307a243df3b025b20
BLAKE2b-256 checksum
How to use checksums
6b373718cd63ab2b500c3cf715d61d3e52ed1095ba70a096e25552610410541e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page