Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ debug = true
[patch.crates-io]
ml-kem = { path = "./ml-kem" }
module-lattice = { path = "./module-lattice" }

elliptic-curve = { git = "https://github.com/RustCrypto/traits" }
2 changes: 1 addition & 1 deletion dhkem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ k256 = ["dep:k256", "ecdh"]
p256 = ["dep:p256", "ecdh"]
p384 = ["dep:p384", "ecdh"]
p521 = ["dep:p521", "ecdh"]
x25519 = ["dep:x25519", "x25519/reusable_secrets"]
x25519 = ["dep:x25519", "x25519/static_secrets"]
zeroize = ["dep:zeroize"]

[package.metadata.docs.rs]
Expand Down
132 changes: 86 additions & 46 deletions dhkem/src/ecdh_kem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::{DecapsulationKey, EncapsulationKey};
use core::marker::PhantomData;
use elliptic_curve::{
AffinePoint, CurveArithmetic, Error, FieldBytesSize, PublicKey,
AffinePoint, CurveArithmetic, Error, FieldBytes, FieldBytesSize, PublicKey, SecretKey,
ecdh::EphemeralSecret,
sec1::{
FromEncodedPoint, ModulusSize, ToEncodedPoint, UncompressedPoint, UncompressedPointSize,
Expand All @@ -15,16 +15,6 @@ use kem::{
};
use rand_core::{CryptoRng, TryCryptoRng};

/// Elliptic Curve Diffie-Hellman Decapsulation Key (i.e. secret decryption key)
///
/// Generic around an elliptic curve `C`.
pub type EcdhDecapsulationKey<C> = DecapsulationKey<EphemeralSecret<C>, PublicKey<C>>;

/// Elliptic Curve Diffie-Hellman Encapsulation Key (i.e. public encryption key)
///
/// Generic around an elliptic curve `C`.
pub type EcdhEncapsulationKey<C> = EncapsulationKey<PublicKey<C>>;

/// Generic Elliptic Curve Diffie-Hellman KEM adapter compatible with curves implemented using
/// traits from the `elliptic-curve` crate.
///
Expand All @@ -45,13 +35,94 @@ where
type SharedKeySize = FieldBytesSize<C>;
}

/// Elliptic Curve Diffie-Hellman Decapsulation Key (i.e. secret decryption key)
///
/// Generic around an elliptic curve `C`.
pub type EcdhDecapsulationKey<C> = DecapsulationKey<SecretKey<C>, PublicKey<C>>;

impl<C> KeySizeUser for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
{
type KeySize = FieldBytesSize<C>;
}

/// From [RFC9810 §7.1.2]: `SerializePrivateKey` and `DeserializePrivateKey`:
///
/// > DeserializePrivateKey() performs the Octet-String-to-Field-Element conversion
/// > according to [SECG].
///
/// [RFC9810 §7.1.2]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.2
/// [SECG]: https://www.secg.org/sec1-v2.pdf
impl<C> TryKeyInit for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
{
fn new(key: &FieldBytes<C>) -> Result<Self, InvalidKey> {
SecretKey::from_bytes(key)
.map(Into::into)
.map_err(|_| InvalidKey)
}
}

/// From [RFC9810 §7.1.2]: `SerializePrivateKey` and `DeserializePrivateKey`:
///
/// > the SerializePrivateKey() function of the KEM performs the Field-Element-to-Octet-String
/// > conversion according to [SECG]. If the private key is an integer outside the range
/// > `[0, order-1]`, where order is the order of the curve being used, the private key MUST be
/// > reduced to its representative in `[0, order-1]` before being serialized.
///
/// [RFC9810 §7.1.2]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.2
/// [SECG]: https://www.secg.org/sec1-v2.pdf
impl<C> KeyExport for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
{
fn to_bytes(&self) -> FieldBytes<C> {
self.dk.to_bytes()
}
}

impl<C> Generate for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
{
fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
Ok(SecretKey::try_generate_from_rng(rng)?.into())
}
}

impl<C> TryDecapsulate<EcdhKem<C>> for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
type Error = Error;

fn try_decapsulate(
&self,
encapsulated_key: &Ciphertext<EcdhKem<C>>,
) -> Result<SharedKey<EcdhKem<C>>, Error> {
let encapsulated_key = PublicKey::<C>::from_sec1_bytes(encapsulated_key)?;
let shared_secret = self.dk.diffie_hellman(&encapsulated_key);
Ok(shared_secret.raw_secret_bytes().clone())
}
}

