111 lines
5.2 KiB
Markdown
111 lines
5.2 KiB
Markdown
# Post-Quantum Hybrid ML-KEM-1024 + AES-256-GCM File Encryption
|
|
|
|
Streaming hybrid file encryption: ML-KEM-1024 handles post-quantum key exchange, and AES-256-GCM handles bulk file encryption with built-in authenticity. The file signature is `KYB`, and the file extension is ".kyb".
|
|
|
|
Simplified flow (ML-KEM-1024 + AES-256 only, HKDF omitted for clarity):
|
|
|
|
<a href="https://notes.juyung.com/gallery/6nIr0qID0vri0DR4Jy7HCbS6/hJv-1N1hIqFxzXIv85KqR9dq" target="_blank"><img src='https://notes.juyung.com/uploads/small2x/f9/20/0805d761b05dbbf2046a68268b58.png' style="height: 720px;" /></a>
|
|
|
|
## Features
|
|
|
|
- Post-quantum secure key exchange via liboqs ML-KEM-1024 KEM (~256-bit PQ security)
|
|
- AES-256-GCM authenticated encryption with a streaming architecture that handles files larger than RAM (64 MB chunks)
|
|
- AES key derived from the ML-KEM shared secret via HKDF-SHA256 (never stored in the file)
|
|
- Unforgeable 128-bit GCM tag over the header + ciphertext to detect tampering or corruption
|
|
- Self-describing CBOR header (RFC 8949; file signature `KYB`, version, algorithm and length fields), so each file identifies its own format and unsupported future versions are rejected cleanly
|
|
|
|
## Requirements
|
|
|
|
- `oqs` ([liboqs-python](https://github.com/open-quantum-safe/liboqs-python))
|
|
- `cryptography` ([pyca/cryptography](https://github.com/pyca/cryptography))
|
|
- `cbor2` ([cbor2](https://github.com/agronholm/cbor2))
|
|
|
|
```bash
|
|
python3 -m venv venv && . venv/bin/activate
|
|
pip install -r requirements.txt
|
|
```
|
|
|
|
## Usage
|
|
|
|
1. Generate an ML-KEM-1024 key pair
|
|
|
|
```bash
|
|
python3 gen_keys.py
|
|
```
|
|
|
|
Produces:
|
|
|
|
- `mlkem1024.pub` - public key (1568 bytes)
|
|
- `mlkem1024.sec` - secret key (3168 bytes)
|
|
|
|
2. Encrypt a file
|
|
|
|
```bash
|
|
python3 encrypt.py archive.7z
|
|
```
|
|
|
|
Output: `archive.7z.kyb`
|
|
|
|
The `.kyb` file is self-contained: a compact CBOR header at the start describes the exact format, algorithms, and lengths. See [File formats](#file-formats) for the layout.
|
|
|
|
3. Decrypt a file
|
|
|
|
```bash
|
|
python3 decrypt.py archive.7z.kyb
|
|
```
|
|
|
|
Output: `archive.7z`
|
|
|
|
The script automatically verifies the GCM tag. If the file has been tampered with, you'll see:
|
|
|
|
```
|
|
Error: Integrity check failed. File corrupted or tampered
|
|
```
|
|
|
|
and the temp file is discarded. No plaintext ever appears at the destination path until the tag has been verified.
|
|
|
|
## File formats
|
|
|
|
**Key files (raw binary)**
|
|
|
|
File|Content|Size (bytes)
|
|
-|-|-
|
|
`mlkem1024.pub`|ML-KEM-1024 public key|1568
|
|
`mlkem1024.sec`|ML-KEM-1024 secret key|3168
|
|
|
|
**CBOR header**
|
|
|
|
Layout: 3-byte signature `KYB` + 1-byte version, then a CBOR map (RFC 8949) of field name → value pairs. CBOR is self-delimiting, so the reader knows exactly where the map ends; unknown keys are simply ignored, keeping the format forward-compatible. The `KDF` and `INFO` fields record the key-derivation parameters, making the file fully self-describing: with the secret key alone, a from-scratch decryptor can derive the AES key from the header.
|
|
|
|
Key | Value (v1)
|
|
-|-
|
|
`KEM`|`MLKEM1024`
|
|
`CIPHER`|`AES256GCM`
|
|
`MLKEMCT`|`1568`
|
|
`IV`|`12`
|
|
`TAG`|`16`
|
|
`KDF`|`HKDF-SHA256`
|
|
`INFO`|`mlkem-aes-key`
|
|
|
|
**Encrypted file structure**
|
|
|
|
`archive.7z.kyb`:
|
|
|
|
Section | Size | Description
|
|
-|-|-
|
|
CBOR header|91 bytes (v1)|Signature `KYB` (3 B) + version (1 B) + CBOR map (87 B)
|
|
ML-KEM ciphertext|1568 bytes|Encapsulated key (ciphertext from ML-KEM-1024); length read from the header
|
|
IV (nonce)|12 bytes|GCM nonce, also the HKDF salt; length read from the header
|
|
File ciphertext|same as plaintext|AES-256-GCM encrypted chunks, streamed in 64 MB blocks
|
|
GCM auth tag|16 bytes|Tag over the header + ML-KEM CT + IV + all ciphertext; length read from the header
|
|
|
|
## Security notes
|
|
|
|
- A fresh 32-byte **ML-KEM shared secret** is generated **per file** by `encap_secret()` and recovered only with `mlkem1024.sec` via `decap_secret()`. It is the root key material: neither the AES key nor the ML-KEM private key.
|
|
- The AES key is **derived from that ML-KEM shared secret** with HKDF-SHA256 (domain-separated `info` label, both recorded in the header), so no key material is ever stored in the file.
|
|
- Because the AES key is only derivable by the legitimate recipient (ML-KEM decapsulation), the GCM tag is **unforgeable**: an attacker without the secret key cannot modify the file undetectably.
|
|
- The GCM tag is computed over the **CBOR header + ML-KEM CT + IV + AES ciphertext** (via additional authenticated data), so any tampering with the encrypted data, the nonce, or the format header is detected before decryption.
|
|
- Decrypted plaintext is written to a temporary file and atomically renamed into place **only after** the GCM tag verifies.
|
|
- The header carries a format **version**; decrypt checks it up front, so a file written by a newer (or unknown) format is refused with a clear error instead of being mis-parsed.
|
|
- ML-KEM-1024 is believed to offer security equivalent to ~256-bit classical keys against quantum attackers.
|
|
- The secret key file (`mlkem1024.sec`) is the single point of failure. Losing it means losing every encrypted file. Treat it like a private SSH key: keep it offline, back it up, and restrict access (e.g. `chmod 600`). For stronger protection, consider encrypting the key file with a passphrase or splitting it into pieces (XOR-splitting or Shamir secret sharing) across separate locations.
|