Coverage Report

Created: 2026-05-16 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/elliptic-curve-0.13.8/src/ecdh.rs
Line
Count
Source
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://webee.technion.ac.il/~hugo/sigma-pdf.pdf
28
29
use crate::{
30
    point::AffineCoordinates, AffinePoint, Curve, CurveArithmetic, FieldBytes, NonZeroScalar,
31
    ProjectivePoint, PublicKey,
32
};
33
use core::borrow::Borrow;
34
use digest::{crypto_common::BlockSizeUser, Digest};
35
use group::Curve as _;
36
use hkdf::{hmac::SimpleHmac, Hkdf};
37
use rand_core::CryptoRngCore;
38
use zeroize::{Zeroize, ZeroizeOnDrop};
39
40
/// Low-level Elliptic Curve Diffie-Hellman (ECDH) function.
41
///
42
/// Whenever possible, we recommend using the high-level ECDH ephemeral API
43
/// provided by [`EphemeralSecret`].
44
///
45
/// However, if you are implementing a protocol which requires a static scalar
46
/// value as part of an ECDH exchange, this API can be used to compute a
47
/// [`SharedSecret`] from that value.
48
///
49
/// Note that this API operates on the low-level [`NonZeroScalar`] and
50
/// [`AffinePoint`] types. If you are attempting to use the higher-level
51
/// [`SecretKey`][`crate::SecretKey`] and [`PublicKey`] types, you will
52
/// need to use the following conversions:
53
///
54
/// ```ignore
55
/// let shared_secret = elliptic_curve::ecdh::diffie_hellman(
56
///     secret_key.to_nonzero_scalar(),
57
///     public_key.as_affine()
58
/// );
59
/// ```
60
0
pub fn diffie_hellman<C>(
61
0
    secret_key: impl Borrow<NonZeroScalar<C>>,
62
0
    public_key: impl Borrow<AffinePoint<C>>,
63
0
) -> SharedSecret<C>
64
0
where
65
0
    C: CurveArithmetic,
66
{
67
0
    let public_point = ProjectivePoint::<C>::from(*public_key.borrow());
68
0
    let secret_point = (public_point * secret_key.borrow().as_ref()).to_affine();
69
0
    SharedSecret::new(secret_point)
70
0
}
71
72
/// Ephemeral Diffie-Hellman Secret.
73
///
74
/// These are ephemeral "secret key" values which are deliberately designed
75
/// to avoid being persisted.
76
///
77
/// To perform an ephemeral Diffie-Hellman exchange, do the following:
78
///
79
/// - Have each participant generate an [`EphemeralSecret`] value
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.
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.
93
pub struct EphemeralSecret<C>
94
where
95
    C: CurveArithmetic,
96
{
97
    scalar: NonZeroScalar<C>,
98
}
99
100
impl<C> EphemeralSecret<C>
101
where
102
    C: CurveArithmetic,
103
{
104
    /// Generate a cryptographically random [`EphemeralSecret`].
105
39.7k
    pub fn random(rng: &mut impl CryptoRngCore) -> Self {
106
39.7k
        Self {
107
39.7k
            scalar: NonZeroScalar::random(rng),
108
39.7k
        }
109
39.7k
    }
110
111
    /// Get the public key associated with this ephemeral secret.
112
    ///
113
    /// The `compress` flag enables point compression.
114
1.85k
    pub fn public_key(&self) -> PublicKey<C> {
115
1.85k
        PublicKey::from_secret_scalar(&self.scalar)
116
1.85k
    }
117
118
    /// Compute a Diffie-Hellman shared secret from an ephemeral secret and the
119
    /// public key of the other participant in the exchange.
120
0
    pub fn diffie_hellman(&self, public_key: &PublicKey<C>) -> SharedSecret<C> {
121
0
        diffie_hellman(self.scalar, public_key.as_affine())
122
0
    }
123
}
124
125
impl<C> From<&EphemeralSecret<C>> for PublicKey<C>
126
where
127
    C: CurveArithmetic,
128
{
129
    fn from(ephemeral_secret: &EphemeralSecret<C>) -> Self {
130
        ephemeral_secret.public_key()
131
    }
132
}
133
134
impl<C> Zeroize for EphemeralSecret<C>
135
where
136
    C: CurveArithmetic,