/// Elliptic Curve Diffie-Hellman Encapsulation Key (i.e. public encryption key)
///
/// Generic around an elliptic curve `C`.
pub type EcdhEncapsulationKey<C> = EncapsulationKey<PublicKey<C>>;

/// From [RFC9810 §7.1.1]: `SerializePublicKey` and `DeserializePublicKey`:
///
/// > For P-256, P-384, and P-521, the SerializePublicKey() function of the
/// > KEM performs the uncompressed Elliptic-Curve-Point-to-Octet-String
/// > conversion according to [SECG].
///
/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#name-serializepublickey-and-dese
/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.1
/// [SECG]: https://www.secg.org/sec1-v2.pdf
impl<C> KeySizeUser for EcdhEncapsulationKey<C>
where
Expand All @@ -66,7 +137,7 @@ where
/// > DeserializePublicKey() performs the uncompressed
/// > Octet-String-to-Elliptic-Curve-Point conversion.
///
/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#name-serializepublickey-and-dese
/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.1
impl<C> TryKeyInit for EcdhEncapsulationKey<C>
where
C: CurveArithmetic,
Expand All @@ -86,7 +157,7 @@ where
/// > KEM performs the uncompressed Elliptic-Curve-Point-to-Octet-String
/// > conversion according to [SECG].
///
/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#name-serializepublickey-and-dese
/// [RFC9810 §7.1.1]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.1
/// [SECG]: https://www.secg.org/sec1-v2.pdf
impl<C> KeyExport for EcdhEncapsulationKey<C>
where
Expand All @@ -95,20 +166,7 @@ where
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
fn to_bytes(&self) -> UncompressedPoint<C> {
// TODO(tarcieri): self.0.to_uncompressed_point()
let mut ret = UncompressedPoint::<C>::default();
ret.copy_from_slice(self.to_encoded_point(false).as_bytes());
ret
}
}

impl<C> Generate for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
{
fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
Ok(EphemeralSecret::try_generate_from_rng(rng)?.into())
self.0.to_uncompressed_point()
}
}

Expand All @@ -133,21 +191,3 @@ where
(pk, ss.raw_secret_bytes().clone())
}
}

impl<C> TryDecapsulate<EcdhKem<C>> for EcdhDecapsulationKey<C>
where
C: CurveArithmetic,
FieldBytesSize<C>: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
type Error = Error;

fn try_decapsulate(
&self,
encapsulated_key: &Ciphertext<EcdhKem<C>>,
) -> Result<SharedKey<EcdhKem<C>>, Error> {
let encapsulated_key = PublicKey::<C>::from_sec1_bytes(encapsulated_key)?;
let shared_secret = self.dk.diffie_hellman(&encapsulated_key);
Ok(shared_secret.raw_secret_bytes().clone())
}
}
90 changes: 59 additions & 31 deletions dhkem/src/x25519_kem.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
use crate::{DecapsulationKey, EncapsulationKey};
use kem::{
Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeySizeUser, TryKeyInit,
common::array::Array, consts::U32,
Decapsulate, Encapsulate, Generate, InvalidKey, Kem, Key, KeyExport, KeyInit, KeySizeUser,
TryKeyInit,
common::array::{Array, sizes::U32},
};
use rand_core::{CryptoRng, TryCryptoRng, UnwrapErr};
use x25519::{PublicKey, ReusableSecret};

/// Elliptic Curve Diffie-Hellman Decapsulation Key (i.e. secret decryption key)
///
/// Generic around an elliptic curve `C`.
pub type X25519DecapsulationKey = DecapsulationKey<ReusableSecret, PublicKey>;

/// Elliptic Curve Diffie-Hellman Encapsulation Key (i.e. public encryption key)
///
/// Generic around an elliptic curve `C`.
pub type X25519EncapsulationKey = EncapsulationKey<PublicKey>;
use rand_core::{CryptoRng, TryCryptoRng};
use x25519::{PublicKey, StaticSecret};

/// X25519 ciphertexts are compressed Montgomery x/u-coordinates.
type Ciphertext = Array<u8, U32>;
Expand All @@ -35,6 +26,59 @@ impl Kem for X25519Kem {
type SharedKeySize = U32;
}

/// Elliptic Curve Diffie-Hellman Decapsulation Key (i.e. secret decryption key)
///
/// Generic around an elliptic curve `C`.
pub type X25519DecapsulationKey = DecapsulationKey<StaticSecret, PublicKey>;

impl KeySizeUser for X25519DecapsulationKey {
type KeySize = U32;
}

