| //! Elliptic Curve Diffie-Hellman Support. |
| //! |
| //! This module contains a generic ECDH implementation which is usable with |
| //! any elliptic curve which implements the [`CurveArithmetic`] trait (presently |
| //! the `k256` and `p256` crates) |
| //! |
| //! # ECDH Ephemeral (ECDHE) Usage |
| //! |
| //! Ephemeral Diffie-Hellman provides a one-time key exchange between two peers |
| //! using a randomly generated set of keys for each exchange. |
| //! |
| //! In practice ECDHE is used as part of an [Authenticated Key Exchange (AKE)][AKE] |
| //! protocol (e.g. [SIGMA]), where an existing cryptographic trust relationship |
| //! can be used to determine the authenticity of the ephemeral keys, such as |
| //! a digital signature. Without such an additional step, ECDHE is insecure! |
| //! (see security warning below) |
| //! |
| //! See the documentation for the [`EphemeralSecret`] type for more information |
| //! on performing ECDH ephemeral key exchanges. |
| //! |
| //! # Static ECDH Usage |
| //! |
| //! Static ECDH key exchanges are supported via the low-level |
| //! [`diffie_hellman`] function. |
| //! |
| //! [AKE]: https://en.wikipedia.org/wiki/Authenticated_Key_Exchange |
| //! [SIGMA]: https://webee.technion.ac.il/~hugo/sigma-pdf.pdf |
| |
| use crate::{ |
| point::AffineCoordinates, AffinePoint, Curve, CurveArithmetic, FieldBytes, NonZeroScalar, |
| ProjectivePoint, PublicKey, |
| }; |
| use core::borrow::Borrow; |
| use digest::{crypto_common::BlockSizeUser, Digest}; |
| use group::Curve as _; |
| use hkdf::{hmac::SimpleHmac, Hkdf}; |
| use rand_core::CryptoRngCore; |
| use zeroize::{Zeroize, ZeroizeOnDrop}; |
| |
| /// Low-level Elliptic Curve Diffie-Hellman (ECDH) function. |
| /// |
| /// Whenever possible, we recommend using the high-level ECDH ephemeral API |
| /// provided by [`EphemeralSecret`]. |
| /// |
| /// However, if you are implementing a protocol which requires a static scalar |
| /// value as part of an ECDH exchange, this API can be used to compute a |
| /// [`SharedSecret`] from that value. |
| /// |
| /// Note that this API operates on the low-level [`NonZeroScalar`] and |
| /// [`AffinePoint`] types. If you are attempting to use the higher-level |
| /// [`SecretKey`][`crate::SecretKey`] and [`PublicKey`] types, you will |
| /// need to use the following conversions: |
| /// |
| /// ```ignore |
| /// let shared_secret = elliptic_curve::ecdh::diffie_hellman( |
| /// secret_key.to_nonzero_scalar(), |
| /// public_key.as_affine() |
| /// ); |
| /// ``` |
| pub fn diffie_hellman<C>( |
| secret_key: impl Borrow<NonZeroScalar<C>>, |
| public_key: impl Borrow<AffinePoint<C>>, |
| ) -> SharedSecret<C> |
| where |
| C: CurveArithmetic, |
| { |
| let public_point = ProjectivePoint::<C>::from(*public_key.borrow()); |
| let secret_point = (public_point * secret_key.borrow().as_ref()).to_affine(); |
| SharedSecret::new(secret_point) |
| } |
| |
| /// Ephemeral Diffie-Hellman Secret. |
| /// |
| /// These are ephemeral "secret key" values which are deliberately designed |
| /// to avoid being persisted. |
| /// |
| /// To perform an ephemeral Diffie-Hellman exchange, do the following: |
| /// |
| /// - Have each participant generate an [`EphemeralSecret`] value |
| /// - Compute the [`PublicKey`] for that value |
| /// - Have each peer provide their [`PublicKey`] to their counterpart |
| /// - Use [`EphemeralSecret`] and the other participant's [`PublicKey`] |
| /// to compute a [`SharedSecret`] value. |
| /// |
| /// # ⚠️ SECURITY WARNING ⚠️ |
| /// |
| /// Ephemeral Diffie-Hellman exchanges are unauthenticated and without a |
| /// further authentication step are trivially vulnerable to man-in-the-middle |
| /// attacks! |
| /// |
| /// These exchanges should be performed in the context of a protocol which |
| /// takes further steps to authenticate the peers in a key exchange. |
| pub struct EphemeralSecret<C> |
| where |
| C: CurveArithmetic, |
| { |
| scalar: NonZeroScalar<C>, |
| } |
| |
| impl<C> EphemeralSecret<C> |
| where |
| C: CurveArithmetic, |
| { |
| /// Generate a cryptographically random [`EphemeralSecret`]. |
| pub fn random(rng: &mut impl CryptoRngCore) -> Self { |
| Self { |
| scalar: NonZeroScalar::random(rng), |
| } |
| } |
| |
| /// Get the public key associated with this ephemeral secret. |
| /// |
| /// The `compress` flag enables point compression. |
| pub fn public_key(&self) -> PublicKey<C> { |
| PublicKey::from_secret_scalar(&self.scalar) |
| } |
| |
| /// Compute a Diffie-Hellman shared secret from an ephemeral secret and the |
| /// public key of the other participant in the exchange. |
| pub fn diffie_hellman(&self, public_key: &PublicKey<C>) -> SharedSecret<C> { |
| diffie_hellman(self.scalar, public_key.as_affine()) |
| } |
| } |
| |
| impl<C> From<&EphemeralSecret<C>> for PublicKey<C> |
| where |
| C: CurveArithmetic, |
| { |
| fn from(ephemeral_secret: &EphemeralSecret<C>) -> Self { |
| ephemeral_secret.public_key() |
| } |
| } |
| |
| impl<C> Zeroize for EphemeralSecret<C> |
| where |
| C: CurveArithmetic, |
| { |
| fn zeroize(&mut self) { |
| self.scalar.zeroize() |
| } |
| } |
| |
| impl<C> ZeroizeOnDrop for EphemeralSecret<C> where C: CurveArithmetic {} |
| |
| impl<C> Drop for EphemeralSecret<C> |
| where |
| C: CurveArithmetic, |
| { |
| fn drop(&mut self) { |
| self.zeroize(); |
| } |
| } |
| |
| /// Shared secret value computed via ECDH key agreement. |
| pub struct SharedSecret<C: Curve> { |
| /// Computed secret value |
| secret_bytes: FieldBytes<C>, |
| } |
| |
| impl<C: Curve> SharedSecret<C> { |
| /// Create a new [`SharedSecret`] from an [`AffinePoint`] for this curve. |
| #[inline] |
| fn new(point: AffinePoint<C>) -> Self |
| where |
| C: CurveArithmetic, |
| { |
| Self { |
| secret_bytes: point.x(), |
| } |
| } |
| |
| /// Use [HKDF] (HMAC-based Extract-and-Expand Key Derivation Function) to |
| /// extract entropy from this shared secret. |
| /// |
| /// This method can be used to transform the shared secret into uniformly |
| /// random values which are suitable as key material. |
| /// |
| /// The `D` type parameter is a cryptographic digest function. |
| /// `sha2::Sha256` is a common choice for use with HKDF. |
| /// |
| /// The `salt` parameter can be used to supply additional randomness. |
| /// Some examples include: |
| /// |
| /// - randomly generated (but authenticated) string |
| /// - fixed application-specific value |
| /// - previous shared secret used for rekeying (as in TLS 1.3 and Noise) |
| /// |
| /// After initializing HKDF, use [`Hkdf::expand`] to obtain output key |
| /// material. |
| /// |
| /// [HKDF]: https://en.wikipedia.org/wiki/HKDF |
| pub fn extract<D>(&self, salt: Option<&[u8]>) -> Hkdf<D, SimpleHmac<D>> |
| where |
| D: BlockSizeUser + Clone + Digest, |
| { |
| Hkdf::new(salt, &self.secret_bytes) |
| } |
| |
| /// This value contains the raw serialized x-coordinate of the elliptic curve |
| /// point computed from a Diffie-Hellman exchange, serialized as bytes. |
| /// |
| /// When in doubt, use [`SharedSecret::extract`] instead. |
| /// |
| /// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️ |
| /// |
| /// This value is not uniformly random and should not be used directly |
| /// as a cryptographic key for anything which requires that property |
| /// (e.g. symmetric ciphers). |
| /// |
| /// Instead, the resulting value should be used as input to a Key Derivation |
| /// Function (KDF) or cryptographic hash function to produce a symmetric key. |
| /// The [`SharedSecret::extract`] function will do this for you. |
| pub fn raw_secret_bytes(&self) -> &FieldBytes<C> { |
| &self.secret_bytes |
| } |
| } |
| |
| impl<C: Curve> From<FieldBytes<C>> for SharedSecret<C> { |
| /// NOTE: this impl is intended to be used by curve implementations to |
| /// instantiate a [`SharedSecret`] value from their respective |
| /// [`AffinePoint`] type. |
| /// |
| /// Curve implementations should provide the field element representing |
| /// the affine x-coordinate as `secret_bytes`. |
| fn from(secret_bytes: FieldBytes<C>) -> Self { |
| Self { secret_bytes } |
| } |
| } |
| |
| impl<C: Curve> ZeroizeOnDrop for SharedSecret<C> {} |
| |
| impl<C: Curve> Drop for SharedSecret<C> { |
| fn drop(&mut self) { |
| self.secret_bytes.zeroize() |
| } |
| } |