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