/// From [RFC9810 §7.1.2]: `SerializePrivateKey` and `DeserializePrivateKey`:
///
/// > For X25519 and X448, private keys are identical to their byte string representation,
/// > so little processing has to be done. [...]
/// > DeserializePrivateKey() function MUST clamp its input
///
/// [RFC9810 §7.1.2]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.2
impl KeyInit for X25519DecapsulationKey {
fn new(key: &Key<Self>) -> Self {
StaticSecret::from(key.0).into()
}
}

/// From [RFC9810 §7.1.2]: `SerializePrivateKey` and `DeserializePrivateKey`:
///
/// > For X25519 and X448, private keys are identical to their byte string representation,
/// > so little processing has to be done. The SerializePrivateKey() function MUST clamp its output
///
/// [RFC9810 §7.1.2]: https://datatracker.ietf.org/doc/html/rfc9180#section-7.1.2
impl KeyExport for X25519DecapsulationKey {
fn to_bytes(&self) -> Key<Self> {
self.dk.to_bytes().into()
}
}

impl Generate for X25519DecapsulationKey {
fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
let key = Key::<Self>::try_generate_from_rng(rng)?;
Ok(StaticSecret::from(key.0).into())
}
}

impl Decapsulate<X25519Kem> for X25519DecapsulationKey {
fn decapsulate(&self, encapsulated_key: &Ciphertext) -> SharedKey {
let public_key = PublicKey::from(encapsulated_key.0);
self.dk.diffie_hellman(&public_key).to_bytes().into()
}
}

/// Elliptic Curve Diffie-Hellman Encapsulation Key (i.e. public encryption key)
///
/// Generic around an elliptic curve `C`.
pub type X25519EncapsulationKey = EncapsulationKey<PublicKey>;

/// From [RFC9810 §7.1.1]: `SerializePublicKey` and `DeserializePublicKey`:
///
/// > For X25519 and X448, the SerializePublicKey() and
Expand Down Expand Up @@ -72,31 +116,15 @@ impl KeyExport for X25519EncapsulationKey {
}
}

impl Generate for X25519DecapsulationKey {
fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
// TODO(tarcieri): don't panic! Fallible `ReusableSecret` generation?
Ok(Self::from(ReusableSecret::random_from_rng(&mut UnwrapErr(
rng,
))))
}
}

impl Encapsulate<X25519Kem> for X25519EncapsulationKey {
fn encapsulate_with_rng<R>(&self, rng: &mut R) -> (Ciphertext, SharedKey)
where
R: CryptoRng + ?Sized,
{
// ECDH encapsulation involves creating a new ephemeral key pair and then doing DH
let sk = ReusableSecret::random_from_rng(rng);
let sk = StaticSecret::random_from_rng(rng);
let pk = PublicKey::from(&sk);
let ss = sk.diffie_hellman(&self.0);
(pk.to_bytes().into(), ss.to_bytes().into())
}
}

impl Decapsulate<X25519Kem> for X25519DecapsulationKey {
fn decapsulate(&self, encapsulated_key: &Ciphertext) -> SharedKey {
let public_key = PublicKey::from(encapsulated_key.0);
self.dk.diffie_hellman(&public_key).to_bytes().into()
}
}
9 changes: 3 additions & 6 deletions dhkem/tests/hpke_p256_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

use core::convert::Infallible;
use dhkem::NistP256DecapsulationKey;
use elliptic_curve::Generate;
use hex_literal::hex;
use hkdf::Hkdf;
use kem::{Encapsulate, KeyExport, TryDecapsulate};
use kem::{Encapsulate, KeyExport, TryDecapsulate, TryKeyInit};
use rand_core::{TryCryptoRng, TryRng};
use sha2::Sha256;

Expand Down Expand Up @@ -68,6 +67,7 @@ fn extract_and_expand(shared_secret: &[u8], kem_context: &[u8]) -> Vec<u8> {
#[test]
// section A.3.1 https://datatracker.ietf.org/doc/html/rfc9180#appendix-A.3.1
fn test_dhkem_p256_hkdf_sha256() {
let example_key = hex!("f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2");
let example_pke = hex!(
"04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b32\
5ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4"
Expand All @@ -79,10 +79,7 @@ fn test_dhkem_p256_hkdf_sha256() {
let example_shared_secret =
hex!("c0d26aeab536609a572b07695d933b589dcf363ff9d93c93adea537aeabb8cb8");

let skr = NistP256DecapsulationKey::try_generate_from_rng(&mut ConstantRng(&hex!(
"f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2"
)))
.unwrap();
let skr = NistP256DecapsulationKey::new(&example_key.into()).unwrap();
let pkr = skr.as_ref();
assert_eq!(&pkr.to_bytes(), &example_pkr);

Expand Down