137
{
138
39.7k
    fn zeroize(&mut self) {
139
39.7k
        self.scalar.zeroize()
140
39.7k
    }
141
}
142
143
impl<C> ZeroizeOnDrop for EphemeralSecret<C> where C: CurveArithmetic {}
144
145
impl<C> Drop for EphemeralSecret<C>
146
where
147
    C: CurveArithmetic,
148
{
149
39.7k
    fn drop(&mut self) {
150
39.7k
        self.zeroize();
151
39.7k
    }
152
}
153
154
/// Shared secret value computed via ECDH key agreement.
155
pub struct SharedSecret<C: Curve> {
156
    /// Computed secret value
157
    secret_bytes: FieldBytes<C>,
158
}
159
160
impl<C: Curve> SharedSecret<C> {
161
    /// Create a new [`SharedSecret`] from an [`AffinePoint`] for this curve.
162
    #[inline]
163
0
    fn new(point: AffinePoint<C>) -> Self
164
0
    where
165
0
        C: CurveArithmetic,
166
    {
167
0
        Self {
168
0
            secret_bytes: point.x(),
169
0
        }
170
0
    }
171
172
    /// Use [HKDF] (HMAC-based Extract-and-Expand Key Derivation Function) to
173
    /// extract entropy from this shared secret.
174
    ///
175
    /// This method can be used to transform the shared secret into uniformly
176
    /// random values which are suitable as key material.
177
    ///
178
    /// The `D` type parameter is a cryptographic digest function.
179
    /// `sha2::Sha256` is a common choice for use with HKDF.
180
    ///
181
    /// The `salt` parameter can be used to supply additional randomness.
182
    /// Some examples include:
183
    ///
184
    /// - randomly generated (but authenticated) string
185
    /// - fixed application-specific value
186
    /// - previous shared secret used for rekeying (as in TLS 1.3 and Noise)
187
    ///
188
    /// After initializing HKDF, use [`Hkdf::expand`] to obtain output key
189
    /// material.
190
    ///
191
    /// [HKDF]: https://en.wikipedia.org/wiki/HKDF
192
    pub fn extract<D>(&self, salt: Option<&[u8]>) -> Hkdf<D, SimpleHmac<D>>
193
    where
194
        D: BlockSizeUser + Clone + Digest,
195
    {
196
        Hkdf::new(salt, &self.secret_bytes)
197
    }
198
199
    /// This value contains the raw serialized x-coordinate of the elliptic curve
200
    /// point computed from a Diffie-Hellman exchange, serialized as bytes.
201
    ///
202
    /// When in doubt, use [`SharedSecret::extract`] instead.
203
    ///
204
    /// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️
205
    ///
206
    /// This value is not uniformly random and should not be used directly
207
    /// as a cryptographic key for anything which requires that property
208
    /// (e.g. symmetric ciphers).
209
    ///
210
    /// Instead, the resulting value should be used as input to a Key Derivation
211
    /// Function (KDF) or cryptographic hash function to produce a symmetric key.
212
    /// The [`SharedSecret::extract`] function will do this for you.
213
0
    pub fn raw_secret_bytes(&self) -> &FieldBytes<C> {
214
0
        &self.secret_bytes
215
0
    }
216
}
217
218
impl<C: Curve> From<FieldBytes<C>> for SharedSecret<C> {
219
    /// NOTE: this impl is intended to be used by curve implementations to
220
    /// instantiate a [`SharedSecret`] value from their respective
221
    /// [`AffinePoint`] type.
222
    ///
223
    /// Curve implementations should provide the field element representing
224
    /// the affine x-coordinate as `secret_bytes`.
225
    fn from(secret_bytes: FieldBytes<C>) -> Self {
226
        Self { secret_bytes }
227
    }
228
}
229
230
impl<C: Curve> ZeroizeOnDrop for SharedSecret<C> {}
231
232
impl<C: Curve> Drop for SharedSecret<C> {
233
0
    fn drop(&mut self) {
234
0
        self.secret_bytes.zeroize()
235
0
    }
236
}