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}