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 [`ProjectiveArithmetic`] 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
29use crate::{
30 AffineArithmetic, AffinePoint, AffineXCoordinate, Curve, FieldBytes, NonZeroScalar,
31 ProjectiveArithmetic, ProjectivePoint, PublicKey,
32};
33use core::borrow::Borrow;
34use digest::{crypto_common::BlockSizeUser, Digest};
35use group::Curve as _;
36use hkdf::{hmac::SimpleHmac, Hkdf};
37use rand_core::{CryptoRng, RngCore};
38use 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/// ```
60pub fn diffie_hellman<C>(
61 secret_key: impl Borrow<NonZeroScalar<C>>,
62 public_key: impl Borrow<AffinePoint<C>>,
63) -> SharedSecret<C>
64where
65 C: Curve + ProjectiveArithmetic,
66{
67 let public_point = ProjectivePoint::<C>::from(*public_key.borrow());
68 let secret_point = (public_point * secret_key.borrow().as_ref()).to_affine();
69 SharedSecret::new(secret_point)
70}
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.
93pub struct EphemeralSecret<C>
94where
95 C: Curve + ProjectiveArithmetic,
96{
97 scalar: NonZeroScalar<C>,
98}
99
100impl<C> EphemeralSecret<C>
101where
102 C: Curve + ProjectiveArithmetic,
103{
104 /// Generate a cryptographically random [`EphemeralSecret`].
105 pub fn random(rng: impl CryptoRng + RngCore) -> Self {
106 Self {
107 scalar: NonZeroScalar::random(rng),
108 }
109 }
110
111 /// Get the public key associated with this ephemeral secret.
112 ///
113 /// The `compress` flag enables point compression.
114 pub fn public_key(&self) -> PublicKey<C> {
115 PublicKey::from_secret_scalar(&self.scalar)
116 }
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 pub fn diffie_hellman(&self, public_key: &PublicKey<C>) -> SharedSecret<C> {
121 diffie_hellman(&self.scalar, public_key.as_affine())
122 }
123}
124
125impl<C> From<&EphemeralSecret<C>> for PublicKey<C>
126where
127 C: Curve + ProjectiveArithmetic,
128{
129 fn from(ephemeral_secret: &EphemeralSecret<C>) -> Self {
130 ephemeral_secret.public_key()
131 }
132}
133
134impl<C> Zeroize for EphemeralSecret<C>
135where
136 C: Curve + ProjectiveArithmetic,
137{
138 fn zeroize(&mut self) {
139 self.scalar.zeroize()
140 }
141}
142
143impl<C> ZeroizeOnDrop for EphemeralSecret<C> where C: Curve + ProjectiveArithmetic {}
144
145impl<C> Drop for EphemeralSecret<C>
146where
147 C: Curve + ProjectiveArithmetic,
148{
149 fn drop(&mut self) {
150 self.zeroize();
151 }
152}
153
154/// Shared secret value computed via ECDH key agreement.
155pub struct SharedSecret<C: Curve> {
156 /// Computed secret value
157 secret_bytes: FieldBytes<C>,
158}
159
160impl<C: Curve> SharedSecret<C> {
161 /// Create a new [`SharedSecret`] from an [`AffinePoint`] for this curve.
162 #[inline]
163 fn new(point: AffinePoint<C>) -> Self
164 where
165 C: AffineArithmetic,
166 {
167 Self {
168 secret_bytes: point.x(),
169 }
170 }
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 pub fn raw_secret_bytes(&self) -> &FieldBytes<C> {
214 &self.secret_bytes
215 }
216}
217
218impl<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
230impl<C: Curve> ZeroizeOnDrop for SharedSecret<C> {}
231
232impl<C: Curve> Drop for SharedSecret<C> {
233 fn drop(&mut self) {
234 self.secret_bytes.zeroize()
235 }
236}