kyber_rs/encrypt/ecies/
ecies_impl.rs

1/// module `ecies` implements the Elliptic Curve Integrated Encryption Scheme (ECIES).
2use crate::{
3    dh::{DhError, AEAD, NONCE_SIZE},
4    encoding::{BinaryMarshaler, BinaryUnmarshaler, Marshaling, MarshallingError},
5    util::random::RandStream,
6    Group, Point, Scalar,
7};
8use thiserror::Error;
9
10/// [`encrypt()`] first computes a shared DH key using the given public key, then
11/// HKDF-derives a symmetric key (and nonce) from that, and finally uses these
12/// values to encrypt the given message via AES-GCM. If the hash input parameter
13/// is `None` then SHA256 is used as a default. [`encrypt()`] returns a byte slice
14/// containing the ephemeral elliptic curve point of the DH key exchange and the
15/// `ciphertext` or an [`Error`](EciesError).
16pub fn encrypt<GROUP: Group>(
17    group: GROUP,
18    public: GROUP::POINT,
19    message: &[u8],
20) -> Result<Vec<u8>, EciesError> {
21    // Generate an ephemeral elliptic curve scalar and point
22    let r = group.scalar().pick(&mut RandStream::default());
23    let r_p = group.point().mul(&r, None);
24
25    // Compute shared DH key
26    let dh = group.point().mul(&r, Some(&public));
27
28    // Derive symmetric key and nonce via HKDF (NOTE: Since we use a new
29    // ephemeral key for every ECIES encryption and thus have a fresh
30    // HKDF-derived key for AES-GCM, the nonce for AES-GCM can be an arbitrary
31    // (even static) value. We derive it here simply via HKDF as well.)
32    let len = 32 + NONCE_SIZE;
33    let buf = derive_key::<GROUP>(&dh, len)?;
34
35    let mut nonce = [0u8; NONCE_SIZE];
36    nonce.copy_from_slice(&buf[32..len]);
37
38    let gcm = AEAD::<GROUP>::new(r_p.clone(), &buf)?;
39
40    // Encrypt message using AES-GCM
41    let c = gcm.seal(None, &nonce, message, None)?;
42
43    // Serialize ephemeral elliptic curve point and ciphertext
44    let mut ctx = Vec::new();
45    r_p.marshal_to(&mut ctx)?;
46    for v in c {
47        ctx.push(v);
48    }
49    Ok(ctx)
50}
51
52/// [`decrypt()`] first computes a shared DH key using the received ephemeral elliptic
53/// curve point (stored in the first part of ctx), then HKDF-derives a symmetric
54/// key (and nonce) from that, and finally uses these values to decrypt the
55/// given ciphertext (stored in the second part of ctx) via AES-GCM. If the hash
56/// input parameter is nil then SHA256 is used as a default. Decrypt returns the
57/// `plaintext message` or an [`Error`](EciesError).
58pub fn decrypt<GROUP: Group>(
59    group: GROUP,
60    private: <GROUP::POINT as Point>::SCALAR,
61    ctx: &[u8],
62) -> Result<Vec<u8>, EciesError> {
63    // Reconstruct the ephemeral elliptic curve point
64    let mut r_p = group.point();
65    let l = group.point_len();
66    r_p.unmarshal_binary(&ctx[..l])?;
67
68    // Compute shared DH key and derive the symmetric key and nonce via HKDF
69    let dh = group.point().mul(&private, Some(&r_p));
70    let len = 32 + NONCE_SIZE;
71    let buf = derive_key::<GROUP>(&dh, len)?;
72
73    let mut nonce = [0u8; NONCE_SIZE];
74    nonce.copy_from_slice(&buf[32..len]);
75
76    // Decrypt message using AES-GCM
77    let gcm = AEAD::<GROUP>::new(r_p.clone(), &buf)?;
78    Ok(gcm.open(None, &nonce, &ctx[l..], None)?)
79}
80
81fn derive_key<GROUP: Group>(dh: &GROUP::POINT, len: usize) -> Result<Vec<u8>, EciesError> {
82    let dhb = dh.marshal_binary()?;
83    let key = GROUP::hkdf(&dhb, &Vec::new(), Some(len))?;
84
85    if key.len() < len {
86        return Err(EciesError::KeyTooShort);
87    }
88    Ok(key)
89}
90
91#[derive(Debug, Error)]
92pub enum EciesError {
93    #[error("marshalling error")]
94    MarshalingError(#[from] MarshallingError),
95    #[error("dh error")]
96    DhError(#[from] DhError),
97    #[error("hkdf-derived key too short")]
98    KeyTooShort,
99}