pq_envelope/lib.rs
1//! A Hybrid PQ Encryption envelope scheme
2//!
3//! An envelope is an encrypted payload that only the specified
4//! list of receivers can decrypt. Each receiver must possess their
5//! own decryption key
6//!
7//! The scheme works as follows:
8//!
9//! 1. Create an AES-256-GCM data encryption key (DEK).
10//! 2. Encrypt the payload with the DEK.
11//! 3. Use a receiver's public key to encrypt the DEK.
12//! - This public key is a post-quantum key encapsulation mechanism (KEM) encapsulation key.
13//! - The encapsulation key creates a `capsule` to be stored and a session key.
14//! - The session key goes through a key derivation function (KDF) to produce
15//! a key encryption key (KEK).
16//! - The KDF is either SHA3's SHAKE128 or SHAKE256 depending on the security level.
17//! - The KEK combined with AES-256-KW creates a wrapped DEK which is also stored.
18//! - The metadata consisting of the wrapped DEK and the `capsule` is called the `recipient`.
19//! 4. The envelope consists of the encrypted payload and a list of recipients.
20//!
21//! The envelope can be decrypted using a receiver's decapsulation key.
22//!
23//! 1. The decapsulation key opens the `capsule` to extract the session key.
24//! 2. The session key goes through a KDF to produce the KEK.
25//! 3. Using the KEK, the DEK is unwrapped.
26//! 4. The payload can be decrypted using the DEK.
27//!
28//! Envelope size and security can be determined using an associated [`Scheme`]
29//! depending on the use case. The most common [`Scheme`] to use where size isn't
30//! an issue for the envelope or keys and performance is the best
31//! is [`Scheme::Nist`] which uses ML-KEM-768. This is also the default.
32//!
33//! However, for settings where size is important use [`Scheme::Small`] which priorities
34//! envelope size but requires the largest key sizes. This scheme uses ClassicMcEliece348864.
35//!
36//! For high security settings [`Scheme::Secure`] which prioritizes 256-bit PQ security
37//! use [`Scheme::Secure`] which uses FrodoKEM1344.
38//!
39//! The overhead for each recipient is in bytes
40//!
41//! | Scheme | Encapsulation Key | Decapsulation Key | Recipient |
42//! | ------ | ----------------- | ----------------- | --------- |
43//! | Nist | 1,184 | 2,400 | 1,088 Cap + 40 KW = 1,128 |
44//! | Small | 261,120 | 6,492 | 96 Cap + 40 KW = 136 |
45//! | Secure | 21,520 | 43,088 | 21,632 Cap + 40 KW = 22,303 |
46//!
47//! # Why ClassicMcEliece, FrodoKEM and ML-KEM
48//!
49//! See [Daniel Bernstein's Blog](https://blog.cr.yp.to/20250423-mceliece.html) on this which says
50//!
51//! "ISO Standardization. ISO has strict secrecy rules regarding its deliberations,
52//! but it does make some procedural information public.
53//! In particular, if you poke around the web page for [ISO project 86890](https://www.iso.org/standard/86890.html)
54//! then you'll see that the project is currently considering a
55//! Draft International Standard (DIS) that includes
56//! Classic McEliece, FrodoKEM, and ML-KEM,
57//! as an amendment to an existing standard, ISO/IEC 18033-2.
58//!
59//! This doesn't guarantee anything...Still, the fact that ISO is considering a draft sounds good."
60//!
61//! The TL;DR is thus: If static-keys are possible i.e. distribute the keys once and their
62//! life-cycle is long, use `Scheme::Small`. If the keys are short-lived and/or ephemeral
63//! use `Scheme::Nist` or `Scheme::Secure`.
64//!
65//! # Usage
66//! To create a receiver, select an appropriate scheme and create their keys:
67//! ```
68//! use pq_envelope::{Scheme, Envelope};
69//!
70//! let scheme = Scheme::Nist;
71//! let (r1_pk, r1_sk) = scheme.key_pair().unwrap();
72//! let (r2_pk, r2_sk) = scheme.key_pair().unwrap();
73//! let (r3_pk, r3_sk) = scheme.key_pair().unwrap();
74//! let plaintext = b"Hello World!".to_vec();
75//!
76//! let envelope = Envelope::new(
77//! &[r1_pk, r2_pk, r3_pk],
78//! &plaintext,
79//! None,
80//! ).unwrap();
81//!
82//! // Uses trial decapsulation to find the intended recipient
83//! assert_eq!(plaintext, envelope.decrypt_by_recipient_secret_key(&r1_sk).unwrap());
84//! assert_eq!(plaintext, envelope.decrypt_by_recipient_secret_key(&r2_sk).unwrap());
85//! assert_eq!(plaintext, envelope.decrypt_by_recipient_secret_key(&r3_sk).unwrap());
86//!
87//! // Or if the recipient is already known by its index
88//! assert_eq!(plaintext, envelope.decrypt_by_recipient_index(0, &r1_sk).unwrap());
89//! assert_eq!(plaintext, envelope.decrypt_by_recipient_index(1, &r2_sk).unwrap());
90//! assert_eq!(plaintext, envelope.decrypt_by_recipient_index(2, &r3_sk).unwrap());
91//!
92//! let (_r4_pk, r4_sk) = scheme.key_pair().unwrap();
93//!
94//! // r4 doesn't have recipient data so this should fail
95//! assert!(envelope.decrypt_by_recipient_secret_key(&r4_sk).is_err());
96//! ```
97//!
98//! All methods support serialization for storage and retrieval.
99//! For this purpose, everything implements [`serde::Serialize`], [`serde::Deserialize`].
100//! And for faster serialization, the `rkyv` crate has also been implemented.
101
102#![cfg_attr(docsrs, feature(doc_cfg))]
103#![warn(
104 missing_docs,
105 missing_debug_implementations,
106 missing_copy_implementations,
107 trivial_casts,
108 trivial_numeric_casts,
109 unused,
110 clippy::mod_module_files
111)]
112#![deny(clippy::unwrap_used)]
113
114mod envelope;
115mod error;
116mod keys;
117mod recipient;
118mod scheme;
119mod util;
120
121use util::*;
122
123/// The length of the capsule for `Scheme::Small`
124pub const SCHEME_SMALL_CAPSULE_LENGTH: usize =
125 oqs::ffi::kem::OQS_KEM_classic_mceliece_348864_length_ciphertext as usize;
126/// The length of the capsule for `Scheme::Secure`
127pub const SCHEME_SECURE_CAPSULE_LENGTH: usize =
128 oqs::ffi::kem::OQS_KEM_frodokem_1344_aes_length_ciphertext as usize;
129/// The length of the capsule for `Scheme::Nist`
130pub const SCHEME_NIST_CAPSULE_LENGTH: usize =
131 oqs::ffi::kem::OQS_KEM_ml_kem_768_length_ciphertext as usize;
132
133/// The length of the encapsulation key for `Scheme::Small`
134pub const SCHEME_SMALL_PUBLIC_KEY_LENGTH: usize =
135 oqs::ffi::kem::OQS_KEM_classic_mceliece_348864_length_public_key as usize;
136/// The length of the encapsulation key for `Scheme::Secure`
137pub const SCHEME_SECURE_PUBLIC_KEY_LENGTH: usize =
138 oqs::ffi::kem::OQS_KEM_frodokem_1344_aes_length_public_key as usize;
139/// The length of the encapsulation key for `Scheme::Nist`
140pub const SCHEME_NIST_PUBLIC_KEY_LENGTH: usize =
141 oqs::ffi::kem::OQS_KEM_ml_kem_768_length_public_key as usize;
142
143/// The length of the decapsulation key for `Scheme::Small`
144pub const SCHEME_SMALL_SECRET_KEY_LENGTH: usize =
145 oqs::ffi::kem::OQS_KEM_classic_mceliece_348864_length_secret_key as usize;
146/// The length of the decapsulation key for `Scheme::Secure`
147pub const SCHEME_SECURE_SECRET_KEY_LENGTH: usize =
148 oqs::ffi::kem::OQS_KEM_frodokem_1344_aes_length_secret_key as usize;
149/// The length of the decapsulation key for `Scheme::Nist`
150pub const SCHEME_NIST_SECRET_KEY_LENGTH: usize =
151 oqs::ffi::kem::OQS_KEM_ml_kem_768_length_secret_key as usize;
152
153/// The length of the shared secret for `Scheme::Small`
154pub const SCHEME_SMALL_SHARED_SECRET_LENGTH: usize =
155 oqs::ffi::kem::OQS_KEM_classic_mceliece_348864_length_shared_secret as usize;
156/// The length of the shared secret for `Scheme::Secure`
157pub const SCHEME_SECURE_SHARED_SECRET_LENGTH: usize =
158 oqs::ffi::kem::OQS_KEM_frodokem_1344_aes_length_shared_secret as usize;
159/// The length of the shared secret for `Scheme::Nist`
160pub const SCHEME_NIST_SHARED_SECRET_LENGTH: usize =
161 oqs::ffi::kem::OQS_KEM_ml_kem_768_length_shared_secret as usize;
162
163pub use envelope::Envelope;
164pub use error::{Error, Result};
165pub use keys::*;
166pub use recipient::Recipient;
167pub use scheme::Scheme;