Expand description
A Hybrid PQ Encryption envelope scheme
An envelope is an encrypted payload that only the specified list of receivers can decrypt. Each receiver must possess their own decryption key
The scheme works as follows:
- Create an AES-256-GCM data encryption key (DEK).
- Encrypt the payload with the DEK.
- Use a receiver’s public key to encrypt the DEK.
- This public key is a post-quantum key encapsulation mechanism (KEM) encapsulation key.
- The encapsulation key creates a
capsuleto be stored and a session key. - The session key goes through a key derivation function (KDF) to produce a key encryption key (KEK).
- The KDF is either SHA3’s SHAKE128 or SHAKE256 depending on the security level.
- The KEK combined with AES-256-KW creates a wrapped DEK which is also stored.
- The metadata consisting of the wrapped DEK and the
capsuleis called therecipient.
- The envelope consists of the encrypted payload and a list of recipients.
The envelope can be decrypted using a receiver’s decapsulation key.
- The decapsulation key opens the
capsuleto extract the session key. - The session key goes through a KDF to produce the KEK.
- Using the KEK, the DEK is unwrapped.
- The payload can be decrypted using the DEK.
Envelope size and security can be determined using an associated Scheme
depending on the use case. The most common Scheme to use where size isn’t
an issue for the envelope or keys and performance is the best
is Scheme::Nist which uses ML-KEM-768. This is also the default.
However, for settings where size is important use Scheme::Small which priorities
envelope size but requires the largest key sizes. This scheme uses ClassicMcEliece348864.
For high security settings Scheme::Secure which prioritizes 256-bit PQ security
use Scheme::Secure which uses FrodoKEM1344.
The overhead for each recipient is in bytes
| Scheme | Encapsulation Key | Decapsulation Key | Recipient |
|---|---|---|---|
| Nist | 1,184 | 2,400 | 1,088 Cap + 40 KW = 1,128 |
| Small | 261,120 | 6,492 | 96 Cap + 40 KW = 136 |
| Secure | 21,520 | 43,088 | 21,632 Cap + 40 KW = 22,303 |
§Why ClassicMcEliece, FrodoKEM and ML-KEM
See Daniel Bernstein’s Blog on this which says
“ISO Standardization. ISO has strict secrecy rules regarding its deliberations, but it does make some procedural information public. In particular, if you poke around the web page for ISO project 86890 then you’ll see that the project is currently considering a Draft International Standard (DIS) that includes Classic McEliece, FrodoKEM, and ML-KEM, as an amendment to an existing standard, ISO/IEC 18033-2.
This doesn’t guarantee anything…Still, the fact that ISO is considering a draft sounds good.“
The TL;DR is thus: If static-keys are possible i.e. distribute the keys once and their
life-cycle is long, use Scheme::Small. If the keys are short-lived and/or ephemeral
use Scheme::Nist or Scheme::Secure.
§Usage
To create a receiver, select an appropriate scheme and create their keys:
use pq_envelope::{Scheme, Envelope};
let scheme = Scheme::Nist;
let (r1_pk, r1_sk) = scheme.key_pair().unwrap();
let (r2_pk, r2_sk) = scheme.key_pair().unwrap();
let (r3_pk, r3_sk) = scheme.key_pair().unwrap();
let plaintext = b"Hello World!".to_vec();
let envelope = Envelope::new(
&[r1_pk, r2_pk, r3_pk],
&plaintext,
None,
).unwrap();
// Uses trial decapsulation to find the intended recipient
assert_eq!(plaintext, envelope.decrypt_by_recipient_secret_key(&r1_sk).unwrap());
assert_eq!(plaintext, envelope.decrypt_by_recipient_secret_key(&r2_sk).unwrap());
assert_eq!(plaintext, envelope.decrypt_by_recipient_secret_key(&r3_sk).unwrap());
// Or if the recipient is already known by its index
assert_eq!(plaintext, envelope.decrypt_by_recipient_index(0, &r1_sk).unwrap());
assert_eq!(plaintext, envelope.decrypt_by_recipient_index(1, &r2_sk).unwrap());
assert_eq!(plaintext, envelope.decrypt_by_recipient_index(2, &r3_sk).unwrap());
let (_r4_pk, r4_sk) = scheme.key_pair().unwrap();
// r4 doesn't have recipient data so this should fail
assert!(envelope.decrypt_by_recipient_secret_key(&r4_sk).is_err());All methods support serialization for storage and retrieval.
For this purpose, everything implements serde::Serialize, serde::Deserialize.
And for faster serialization, the rkyv crate has also been implemented.
Structs§
- Envelope
- The envelope structure that holds the encrypted data along with the necessary metadata.
- Public
Key - A public encapsulation key
- Recipient
- The recipient structure that holds the necessary metadata for a recipient to decrypt the data.
- Secret
Key - A private decapsulation key
Enums§
Constants§
- SCHEME_
NIST_ CAPSULE_ LENGTH - The length of the capsule for
Scheme::Nist - SCHEME_
NIST_ PUBLIC_ KEY_ LENGTH - The length of the encapsulation key for
Scheme::Nist - SCHEME_
NIST_ SECRET_ KEY_ LENGTH - The length of the decapsulation key for
Scheme::Nist - SCHEME_
NIST_ SHARED_ SECRET_ LENGTH - The length of the shared secret for
Scheme::Nist - SCHEME_
SECURE_ CAPSULE_ LENGTH - The length of the capsule for
Scheme::Secure - SCHEME_
SECURE_ PUBLIC_ KEY_ LENGTH - The length of the encapsulation key for
Scheme::Secure - SCHEME_
SECURE_ SECRET_ KEY_ LENGTH - The length of the decapsulation key for
Scheme::Secure - SCHEME_
SECURE_ SHARED_ SECRET_ LENGTH - The length of the shared secret for
Scheme::Secure - SCHEME_
SMALL_ CAPSULE_ LENGTH - The length of the capsule for
Scheme::Small - SCHEME_
SMALL_ PUBLIC_ KEY_ LENGTH - The length of the encapsulation key for
Scheme::Small - SCHEME_
SMALL_ SECRET_ KEY_ LENGTH - The length of the decapsulation key for
Scheme::Small - SCHEME_
SMALL_ SHARED_ SECRET_ LENGTH - The length of the shared secret for
Scheme::Small
Type Aliases§
- Result
- A specialized
Resulttype for this crate.