Crate pq_envelope

Crate pq_envelope 

Source
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:

  1. Create an AES-256-GCM data encryption key (DEK).
  2. Encrypt the payload with the DEK.
  3. 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 capsule to 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 capsule is called the recipient.
  1. The envelope consists of the encrypted payload and a list of recipients.

The envelope can be decrypted using a receiver’s decapsulation key.

  1. The decapsulation key opens the capsule to extract the session key.
  2. The session key goes through a KDF to produce the KEK.
  3. Using the KEK, the DEK is unwrapped.
  4. 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

SchemeEncapsulation KeyDecapsulation KeyRecipient
Nist1,1842,4001,088 Cap + 40 KW = 1,128
Small261,1206,49296 Cap + 40 KW = 136
Secure21,52043,08821,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.
PublicKey
A public encapsulation key
Recipient
The recipient structure that holds the necessary metadata for a recipient to decrypt the data.
SecretKey
A private decapsulation key

Enums§

Error
The error type for this crate.
Scheme
The type of Schemes supported by this crate.

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 Result type for this crate.