v4.2.0: full (pure) post-quantum mode + critical dedup nonce fix
Add a native full post-quantum encryption mode and fix a critical keystream-reuse bug in deduplicated encrypted archives. Full post-quantum mode (--pq-only) - New envelope type 0x06 (ZUPT_ENC_PQ_ONLY): ML-KEM-768 (FIPS 203) as the sole key-establishment mechanism, with no classical X25519 component. Archive key = SHA3-512(ml_ss || ml_ct || "ZUPT-PQ-ONLY-v1"). - For compliance postures that require a single NIST-standardised PQ primitive with no classical KEM in the envelope (CNSA 2.0-style "PQ-only"). Hybrid --pq stays the recommended default; --pq-only has no classical fallback, so a break of ML-KEM-768 alone breaks it. - keygen --pq-only / keygen --pub --pq-only (ZPQK magic, 1200B pub / 3600B priv; not interchangeable with hybrid --pq keys). Wrong or tampered ciphertext is rejected via ML-KEM FO implicit rejection plus the HMAC-SHA256 Encrypt-then-MAC envelope. In-tree, default build. Security (critical): AES-256-CTR keystream reuse under --dedup - Dedup assigns block sequence 0 to every data block (the sentinel that keeps cross-file dedup references authenticating consistently). The per-block nonce was base_nonce XOR block_seq, so under --dedup every block collapsed to the same nonce, reusing the CTR keystream across distinct plaintexts (a many-time-pad). Each block now uses a fresh random 128-bit nonce stored in the block prefix and bound into the block MAC; block_seq is still bound as MAC AAD. Regression test: tests/test_dedup_nonce.sh. Re-encrypt any --dedup encrypted archives written by <= 4.1.0. Other - keygen --sdk / --box on a source-only build now fails with a clear message pointing to native --pq / --pq-only (or a WITH_SDK=1 build). - Documentation: README, SECURITY, THREAT_MODEL, man page, CHANGELOG, and all packaging recipes updated for the new mode and the security fix; version bumped to 4.2.0. Wire format v1.6 unchanged (0x06 is additive). Validation: make check 16/16, quick suite 11/11 (incl. PQ-only), dedup-nonce regression (all block nonces distinct), cppcheck clean.
This commit is contained in:
parent
31fa4028aa
commit
124958aea9
24 changed files with 811 additions and 123 deletions
147
doc/vaptvupt.1
147
doc/vaptvupt.1
|
|
@ -1,7 +1,7 @@
|
|||
.\" Manpage for vaptvupt (formerly zupt; INPI Brasil trademark rename in v3.0.0)
|
||||
.\" SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
.\" Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||
.TH VAPTVUPT 1 "July 2026" "vaptvupt 4.1.0" "User Commands"
|
||||
.TH VAPTVUPT 1 "July 2026" "vaptvupt 4.2.0" "User Commands"
|
||||
|
||||
.SH NAME
|
||||
vaptvupt \- post-quantum backup compression utility (formerly zupt)
|
||||
|
|
@ -125,7 +125,8 @@ archive without restoring it. Exit code is non-zero on any failure.
|
|||
.B info
|
||||
Print archive header metadata without requiring the decryption key.
|
||||
Reports: format version, codec, encryption type (none / PBKDF2 /
|
||||
Argon2id / ML-KEM-768+X25519), KDF iteration count, file count,
|
||||
Argon2id / ML-KEM-768+X25519 hybrid / ML-KEM-768 pure-PQ), KDF
|
||||
iteration count, file count,
|
||||
creation timestamp, archive UUID, AIT presence. Safe to run on an
|
||||
untrusted archive.
|
||||
|
||||
|
|
@ -157,18 +158,32 @@ after each block.
|
|||
|
||||
.TP
|
||||
.B keygen
|
||||
Generate a key file for keyfile-mode encryption. With
|
||||
Generate a key file. With no PQ flag, writes a 32-byte raw key for
|
||||
keyfile-mode encryption. With
|
||||
.B --pq
|
||||
generates a native ML-KEM-768 + X25519 \fBhybrid\fR keypair for
|
||||
.B --pq
|
||||
encryption (in-tree crypto; no external library). With
|
||||
.B --pq-only
|
||||
generates a native pure ML-KEM-768 keypair (magic
|
||||
.BR ZPQK )
|
||||
for
|
||||
.B --pq-only
|
||||
encryption. With
|
||||
.B --sdk
|
||||
generates an ML-KEM-768 + X25519 hybrid keypair suitable for
|
||||
generates a keypair for the optional
|
||||
.B --pq-sdk
|
||||
mode. With
|
||||
mode, and with
|
||||
.B --box
|
||||
generates a libpqvaptvupt sealed-box keypair (writes \fIfile\fR and
|
||||
\fIfile\fR.pub) for
|
||||
a libpqvaptvupt sealed-box keypair for
|
||||
.B --pq-box
|
||||
mode. With
|
||||
(both need a
|
||||
.B WITH_SDK=1
|
||||
build). With
|
||||
.B --pub
|
||||
extracts the public key from an existing private key.
|
||||
extracts the public key from an existing private key (combine with the
|
||||
matching PQ flag, e.g.
|
||||
.BR "keygen --pub --pq-only" ).
|
||||
|
||||
.SH GLOBAL OPTIONS
|
||||
|
||||
|
|
@ -233,10 +248,55 @@ Choose key-derivation function for password mode:
|
|||
Use a 32-byte raw key file (generated with
|
||||
.BR "vaptvupt keygen" ).
|
||||
|
||||
.TP
|
||||
.B --pq \fIpub\fR
|
||||
Enable native post-quantum \fBhybrid\fR encryption (envelope type 0x02,
|
||||
recommended). Combines ML-KEM-768 (FIPS 203) with X25519 (RFC 7748) so
|
||||
the archive key is secure unless \fBboth\fR the lattice KEM and the
|
||||
elliptic-curve exchange are broken. Uses the in-tree crypto only — no
|
||||
external library, always available. The
|
||||
.I pub
|
||||
argument is the recipient's public-key file from
|
||||
.BR "vaptvupt keygen" .
|
||||
On extraction, pass the secret key:
|
||||
.B --pq
|
||||
\fIpriv\fR.
|
||||
|
||||
.TP
|
||||
.B --pq-only \fIpub\fR
|
||||
Enable native \fBfull\fR (pure) post-quantum encryption (envelope type
|
||||
0x06). ML-KEM-768 is the \fIsole\fR key-establishment mechanism — no
|
||||
X25519 component. Choose this only when a policy mandates a single
|
||||
NIST-standardised PQ primitive with no classical KEM in the envelope
|
||||
(e.g. CNSA 2.0-style "PQ-only" postures). The archive key is
|
||||
SHA3-512(ml_ss || ml_ct || "ZUPT-PQ-ONLY-v1"). Note the deliberate
|
||||
trade-off: unlike
|
||||
.BR --pq ,
|
||||
a future weakness in ML-KEM-768 alone is sufficient to break the
|
||||
envelope, because there is no classical KEM to fall back on. When in
|
||||
doubt use
|
||||
.B --pq
|
||||
(hybrid). Keys are generated with
|
||||
.BR "vaptvupt keygen --pq-only" ;
|
||||
the private and public key files (magic
|
||||
.BR ZPQK )
|
||||
are not interchangeable with hybrid
|
||||
.B --pq
|
||||
keys. On extraction, pass the secret key:
|
||||
.B --pq-only
|
||||
\fIpriv\fR.
|
||||
|
||||
.TP
|
||||
.B --pq-box \fIpub\fR
|
||||
Enable post-quantum sealed-box encryption via the vendored libpqvaptvupt
|
||||
(v4.0.0+, envelope type 0x05). ML-KEM-768 + X25519 shared secrets are
|
||||
Enable post-quantum sealed-box encryption via libpqvaptvupt (envelope
|
||||
type 0x05). \fBRequires an optional\fR \fBWITH_SDK=1\fR \fBbuild\fR: the
|
||||
default source-only tree ships no vendored library, so this mode is
|
||||
absent unless you build against libpqvaptvupt yourself. Prefer the
|
||||
native
|
||||
.B --pq
|
||||
or
|
||||
.B --pq-only
|
||||
modes, which need no external library. ML-KEM-768 + X25519 shared secrets are
|
||||
combined through HKDF-SHA256 with a domain-separating info string
|
||||
("pqvv-seal-v1"); the box carries AES-256-CTR + HMAC-SHA256
|
||||
Encrypt-then-MAC. The
|
||||
|
|
@ -249,8 +309,13 @@ On extraction, pass the secret key:
|
|||
\fIpriv\fR.
|
||||
.TP
|
||||
.B --pq-sdk \fIpub\fR
|
||||
Enable post-quantum hybrid encryption. Uses ML-KEM-768 + X25519 with
|
||||
HKDF combiner, HPKE binding, and key commitment. The
|
||||
Enable post-quantum hybrid encryption via libzuptsdk (envelope type
|
||||
0x03). \fBRequires an optional\fR \fBWITH_SDK=1\fR \fBbuild\fR and is
|
||||
absent from the default source-only tree; use the native
|
||||
.B --pq
|
||||
instead, which provides the same ML-KEM-768 + X25519 hybrid with no
|
||||
external dependency. Uses ML-KEM-768 + X25519 with HKDF combiner, HPKE
|
||||
binding, and key commitment. The
|
||||
.I pub
|
||||
argument is the recipient's public-key file generated by
|
||||
.BR "vaptvupt keygen --sdk" .
|
||||
|
|
@ -293,11 +358,16 @@ flag of the same name.
|
|||
.SH POST-QUANTUM ENCRYPTION
|
||||
|
||||
.B vaptvupt
|
||||
implements a hybrid KEM as specified in FIPS 203 (ML-KEM) combined
|
||||
with X25519 (RFC 7748). The session key is derived as:
|
||||
offers two native post-quantum modes, both built entirely from the
|
||||
in-tree crypto (no external library):
|
||||
|
||||
.RS
|
||||
.IP "\fB--pq\fR (hybrid, recommended)" 4
|
||||
A hybrid KEM combining ML-KEM-768 (FIPS 203) with X25519 (RFC 7748).
|
||||
The archive key is derived as:
|
||||
.RS
|
||||
.nf
|
||||
|
||||
ss_pq = ML-KEM-768.decaps(sk_pq, ct_pq)
|
||||
ss_ec = X25519(sk_ec, pk_ec_peer)
|
||||
session = HKDF-SHA256(ss_pq || ss_ec,
|
||||
|
|
@ -305,10 +375,38 @@ session = HKDF-SHA256(ss_pq || ss_ec,
|
|||
salt = archive_uuid)
|
||||
.fi
|
||||
.RE
|
||||
The hybrid combiner means the session key is at least as strong as the
|
||||
strongest of {ML-KEM-768, X25519}: an attacker must break \fBboth\fR to
|
||||
recover the key. This is the default recommendation and the right choice
|
||||
for almost every user, because it stays secure even if one primitive is
|
||||
later found weak.
|
||||
.IP "\fB--pq-only\fR (full / pure PQ)" 4
|
||||
ML-KEM-768 as the \fIsole\fR key-establishment mechanism, with no
|
||||
classical component. The archive key is derived as:
|
||||
.RS
|
||||
.nf
|
||||
|
||||
The hybrid combiner means the session key is at least as strong as
|
||||
the strongest of {ML-KEM-768, X25519}: an attacker must break both
|
||||
to recover the key.
|
||||
(ss_pq, ct_pq) = ML-KEM-768.encaps(pk_pq)
|
||||
archive_key = SHA3-512(ss_pq || ct_pq || "ZUPT-PQ-ONLY-v1")
|
||||
.fi
|
||||
.RE
|
||||
Use this only when a compliance posture requires a single
|
||||
NIST-standardised PQ primitive with no classical KEM in the envelope
|
||||
(for example CNSA 2.0-style "PQ-only" requirements). The deliberate
|
||||
trade-off is that the envelope has \fBno hybrid safety net\fR: a future
|
||||
cryptanalytic break of ML-KEM-768 alone breaks the archive, whereas
|
||||
under
|
||||
.B --pq
|
||||
the attacker would still have to break X25519 as well. Unless a policy
|
||||
forbids the classical component, prefer
|
||||
.BR --pq .
|
||||
.RE
|
||||
|
||||
.PP
|
||||
Both modes carry the same authenticated envelope as password mode:
|
||||
per-block AES-256-CTR with a fresh random 128-bit nonce, HMAC-SHA256
|
||||
Encrypt-then-MAC, and ML-KEM Fujisaki-Okamoto implicit rejection, so a
|
||||
wrong or tampered ciphertext is rejected rather than yielding garbage.
|
||||
|
||||
.PP
|
||||
.B Key commitment:
|
||||
|
|
@ -537,6 +635,19 @@ $ vaptvupt compress --pq mykey.pub backup.zupt ~/Documents
|
|||
.fi
|
||||
.RE
|
||||
|
||||
Compress with full (pure) post-quantum encryption — ML-KEM-768 only,
|
||||
no classical component (compliance postures that mandate a single PQ
|
||||
primitive):
|
||||
|
||||
.RS
|
||||
.nf
|
||||
$ vaptvupt keygen --pq-only -o pqkey
|
||||
$ vaptvupt keygen --pub --pq-only -o pqkey.pub -k pqkey
|
||||
$ vaptvupt compress --pq-only pqkey.pub backup.zupt ~/Documents
|
||||
$ vaptvupt extract --pq-only pqkey -o restored backup.zupt
|
||||
.fi
|
||||
.RE
|
||||
|
||||
Backup a block device, sparse-aware:
|
||||
|
||||
.RS
|
||||
|
|
|
|||
Loading…
Reference in a new issue