x25519_dalek/x25519.rs
1// -*- mode: rust; -*-
2//
3// This file is part of x25519-dalek.
4// Copyright (c) 2017-2021 isis lovecruft
5// Copyright (c) 2019-2021 DebugSteven
6// See LICENSE for licensing information.
7//
8// Authors:
9// - isis agora lovecruft <isis@patternsinthevoid.net>
10// - DebugSteven <debugsteven@gmail.com>
11
12//! x25519 Diffie-Hellman key exchange
13//!
14//! This implements x25519 key exchange as specified by Mike Hamburg
15//! and Adam Langley in [RFC7748](https://tools.ietf.org/html/rfc7748).
16
17use curve25519_dalek::{edwards::EdwardsPoint, montgomery::MontgomeryPoint, traits::IsIdentity};
18
19use rand_core::CryptoRng;
20use rand_core::RngCore;
21
22#[cfg(feature = "zeroize")]
23use zeroize::Zeroize;
24
25/// A Diffie-Hellman public key
26///
27/// We implement `Zeroize` so that downstream consumers may derive it for `Drop`
28/// should they wish to erase public keys from memory. Note that this erasure
29/// (in this crate) does *not* automatically happen, but either must be derived
30/// for Drop or explicitly called.
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[cfg_attr(feature = "zeroize", derive(Zeroize))]
33#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
34pub struct PublicKey(pub(crate) MontgomeryPoint);
35
36impl From<[u8; 32]> for PublicKey {
37 /// Given a byte array, construct a x25519 `PublicKey`.
38 fn from(bytes: [u8; 32]) -> PublicKey {
39 PublicKey(MontgomeryPoint(bytes))
40 }
41}
42
43impl PublicKey {
44 /// Convert this public key to a byte array.
45 #[inline]
46 pub fn to_bytes(&self) -> [u8; 32] {
47 self.0.to_bytes()
48 }
49
50 /// View this public key as a byte array.
51 #[inline]
52 pub fn as_bytes(&self) -> &[u8; 32] {
53 self.0.as_bytes()
54 }
55}
56
57impl AsRef<[u8]> for PublicKey {
58 /// View this public key as a byte array.
59 #[inline]
60 fn as_ref(&self) -> &[u8] {
61 self.as_bytes()
62 }
63}
64
65/// A short-lived Diffie-Hellman secret key that can only be used to compute a single
66/// [`SharedSecret`].
67///
68/// This type is identical to the `StaticSecret` type, except that the
69/// [`EphemeralSecret::diffie_hellman`] method consumes and then wipes the secret key, and there
70/// are no serialization methods defined. This means that [`EphemeralSecret`]s can only be
71/// generated from fresh randomness where the compiler statically checks that the resulting
72/// secret is used at most once.
73#[cfg_attr(feature = "zeroize", derive(Zeroize))]
74#[cfg_attr(feature = "zeroize", zeroize(drop))]
75pub struct EphemeralSecret(pub(crate) [u8; 32]);
76
77impl EphemeralSecret {
78 /// Perform a Diffie-Hellman key agreement between `self` and
79 /// `their_public` key to produce a [`SharedSecret`].
80 pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret {
81 SharedSecret(their_public.0.mul_clamped(self.0))
82 }
83
84 /// Generate a new [`EphemeralSecret`] with the supplied RNG.
85 #[deprecated(
86 since = "2.0.0",
87 note = "Renamed to `random_from_rng`. This will be removed in 2.1.0"
88 )]
89 pub fn new<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
90 Self::random_from_rng(&mut csprng)
91 }
92
93 /// Generate a new [`EphemeralSecret`] with the supplied RNG.
94 pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
95 // The secret key is random bytes. Clamping is done later.
96 let mut bytes = [0u8; 32];
97 csprng.fill_bytes(&mut bytes);
98 EphemeralSecret(bytes)
99 }
100
101 /// Generate a new [`EphemeralSecret`].
102 #[cfg(feature = "getrandom")]
103 pub fn random() -> Self {
104 Self::random_from_rng(rand_core::OsRng)
105 }
106}
107
108impl<'a> From<&'a EphemeralSecret> for PublicKey {
109 /// Given an x25519 [`EphemeralSecret`] key, compute its corresponding [`PublicKey`].
110 fn from(secret: &'a EphemeralSecret) -> PublicKey {
111 PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery())
112 }
113}
114
115/// A Diffie-Hellman secret key which may be used more than once, but is
116/// purposefully not serialiseable in order to discourage key-reuse. This is
117/// implemented to facilitate protocols such as Noise (e.g. Noise IK key usage,
118/// etc.) and X3DH which require an "ephemeral" key to conduct the
119/// Diffie-Hellman operation multiple times throughout the protocol, while the
120/// protocol run at a higher level is only conducted once per key.
121///
122/// Similarly to [`EphemeralSecret`], this type does _not_ have serialisation
123/// methods, in order to discourage long-term usage of secret key material. (For
124/// long-term secret keys, see `StaticSecret`.)
125///
126/// # Warning
127///
128/// If you're uncertain about whether you should use this, then you likely
129/// should not be using this. Our strongly recommended advice is to use
130/// [`EphemeralSecret`] at all times, as that type enforces at compile-time that
131/// secret keys are never reused, which can have very serious security
132/// implications for many protocols.
133#[cfg(feature = "reusable_secrets")]
134#[cfg_attr(feature = "zeroize", derive(Zeroize))]
135#[cfg_attr(feature = "zeroize", zeroize(drop))]
136#[derive(Clone)]
137pub struct ReusableSecret(pub(crate) [u8; 32]);
138
139#[cfg(feature = "reusable_secrets")]
140impl ReusableSecret {
141 /// Perform a Diffie-Hellman key agreement between `self` and
142 /// `their_public` key to produce a [`SharedSecret`].
143 pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
144 SharedSecret(their_public.0.mul_clamped(self.0))
145 }
146
147 /// Generate a new [`ReusableSecret`] with the supplied RNG.
148 #[deprecated(
149 since = "2.0.0",
150 note = "Renamed to `random_from_rng`. This will be removed in 2.1.0."
151 )]
152 pub fn new<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
153 Self::random_from_rng(&mut csprng)
154 }
155
156 /// Generate a new [`ReusableSecret`] with the supplied RNG.
157 pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
158 // The secret key is random bytes. Clamping is done later.
159 let mut bytes = [0u8; 32];
160 csprng.fill_bytes(&mut bytes);
161 ReusableSecret(bytes)
162 }
163
164 /// Generate a new [`ReusableSecret`].
165 #[cfg(feature = "getrandom")]
166 pub fn random() -> Self {
167 Self::random_from_rng(rand_core::OsRng)
168 }
169}
170
171#[cfg(feature = "reusable_secrets")]
172impl<'a> From<&'a ReusableSecret> for PublicKey {
173 /// Given an x25519 [`ReusableSecret`] key, compute its corresponding [`PublicKey`].
174 fn from(secret: &'a ReusableSecret) -> PublicKey {
175 PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery())
176 }
177}
178
179/// A Diffie-Hellman secret key that can be used to compute multiple [`SharedSecret`]s.
180///
181/// This type is identical to the [`EphemeralSecret`] type, except that the
182/// [`StaticSecret::diffie_hellman`] method does not consume the secret key, and the type provides
183/// serialization methods to save and load key material. This means that the secret may be used
184/// multiple times (but does not *have to be*).
185///
186/// # Warning
187///
188/// If you're uncertain about whether you should use this, then you likely
189/// should not be using this. Our strongly recommended advice is to use
190/// [`EphemeralSecret`] at all times, as that type enforces at compile-time that
191/// secret keys are never reused, which can have very serious security
192/// implications for many protocols.
193#[cfg(feature = "static_secrets")]
194#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
195#[cfg_attr(feature = "zeroize", derive(Zeroize))]
196#[cfg_attr(feature = "zeroize", zeroize(drop))]
197#[derive(Clone)]
198pub struct StaticSecret([u8; 32]);
199
200#[cfg(feature = "static_secrets")]
201impl StaticSecret {
202 /// Perform a Diffie-Hellman key agreement between `self` and
203 /// `their_public` key to produce a `SharedSecret`.
204 pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
205 SharedSecret(their_public.0.mul_clamped(self.0))
206 }
207
208 /// Generate a new [`StaticSecret`] with the supplied RNG.
209 #[deprecated(
210 since = "2.0.0",
211 note = "Renamed to `random_from_rng`. This will be removed in 2.1.0"
212 )]
213 pub fn new<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
214 Self::random_from_rng(&mut csprng)
215 }
216
217 /// Generate a new [`StaticSecret`] with the supplied RNG.
218 pub fn random_from_rng<T: RngCore + CryptoRng>(mut csprng: T) -> Self {
219 // The secret key is random bytes. Clamping is done later.
220 let mut bytes = [0u8; 32];
221 csprng.fill_bytes(&mut bytes);
222 StaticSecret(bytes)
223 }
224
225 /// Generate a new [`StaticSecret`].
226 #[cfg(feature = "getrandom")]
227 pub fn random() -> Self {
228 Self::random_from_rng(rand_core::OsRng)
229 }
230
231 /// Extract this key's bytes for serialization.
232 #[inline]
233 pub fn to_bytes(&self) -> [u8; 32] {
234 self.0
235 }
236
237 /// View this key as a byte array.
238 #[inline]
239 pub fn as_bytes(&self) -> &[u8; 32] {
240 &self.0
241 }
242}
243
244#[cfg(feature = "static_secrets")]
245impl From<[u8; 32]> for StaticSecret {
246 /// Load a secret key from a byte array.
247 fn from(bytes: [u8; 32]) -> StaticSecret {
248 StaticSecret(bytes)
249 }
250}
251
252#[cfg(feature = "static_secrets")]
253impl<'a> From<&'a StaticSecret> for PublicKey {
254 /// Given an x25519 [`StaticSecret`] key, compute its corresponding [`PublicKey`].
255 fn from(secret: &'a StaticSecret) -> PublicKey {
256 PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery())
257 }
258}
259
260#[cfg(feature = "static_secrets")]
261impl AsRef<[u8]> for StaticSecret {
262 /// View this key as a byte array.
263 #[inline]
264 fn as_ref(&self) -> &[u8] {
265 self.as_bytes()
266 }
267}
268
269/// The result of a Diffie-Hellman key exchange.
270///
271/// Each party computes this using their [`EphemeralSecret`] or [`StaticSecret`] and their
272/// counterparty's [`PublicKey`].
273#[cfg_attr(feature = "zeroize", derive(Zeroize))]
274#[cfg_attr(feature = "zeroize", zeroize(drop))]
275pub struct SharedSecret(pub(crate) MontgomeryPoint);
276
277impl SharedSecret {
278 /// Convert this shared secret to a byte array.
279 #[inline]
280 pub fn to_bytes(&self) -> [u8; 32] {
281 self.0.to_bytes()
282 }
283
284 /// View this shared secret key as a byte array.
285 #[inline]
286 pub fn as_bytes(&self) -> &[u8; 32] {
287 self.0.as_bytes()
288 }
289
290 /// Ensure in constant-time that this shared secret did not result from a
291 /// key exchange with non-contributory behaviour.
292 ///
293 /// In some more exotic protocols which need to guarantee "contributory"
294 /// behaviour for both parties, that is, that each party contributed a public
295 /// value which increased the security of the resulting shared secret.
296 /// To take an example protocol attack where this could lead to undesirable
297 /// results [from Thái "thaidn" Dương](https://vnhacker.blogspot.com/2015/09/why-not-validating-curve25519-public.html):
298 ///
299 /// > If Mallory replaces Alice's and Bob's public keys with zero, which is
300 /// > a valid Curve25519 public key, he would be able to force the ECDH
301 /// > shared value to be zero, which is the encoding of the point at infinity,
302 /// > and thus get to dictate some publicly known values as the shared
303 /// > keys. It still requires an active man-in-the-middle attack to pull the
304 /// > trick, after which, however, not only Mallory can decode Alice's data,
305 /// > but everyone too! It is also impossible for Alice and Bob to detect the
306 /// > intrusion, as they still share the same keys, and can communicate with
307 /// > each other as normal.
308 ///
309 /// The original Curve25519 specification argues that checks for
310 /// non-contributory behaviour are "unnecessary for Diffie-Hellman".
311 /// Whether this check is necessary for any particular given protocol is
312 /// often a matter of debate, which we will not re-hash here, but simply
313 /// cite some of the [relevant] [public] [discussions].
314 ///
315 /// # Returns
316 ///
317 /// Returns `true` if the key exchange was contributory (good), and `false`
318 /// otherwise (can be bad for some protocols).
319 ///
320 /// [relevant]: https://tools.ietf.org/html/rfc7748#page-15
321 /// [public]: https://vnhacker.blogspot.com/2015/09/why-not-validating-curve25519-public.html
322 /// [discussions]: https://vnhacker.blogspot.com/2016/08/the-internet-of-broken-protocols.html
323 #[must_use]
324 pub fn was_contributory(&self) -> bool {
325 !self.0.is_identity()
326 }
327}
328
329impl AsRef<[u8]> for SharedSecret {
330 /// View this shared secret key as a byte array.
331 #[inline]
332 fn as_ref(&self) -> &[u8] {
333 self.as_bytes()
334 }
335}
336
337/// The bare, byte-oriented x25519 function, exactly as specified in RFC7748.
338///
339/// This can be used with [`X25519_BASEPOINT_BYTES`] for people who
340/// cannot use the better, safer, and faster ephemeral DH API.
341///
342/// # Example
343#[cfg_attr(feature = "static_secrets", doc = "```")]
344#[cfg_attr(not(feature = "static_secrets"), doc = "```ignore")]
345/// use rand_core::OsRng;
346/// use rand_core::RngCore;
347///
348/// use x25519_dalek::x25519;
349/// use x25519_dalek::StaticSecret;
350/// use x25519_dalek::PublicKey;
351///
352/// // Generate Alice's key pair.
353/// let alice_secret = StaticSecret::random_from_rng(&mut OsRng);
354/// let alice_public = PublicKey::from(&alice_secret);
355///
356/// // Generate Bob's key pair.
357/// let bob_secret = StaticSecret::random_from_rng(&mut OsRng);
358/// let bob_public = PublicKey::from(&bob_secret);
359///
360/// // Alice and Bob should now exchange their public keys.
361///
362/// // Once they've done so, they may generate a shared secret.
363/// let alice_shared = x25519(alice_secret.to_bytes(), bob_public.to_bytes());
364/// let bob_shared = x25519(bob_secret.to_bytes(), alice_public.to_bytes());
365///
366/// assert_eq!(alice_shared, bob_shared);
367/// ```
368pub fn x25519(k: [u8; 32], u: [u8; 32]) -> [u8; 32] {
369 MontgomeryPoint(u).mul_clamped(k).to_bytes()
370}
371
372/// The X25519 basepoint, for use with the bare, byte-oriented x25519
373/// function. This is provided for people who cannot use the typed
374/// DH API for some reason.
375pub const X25519_BASEPOINT_BYTES: [u8; 32] = [
376 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
377];