Skip to main content

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];