Skip to content

Commit 7aa23bb

Browse files
committed
Add PEP about post-quantum encryption migration
1 parent d272924 commit 7aa23bb

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ docs/drafts/*
1717
!docs/drafts/pep-03.md
1818
!docs/drafts/pep-04.txt
1919
!docs/drafts/pep-04-01.txt
20+
!docs/drafts/pep-13-pq-encryption.md
2021
!docs/drafts/pep-14-encrypted-keystore.md
2122
!docs/drafts/pep-15-hd-mnemonic-keys.md
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# PEP-13: post-quantum-encryption (hybrid PQC encryption scheme)
2+
3+
Status: draft
4+
Author: NCrashed
5+
Depends on: PEP-02 (ACB), current GroupKeySymm implementation
6+
Related: PEP-15 (HD keys from a mnemonic), PEP-14 (encrypted storage)
7+
8+
## Motivation
9+
10+
The confidentiality of an encrypted repository rests entirely on
11+
elliptic-curve asymmetric crypto (X25519 sealed box when wrapping the
12+
group secret for each recipient; Ed25519 when signing refs and
13+
identities). Both are broken by Shor's algorithm on a
14+
cryptographically relevant quantum computer (CRQC).
15+
16+
The symmetric layer (XSalsa20-Poly1305 on a 256-bit key, HKDF-SHA-512,
17+
Merkle hashes) is quantum-sufficient: Grover's algorithm gives only a
18+
quadratic speedup, turning 256 bits into ~128 bits of strength, which
19+
is out of reach. The symmetric layer does not need to change.
20+
21+
The main threat is not "someday it will be broken" but
22+
harvest-now-decrypt-later. By its nature hbs2:
23+
24+
- replicates encrypted blocks widely and permanently
25+
(content-addressed storage, blocks spread across peers);
26+
- stores the GroupKey with the wrapped secret and the recipients'
27+
public keys right next to them.
28+
29+
This means a private repository that goes onto the network today can
30+
be archived by an adversary and decrypted on the day a CRQC arrives.
31+
The threat to confidentiality is in effect from the moment of first
32+
publication, not from the moment a quantum computer appears. Given the
33+
NIST timelines (deprecation of ECC recommended by 2030, disallowed by
34+
2035), this is worth tackling early.
35+
36+
## Goals
37+
38+
- Protect repository confidentiality against
39+
harvest-now-decrypt-later.
40+
- Do not break existing encrypted repositories and old keys.
41+
- Retain classical strength in case a classical break is found in
42+
the new PQC scheme (hence a hybrid, not a replacement).
43+
44+
## Non-goals
45+
46+
- Replacing symmetric encryption (it is quantum-sufficient).
47+
- An urgent signature migration. Forging a signature requires
48+
breaking a key while it is still in use; back-dating archival
49+
signatures is pointless. Signatures migrate in a separate phase.
50+
- Support for arbitrary PQC algorithms. We pin the NIST standards.
51+
52+
## Choice of primitives
53+
54+
- KEM (wrapping the group secret): X25519 + ML-KEM-768 (FIPS 203,
55+
formerly Kyber), hybrid. The shared secrets are combined in a KDF;
56+
confidentiality holds as long as at least one of the two is
57+
intact.
58+
- Signatures (phase 2): Ed25519 + ML-DSA (FIPS 204, Dilithium). For
59+
long-lived root identities, optionally SLH-DSA (FIPS 205,
60+
SPHINCS+) as a more conservative variant (hash-based only).
61+
- Symmetric: unchanged (XSalsa20-Poly1305, HKDF-SHA-512).
62+
63+
## Design
64+
65+
### New crypto scheme at the type level
66+
67+
The code already has scheme indirection (`'HBS2Basic` in
68+
`PubKey 'Encrypt`, `PubKey 'Sign`, etc.). Introduce a parallel scheme
69+
without touching the old one:
70+
71+
```
72+
data CryptoScheme = HBS2Basic | HBS2Hybrid
73+
74+
type instance PubKey 'Encrypt 'HBS2Hybrid = HybridEncPK
75+
type instance PrivKey 'Encrypt 'HBS2Hybrid = HybridEncSK
76+
77+
data HybridEncPK = HybridEncPK
78+
{ hePkX25519 :: Encrypt.PublicKey -- 32 bytes, as before
79+
, hePkMlKem :: MlKemPublicKey -- ~1184 bytes
80+
}
81+
```
82+
83+
In phase 1 signatures stay `HBS2Basic`/Ed25519. The hybrid touches
84+
encryption only.
85+
86+
### Hybrid wrapping of the group secret
87+
88+
Today the secret is wrapped like this:
89+
90+
```
91+
Recipients s = HashMap (PubKey 'Encrypt s) (EncryptedBox GroupSecret)
92+
```
93+
94+
Extend EncryptedBox with a double-wrapped variant. The idea: obtain a
95+
shared secret independently from two KEMs, combine them through a KDF,
96+
and encrypt the group secret itself with the resulting key. That way
97+
confidentiality holds as long as at least one KEM is intact.
98+
99+
```
100+
data HybridWrap = HybridWrap
101+
{ hwX25519Box :: ByteString -- sealed box over X25519, as before
102+
, hwMlKemCt :: ByteString -- ML-KEM ciphertext (~1088 bytes)
103+
, hwSecretBox :: ByteString -- secretbox(group-secret) under kek
104+
, hwNonce :: ByteString
105+
}
106+
```
107+
108+
Key-encryption-key derivation:
109+
110+
```
111+
ss_x = X25519(eph_sk, recipient_pk) -- classical DH
112+
ss_pq = ML-KEM.decap(ct, recipient_mlkem_sk) -- PQ encapsulation
113+
kek = HKDF-SHA-512( ss_x || ss_pq ) -- both secrets together
114+
gsec = secretbox_open(kek, nonce, hwSecretBox)
115+
```
116+
117+
On encryption: generate an ephemeral X25519 (as in the sealed box) and
118+
ML-KEM.encap to the recipient's public key, combine both shared
119+
secrets in the same HKDF, and encrypt the group secret with the
120+
resulting kek.
121+
122+
### New MTreeEncryption variant
123+
124+
`MTreeEncryption` is already versioned (`EncryptGroupNaClSymm1/2`).
125+
Add a variant for the hybrid so the reader knows how to decrypt the
126+
secret wrapper (the bulk block layer itself does not change):
127+
128+
```
129+
data MTreeEncryption
130+
= NullEncryption
131+
| CryptAccessKeyNaClAsymm (Hash HbSync)
132+
| EncryptGroupNaClSymm1 (Hash HbSync) ByteString
133+
| EncryptGroupNaClSymm2 EncryptGroupNaClSymmOpts (Hash HbSync) ByteString
134+
| EncryptGroupHybridSymm (Hash HbSync) ByteString -- new
135+
```
136+
137+
Bulk block encryption (XSalsa20-Poly1305 + index nonces) stays the
138+
same: only the way the group secret is delivered to the recipient
139+
changes.
140+
141+
### GroupKey versioning
142+
143+
`GroupKeySymmFancy` already carries `groupKeyIdScheme` and
144+
`groupKeyTimestamp`. Use `groupKeyIdScheme` to mark the hybrid
145+
wrapper, and bump the serialisation format version while keeping
146+
backward-compatible reading of old keys.
147+
148+
## Compatibility and migration
149+
150+
- Old repositories (`EncryptGroupNaClSymm1/2`) are read as before,
151+
indefinitely.
152+
- New private repositories are created as hybrid when every
153+
recipient has an ML-KEM key; otherwise fall back to the old scheme
154+
with a warning.
155+
- A recipient with a hybrid key can be added to both an old and a
156+
new GroupKey (the X25519 part is compatible).
157+
- Re-encrypting existing repositories to the hybrid does not undo
158+
harvest-now-decrypt-later for blocks that already leaked, but it
159+
protects everything published after the migration. This must be
160+
stated plainly in the docs.
161+
162+
## Library and dependencies
163+
164+
saltine/libsodium do not contain PQC. Options:
165+
166+
- An FFI binding to liboqs (which has ML-KEM, ML-DSA, SLH-DSA).
167+
- botan (has ML-KEM/ML-DSA), but pulls in a large dependency.
168+
169+
Preferred: a thin in-house FFI binding to liboqs covering only the
170+
needed algorithms, in the spirit of the devendoring policy (fork
171+
under NCrashed, publish to Hackage where possible). See the
172+
devendoring plan.
173+
174+
## Sizes (rough)
175+
176+
| Object | Classical (X25519) | PQC |
177+
|---------------------|--------------------|----------------------|
178+
| Public enc key | 32 bytes | ML-KEM-768 ~1184 |
179+
| KEM ciphertext | 32 (eph pk) | ML-KEM-768 ~1088 |
180+
| Signature | Ed25519 64 | ML-DSA ~2420 |
181+
| Signature (SLH-DSA) | - | tens of KB |
182+
183+
The growth per recipient secret wrapper is tolerable, but the
184+
recipient HashMap in a GroupKey grows linearly with the number of
185+
readers. On refs (frequent signatures) the signature growth is more
186+
noticeable, which is why the signature phase is separate.
187+
188+
## Rollout plan
189+
190+
- Phase 0: FFI binding to liboqs (ML-KEM), round-trip tests.
191+
- Phase 1: the `HBS2Hybrid` scheme for encryption,
192+
`EncryptGroupHybridSymm`, hybrid wrapping of the group secret,
193+
generation of hybrid keys in the keyring, reading of old formats.
194+
- Phase 2 (later): hybrid signatures (Ed25519 + ML-DSA), optionally
195+
SLH-DSA for root identities.
196+
197+
## Open questions
198+
199+
- ML-KEM level: 768 (default) or 1024 for long-lived repositories?
200+
- Where to store the ML-KEM key relative to the existing keyring: a
201+
new KeyringEntry variant or an extension of the current one?
202+
- Is a separate group-key rotation mechanism needed after migration,
203+
to force re-encryption to the hybrid?
204+
- Should domain separation be baked into the HKDF from the start (a
205+
scheme label in `info`) to keep classical and hybrid derivations
206+
apart?

0 commit comments

Comments
 (0)