Skip to main content

elliptic_curve/
ecdh.rs

1//! Elliptic Curve Diffie-Hellman Support.
2//!
3//! This module contains a generic ECDH implementation which is usable with
4//! any elliptic curve which implements the [`CurveArithmetic`] trait (presently
5//! the `k256` and `p256` crates)
6//!
7//! # ECDH Ephemeral (ECDHE) Usage
8//!
9//! Ephemeral Diffie-Hellman provides a one-time key exchange between two peers
10//! using a randomly generated set of keys for each exchange.
11//!
12//! In practice ECDHE is used as part of an [Authenticated Key Exchange (AKE)][AKE]
13//! protocol (e.g. [SIGMA]), where an existing cryptographic trust relationship
14//! can be used to determine the authenticity of the ephemeral keys, such as
15//! a digital signature. Without such an additional step, ECDHE is insecure!
16//! (see security warning below)
17//!
18//! See the documentation for the [`EphemeralSecret`] type for more information
19//! on performing ECDH ephemeral key exchanges.
20//!
21//! # Static ECDH Usage
22//!
23//! Static ECDH key exchanges are supported via the low-level
24//! [`diffie_hellman`] function.
25//!
26//! [AKE]: https://en.wikipedia.org/wiki/Authenticated_Key_Exchange
27//! [SIGMA]: https://www.iacr.org/cryptodb/archive/2003/CRYPTO/1495/1495.pdf
28
29pub use hkdf::hmac::EagerHash;
30
31use crate::{
32    AffinePoint, Curve, CurveArithmetic, CurveGroup, FieldBytes, NonZeroScalar, ProjectivePoint,
33    PublicKey, point::AffineCoordinates,
34};
35use common::Generate;
36use core::{borrow::Borrow, fmt};
37use hkdf::Hkdf;
38use rand_core::{CryptoRng, TryCryptoRng};
39use zeroize::{Zeroize, ZeroizeOnDrop};
40
41/// Low-level Elliptic Curve Diffie-Hellman (ECDH) function.
42///
43/// Whenever possible, we recommend using the high-level ECDH ephemeral API
44/// provided by [`EphemeralSecret`].
45///
46/// However, if you are implementing a protocol which requires a static scalar
47/// value as part of an ECDH exchange, this API can be used to compute a
48/// [`SharedSecret`] from that value.
49///
50/// Note that this API operates on the low-level [`NonZeroScalar`] and
51/// [`AffinePoint`] types. If you are attempting to use the higher-level
52/// [`SecretKey`][`crate::SecretKey`] and [`PublicKey`] types, you will
53/// need to use the following conversions:
54///
55/// ```ignore
56/// let shared_secret = elliptic_curve::ecdh::diffie_hellman(
57///     secret_key.to_nonzero_scalar(),
58///     public_key.as_affine()
59/// );
60/// ```
61pub fn diffie_hellman<C>(
62    secret_key: impl Borrow<NonZeroScalar<C>>,
63    public_key: impl Borrow<AffinePoint<C>>,
64) -> SharedSecret<C>
65where
66    C: CurveArithmetic,
67{
68    let public_point = ProjectivePoint::<C>::from(*public_key.borrow());
69    let secret_point = (public_point * secret_key.borrow().as_ref()).to_affine();
70    SharedSecret::new(secret_point)
71}
72
73/// Ephemeral Diffie-Hellman Secret.
74///
75/// These are ephemeral "secret key" values which are deliberately designed to avoid persistence.
76///
77/// To perform an ephemeral Diffie-Hellman exchange, do the following:
78///
79/// - Have each participant generate an [`EphemeralSecret`] value using the [`Generate`] trait
80/// - Compute the [`PublicKey`] for that value
81/// - Have each peer provide their [`PublicKey`] to their counterpart
82/// - Use [`EphemeralSecret`] and the other participant's [`PublicKey`]
83///   to compute a [`SharedSecret`] value using the [`EphemeralSecret::diffie_hellman`] function
84///
85/// # ⚠️ SECURITY WARNING ⚠️
86///
87/// Ephemeral Diffie-Hellman exchanges are unauthenticated and without a
88/// further authentication step are trivially vulnerable to man-in-the-middle
89/// attacks!
90///
91/// These exchanges should be performed in the context of a protocol which
92/// takes further steps to authenticate the peers in a key exchange.
93pub struct EphemeralSecret<C>
94where
95    C: CurveArithmetic,
96{
97    scalar: NonZeroScalar<C>,
98}
99
100impl<C: CurveArithmetic> fmt::Debug for EphemeralSecret<C> {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        f.debug_struct("EphemeralSecret").finish_non_exhaustive()
103    }
104}
105
106impl<C> Generate for EphemeralSecret<C>
107where
108    C: CurveArithmetic,
109{
110    fn try_generate_from_rng<R: TryCryptoRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
111        Ok(Self {
112            scalar: NonZeroScalar::try_generate_from_rng(rng)?,
113        })
114    }
115}
116
117impl<C> EphemeralSecret<C>
118where
119    C: CurveArithmetic,
120{
121    /// Get the public key associated with this ephemeral secret.
122    ///
123    /// The `compress` flag enables point compression.
124    pub fn public_key(&self) -> PublicKey<C> {
125        PublicKey::from_secret_scalar(&self.scalar)
126    }
127
128    /// Compute a Diffie-Hellman shared secret from an ephemeral secret and the
129    /// public key of the other participant in the exchange.
130    pub fn diffie_hellman(&self, public_key: &PublicKey<C>) -> SharedSecret<C> {
131        diffie_hellman(self.scalar, public_key.as_affine())
132    }
133
134    /// DEPRECATED: Generate a cryptographically random [`EphemeralSecret`].
135    ///
136    /// Use the [`Generate`] trait instead.
137    #[deprecated(since = "0.14.0", note = "use the `Generate` trait instead")]
138    pub fn random<R: CryptoRng + ?Sized>(rng: &mut R) -> Self {
139        Self::generate_from_rng(rng)
140    }
141}
142
143impl<C> From<&EphemeralSecret<C>> for PublicKey<C>
144where
145    C: CurveArithmetic,
146{
147    fn from(ephemeral_secret: &EphemeralSecret<C>) -> Self {
148        ephemeral_secret.public_key()
149    }
150}
151
152impl<C> Zeroize for EphemeralSecret<C>
153where
154    C: CurveArithmetic,
155{
156    fn zeroize(&mut self) {
157        self.scalar.zeroize();
158    }
159}
160
161impl<C> ZeroizeOnDrop for EphemeralSecret<C> where C: CurveArithmetic {}
162
163impl<C> Drop for EphemeralSecret<C>
164where
165    C: CurveArithmetic,
166{
167    fn drop(&mut self) {
168        self.zeroize();
169    }
170}
171
172/// Shared secret value computed via ECDH key agreement.
173pub struct SharedSecret<C: Curve> {
174    /// Computed secret value
175    secret_bytes: FieldBytes<C>,
176}
177
178impl<C: Curve> fmt::Debug for SharedSecret<C> {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        f.debug_struct("SharedSecret").finish_non_exhaustive()
181    }
182}
183
184impl<C: Curve> SharedSecret<C> {
185    /// Create a new [`SharedSecret`] from an [`AffinePoint`] for this curve.
186    #[inline]
187    fn new(point: AffinePoint<C>) -> Self
188    where
189        C: CurveArithmetic,
190    {
191        Self {
192            secret_bytes: point.x(),
193        }
194    }
195
196    /// Use [HKDF] (HMAC-based Extract-and-Expand Key Derivation Function) to
197    /// extract entropy from this shared secret.
198    ///
199    /// This method can be used to transform the shared secret into uniformly
200    /// random values which are suitable as key material.
201    ///
202    /// The `D` type parameter is a cryptographic digest function.
203    /// `sha2::Sha256` is a common choice for use with HKDF.
204    ///
205    /// The `salt` parameter can be used to supply additional randomness.
206    /// Some examples include:
207    ///
208    /// - randomly generated (but authenticated) string
209    /// - fixed application-specific value
210    /// - previous shared secret used for rekeying (as in TLS 1.3 and Noise)
211    ///
212    /// After initializing HKDF, use [`Hkdf::expand`] to obtain output key
213    /// material.
214    ///
215    /// [HKDF]: https://en.wikipedia.org/wiki/HKDF
216    pub fn extract<D>(&self, salt: Option<&[u8]>) -> Hkdf<D>
217    where
218        D: EagerHash,
219    {
220        Hkdf::new(salt, &self.secret_bytes)
221    }
222
223    /// This value contains the raw serialized x-coordinate of the elliptic curve
224    /// point computed from a Diffie-Hellman exchange, serialized as bytes.
225    ///
226    /// When in doubt, use [`SharedSecret::extract`] instead.
227    ///
228    /// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️
229    ///
230    /// This value is not uniformly random and should not be used directly
231    /// as a cryptographic key for anything which requires that property
232    /// (e.g. symmetric ciphers).
233    ///
234    /// Instead, the resulting value should be used as input to a Key Derivation
235    /// Function (KDF) or cryptographic hash function to produce a symmetric key.
236    /// The [`SharedSecret::extract`] function will do this for you.
237    pub fn raw_secret_bytes(&self) -> &FieldBytes<C> {
238        &self.secret_bytes
239    }
240}
241
242impl<C: Curve> From<FieldBytes<C>> for SharedSecret<C> {
243    /// NOTE: this impl is intended to be used by curve implementations to
244    /// instantiate a [`SharedSecret`] value from their respective
245    /// [`AffinePoint`] type.
246    ///
247    /// Curve implementations should provide the field element representing
248    /// the affine x-coordinate as `secret_bytes`.
249    fn from(secret_bytes: FieldBytes<C>) -> Self {
250        Self { secret_bytes }
251    }
252}
253
254impl<C: Curve> ZeroizeOnDrop for SharedSecret<C> {}
255
256impl<C: Curve> Drop for SharedSecret<C> {
257    fn drop(&mut self) {
258        self.secret_bytes.zeroize();
259    }
260}