Skip to content

Qsafe 4.0: durable public-key workflow, pipes, metadata, cross-platform#1

Open
SP1R4 wants to merge 4 commits into
masterfrom
v4-public-key-workflow
Open

Qsafe 4.0: durable public-key workflow, pipes, metadata, cross-platform#1
SP1R4 wants to merge 4 commits into
masterfrom
v4-public-key-workflow

Conversation

@SP1R4

@SP1R4 SP1R4 commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the core data-loss bug where every encrypt regenerated and overwrote the keypair, making previously encrypted files unrecoverable. Qsafe now follows a true public-key workflow with a one-time keygen step, plus a set of usability and portability improvements.

The critical fix — durable, asymmetric keys

  • New keygen command generates the ML-KEM-1024 keypair once: secret_key.bin is passphrase-wrapped (0600), secret_key.bin.pub is stored in the clear.
  • encrypt uses only the public key (no passphrase); decrypt uses the secret key. Encrypting a second file no longer invalidates earlier ciphertexts.

Usability

  • Auto-detect file vs. directory — the trailing file|dir argument is gone.
  • Optional output paths with sensible defaults (report.pdfreport.pdf.qsafe, and back).
  • Passphrase via interactive no-echo prompt, $QSAFE_PASSPHRASE, or --passphrase-file instead of being required on the command line. Options may appear before or after the command.

New capabilities (format bump to QSAFE004)

  • stdin/stdout piping via - (composes with tar, ssh, …). Drove moving the nonce to the front and recovering the GCM tag via 16-byte hold-back, so decryption streams without seeking.
  • Metadata preservation: original filename, mode, and mtime are encrypted+authenticated and restored on decrypt; decrypt file.qsafe ./dir/ restores into the directory under the original name.

Build / platform

  • Binary renamed crypto-v2qsafe; added make install/uninstall (honors PREFIX/DESTDIR).
  • Makefile auto-discovers Homebrew openssl@3/liboqs on macOS, falls back to system paths on Linux. setup.sh handles both.

Tests / docs

  • Extended unit tests (public-key round-trip) and rewrote test.sh (keygen, pipes, dir mode, filename/permission restore, rejection cases). README and flow diagram updated.
  • Full suite passes: 59/59 unit + 34/34 integration.

Breaking changes

  • QSAFE003 files cannot be read by 4.0 — decrypt them with a 3.x build first.
  • CLI changed: no trailing file|dir arg, optional output paths, binary renamed to qsafe.

SP1R4 and others added 4 commits May 21, 2026 21:54
Breaking change — the on-disk format is bumped to "QSAFE003" and is
incompatible with 2.x files (different KEM and key-file layout).

Security
- Switch KEM from round-3 Kyber1024 to ML-KEM-1024, so the FIPS 203
  claim is accurate (OQS_KEM_alg_ml_kem_1024, liboqs 0.10+).
- Protect the secret key file with scrypt (memory-hard) + a random
  per-file salt instead of feeding the passphrase straight into HKDF.
- Authenticate the version header and KEM ciphertext as GCM AAD.
- Wipe key buffers with OPENSSL_cleanse, including the previously
  un-zeroed passphrase-derived key buffers.
- Allocate the KEM shared-secret buffer from kem->length_shared_secret
  rather than assuming 32 bytes.
- Remove partial output files when encryption/decryption fails.

Correctness
- Make decryption truly streaming: it no longer loads the whole
  ciphertext into memory (the README's constant-memory claim now holds).
- Implement --force: without it, Qsafe prompts before overwriting an
  existing output file (previously the flag was a no-op).
- Fix the progress bar (it only rendered at 100%) and guard against a
  division by zero on empty input.
- Make directory processing recursive and skip the secret key file.

Build, tests, docs
- Harden the Makefile (-Wextra, -fstack-protector-strong,
  _FORTIFY_SOURCE, -std=c11) and build the binary before `make test`.
- Add a GitHub Actions CI workflow (build liboqs, build, run tests).
- Expand the unit/integration suites: scrypt KDF, salt randomness,
  empty/single-chunk/multi-chunk round-trips, recursive directories,
  KEM-ciphertext tampering, argument validation.
- Rewrite README for accuracy; remove the stale docs/ReadMe.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fix the core data-loss bug where every encrypt regenerated and overwrote
the keypair, making previously encrypted files unrecoverable. Qsafe now
follows a true public-key workflow with a one-time keygen step.

- keygen: generate the ML-KEM-1024 keypair once; secret_key.bin is
  passphrase-wrapped (0600), secret_key.bin.pub is stored in the clear
- encrypt uses only the public key (no passphrase); decrypt uses the
  secret key. Encrypting never invalidates earlier ciphertexts
- new QSAFE004 format: nonce moved to the front + 16-byte tag hold-back
  so decryption streams from a pipe without seeking
- stdin/stdout piping via '-' (composes with tar, ssh, etc.)
- encrypted+authenticated metadata block preserves original name, mode,
  and mtime; decrypt can restore into a directory by original name
- auto-detect file vs dir (drop the trailing file|dir arg); optional
  output paths with sensible defaults
- passphrase via interactive no-echo prompt, $QSAFE_PASSPHRASE, or
  --passphrase-file instead of being required on the command line
- rename binary crypto-v2 -> qsafe; add make install/uninstall
- cross-platform Makefile + setup.sh (macOS Homebrew and Linux)
- update unit + integration tests and docs; full suite passes
glibc restricts to strict ISO C with -std=c11, hiding mode_t, PATH_MAX,
fileno, tcgetattr, realpath, and utime. macOS exposes them by default, so
the build only failed in CI. Define _DEFAULT_SOURCE at the top of both
translation units and add <sys/types.h> for mode_t.
GNU stat -f means --file-system and succeeds with the wrong output, so the
BSD-first fallback never ran. Try GNU 'stat -c %a' first, then BSD 'stat -f %Lp'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant