orion 0.18.1

Usable, easy and safe pure-Rust crypto
Documentation
// MIT License

// Copyright (c) 2026 The orion Developers

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

//! Hybrid Public Key Encryption.
//!
//! # Use case:
//! `orion::hpke` can be used to encrypt one or more messages for a recipient public key.
//!
//! # About:
//! - Uses the PQ/T HPKE suite `X-Wing/(ML-KEM768, X25519), SHAKE256, ChaCha20Poly1305`.
//! - HPKE provides no forward secrecy for the recipient long-term key. If the recipients long-term
//!   key is compromised, past ciphertext can be decrypted.
//! - Messages must be decrypted in the same order they were encrypted.
//!   HPKE provides no further protections against replay.
//! - Length of the messages encrypted are not hidden.
//! - Base-mode provides no sender authentication. PSK-mode should be used for this.
//!
//! Please refer to the [RFC9180] for more security information.
//!
//! This suite is defined in:
//! - KEM suite: [draft-irtf-cfrg-concrete-hybrid-kems-04](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-concrete-hybrid-kems-04)
//! - HPKE suite: [draft-ietf-hpke-pq-05](https://datatracker.ietf.org/doc/html/draft-ietf-hpke-pq-05)
//!
//! # Parameters:
//! - `pubkey_r`: Public key of the recipient.
//! - `info`: Optional additional information for HPKE state. Must be identical for sender and recipient.
//! - `enc`: Encapsulated key, generated by the sender.
//! - `secret_key_r`: Secret key of the recipient.
//! - `psk`: Pre-shared key known to both sender and recipient.
//! - `psk_id`: The identifier for `psk`.
//!
//! # Errors:
//! An error will be returned if:
//! - `info` is longer than [`u16::MAX`] bytes
//! - The internal counter reaches [`u64::MAX`] and a call to `seal()`/`open()` is made
//! - Calling [`HpkeBase::seal()`]/[`HpkePsk::seal()`] when the role is recipient
//! - Calling [`HpkeBase::open()`]/[`HpkePsk::open()`] when the role is sender
//! - Calling [`HpkeBase::open()`]/[`HpkePsk::open()`] on a set of messages that does not match the order of how they were `seal()`'ed (re-ordering)
//! - `psk` or `psk_id` are empty
//! - `psk` is less than `32` bytes or more than [`u16::MAX`] bytes
//! - `psk_id` is more than [`u16::MAX`] bytes
//! - If a shared secret is all-zero.
//! - `getrandom` errors during [`HpkeBase::new_sender()`] or [`HpkePsk::new_sender()`].
//!
//! # Security:
//! - If forward secrecy for both the sender and recipient are required, what is usually considered the long-term
//!   key in HPKE, those keys should be made ephemeral (generated for each message). This turns it into an interactive protocol
//!   and is not specified in the standard. Frequent key-rotations of "long-term" keys will shrink the window of
//!   compromised ciphertexts, for a single key, if leaked.
//!
//! # Example:
//! ```rust
//! use orion::hpke::*;
//!
//! let recipient_kp = KeyPair::generate()?;
//! let psk = b"any preshared secret key that is 32 bytes minimum";
//! let psk_id = b"identifier for psk";
//!
//!
//! let (mut hpke_sender, enc) = HpkeBase::new_sender(recipient_kp.public(), b"info parameter")?;
//! let ct0 = hpke_sender.seal(&[0u8; 16], b"aad parameter 0")?;
//! let ct1 = hpke_sender.seal(&[1u8; 16], b"aad parameter 1")?;
//! let ct2 = hpke_sender.seal(&[2u8; 16], b"aad parameter 2")?;
//!
//! let mut hpke_recipient = HpkeBase::new_recipient(&enc, recipient_kp.private(), b"info parameter")?;
//! let pt0 = hpke_recipient.open(&ct0, b"aad parameter 0")?;
//! let pt1 = hpke_recipient.open(&ct1, b"aad parameter 1")?;
//! let pt2 = hpke_recipient.open(&ct2, b"aad parameter 2")?;
//!
//! assert_eq!(&pt0, &[0u8; 16]);
//! assert_eq!(&pt1, &[1u8; 16]);
//! assert_eq!(&pt2, &[2u8; 16]);
//!
//! let (mut hpke_sender, enc) = HpkePsk::new_sender(recipient_kp.public(), b"info parameter", psk, psk_id)?;
//! let ct0 = hpke_sender.seal(&[0u8; 16], b"aad parameter 0")?;
//! let ct1 = hpke_sender.seal(&[1u8; 16], b"aad parameter 1")?;
//! let ct2 = hpke_sender.seal(&[2u8; 16], b"aad parameter 2")?;
//!
//! let mut hpke_recipient = HpkePsk::new_recipient(&enc, recipient_kp.private(), b"info parameter", psk, psk_id)?;
//! let pt0 = hpke_recipient.open(&ct0, b"aad parameter 0")?;
//! let pt1 = hpke_recipient.open(&ct1, b"aad parameter 1")?;
//! let pt2 = hpke_recipient.open(&ct2, b"aad parameter 2")?;
//!
//! assert_eq!(&pt0, &[0u8; 16]);
//! assert_eq!(&pt1, &[1u8; 16]);
//! assert_eq!(&pt2, &[2u8; 16]);
//! # Ok::<(), orion::errors::UnknownCryptoError>(())
//! ```
//! [RFC9180]: <https://www.rfc-editor.org/rfc/rfc9180.html#name-security-considerations>
//! [`xwing::DecapsulationKey`]: crate::hazardous::kem::xwing::DecapsulationKey

#![cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]

pub use crate::KP;
use crate::errors::UnknownCryptoError;
use crate::hazardous::hpke::MLKEM768_X25519_SHAKE256_CHACHA20;
use crate::hazardous::hpke::ModeBase;
use crate::hazardous::hpke::ModePsk;
pub use crate::hazardous::kem::xwing::{Ciphertext, DecapsulationKey, EncapsulationKey, KeyPair};

#[derive(Debug, PartialEq)]
/// PQ/T HPKE with X-Wing and SHAKE256 in base-mode.
pub struct HpkeBase(ModeBase<MLKEM768_X25519_SHAKE256_CHACHA20>);

impl HpkeBase {
    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
    /// HPKE Base mode sender.
    pub fn new_sender(
        pk_r: &EncapsulationKey,
        info: &[u8],
    ) -> Result<(Self, Ciphertext), UnknownCryptoError> {
        let (ctx, enc) = ModeBase::<MLKEM768_X25519_SHAKE256_CHACHA20>::new_sender(pk_r, info)?;
        Ok((Self(ctx), enc))
    }

    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
    /// HPKE Base mode recipient.
    pub fn new_recipient(
        enc: &Ciphertext,
        secret_key_r: &DecapsulationKey,
        info: &[u8],
    ) -> Result<Self, UnknownCryptoError> {
        Ok(Self(
            ModeBase::<MLKEM768_X25519_SHAKE256_CHACHA20>::new_recipient(enc, secret_key_r, info)?,
        ))
    }

    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
    /// Context-aware sealing operations.
    pub fn seal(&mut self, plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, UnknownCryptoError> {
        let mut out = vec![
            0u8;
            plaintext
                .len()
                .checked_add(MLKEM768_X25519_SHAKE256_CHACHA20::NT)
                .ok_or(UnknownCryptoError)?
        ];
        self.0.seal(plaintext, aad, &mut out)?;

        Ok(out)
    }

    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
    /// Context-aware opening operations.
    pub fn open(&mut self, ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, UnknownCryptoError> {
        let mut out = vec![
            0u8;
            ciphertext
                .len()
                .checked_sub(MLKEM768_X25519_SHAKE256_CHACHA20::NT)
                .ok_or(UnknownCryptoError)?
        ];
        self.0.open(ciphertext, aad, &mut out)?;

        Ok(out)
    }
}

#[derive(Debug, PartialEq)]
/// PQ/T HPKE with X-Wing and SHAKE256 in PSK-mode.
pub struct HpkePsk(ModePsk<MLKEM768_X25519_SHAKE256_CHACHA20>);

impl HpkePsk {
    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
    /// HPKE PSK mode sender.
    pub fn new_sender(
        pk_r: &EncapsulationKey,
        info: &[u8],
        psk: &[u8],
        psk_id: &[u8],
    ) -> Result<(Self, Ciphertext), UnknownCryptoError> {
        let (ctx, enc) =
            ModePsk::<MLKEM768_X25519_SHAKE256_CHACHA20>::new_sender(pk_r, info, psk, psk_id)?;
        Ok((Self(ctx), enc))
    }

    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
    /// HPKE PSK mode recipient.
    pub fn new_recipient(
        enc: &Ciphertext,
        secret_key_r: &DecapsulationKey,
        info: &[u8],
        psk: &[u8],
        psk_id: &[u8],
    ) -> Result<Self, UnknownCryptoError> {
        Ok(Self(
            ModePsk::<MLKEM768_X25519_SHAKE256_CHACHA20>::new_recipient(
                enc,
                secret_key_r,
                info,
                psk,
                psk_id,
            )?,
        ))
    }

    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
    /// Context-aware sealing operations.
    pub fn seal(&mut self, plaintext: &[u8], aad: &[u8]) -> Result<Vec<u8>, UnknownCryptoError> {
        let mut out = vec![
            0u8;
            plaintext
                .len()
                .checked_add(MLKEM768_X25519_SHAKE256_CHACHA20::NT)
                .ok_or(UnknownCryptoError)?
        ];
        self.0.seal(plaintext, aad, &mut out)?;

        Ok(out)
    }

    #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
    /// Context-aware opening operations.
    pub fn open(&mut self, ciphertext: &[u8], aad: &[u8]) -> Result<Vec<u8>, UnknownCryptoError> {
        let mut out = vec![
            0u8;
            ciphertext
                .len()
                .checked_sub(MLKEM768_X25519_SHAKE256_CHACHA20::NT)
                .ok_or(UnknownCryptoError)?
        ];
        self.0.open(ciphertext, aad, &mut out)?;

        Ok(out)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_basic_roundtrip() {
        let recipient_kp = KeyPair::generate().unwrap();
        let psk = b"any preshared secret key that is 32 bytes minimum";
        let psk_id = b"identifier for psk";

        let (mut hpke_sender, enc) =
            HpkeBase::new_sender(recipient_kp.public(), b"info parameter").unwrap();
        let ct0 = hpke_sender.seal(&[0u8; 16], b"aad parameter 0").unwrap();
        let ct1 = hpke_sender.seal(&[1u8; 16], b"aad parameter 1").unwrap();
        let ct2 = hpke_sender.seal(&[2u8; 16], b"aad parameter 2").unwrap();

        let mut hpke_recipient =
            HpkeBase::new_recipient(&enc, recipient_kp.private(), b"info parameter").unwrap();
        let pt0 = hpke_recipient.open(&ct0, b"aad parameter 0").unwrap();
        let pt1 = hpke_recipient.open(&ct1, b"aad parameter 1").unwrap();
        let pt2 = hpke_recipient.open(&ct2, b"aad parameter 2").unwrap();
        assert_eq!(&pt0, &[0u8; 16]);
        assert_eq!(&pt1, &[1u8; 16]);
        assert_eq!(&pt2, &[2u8; 16]);

        let (mut hpke_sender, enc) =
            HpkePsk::new_sender(recipient_kp.public(), b"info parameter", psk, psk_id).unwrap();
        let ct0 = hpke_sender.seal(&[0u8; 16], b"aad parameter 0").unwrap();
        let ct1 = hpke_sender.seal(&[1u8; 16], b"aad parameter 1").unwrap();
        let ct2 = hpke_sender.seal(&[2u8; 16], b"aad parameter 2").unwrap();
        let mut hpke_recipient =
            HpkePsk::new_recipient(&enc, recipient_kp.private(), b"info parameter", psk, psk_id)
                .unwrap();
        let pt0 = hpke_recipient.open(&ct0, b"aad parameter 0").unwrap();
        let pt1 = hpke_recipient.open(&ct1, b"aad parameter 1").unwrap();
        let pt2 = hpke_recipient.open(&ct2, b"aad parameter 2").unwrap();
        assert_eq!(&pt0, &[0u8; 16]);
        assert_eq!(&pt1, &[1u8; 16]);
        assert_eq!(&pt2, &[2u8; 16]);
    }
}