From 9fe535dcc7fcc2c1c8e6546efc978c0bd190c07e Mon Sep 17 00:00:00 2001 From: DebugSteven Date: Sat, 16 Feb 2019 08:50:20 -0700 Subject: [PATCH 1/8] rename EphemeralPublic to PublicKey --- src/x25519.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/x25519.rs b/src/x25519.rs index f3c6870..fa5e889 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -23,18 +23,18 @@ use curve25519_dalek::scalar::Scalar; use rand_core::RngCore; use rand_core::CryptoRng; -/// A DH ephemeral public key. -pub struct EphemeralPublic(pub (crate) MontgomeryPoint); +/// A DH public key. +pub struct PublicKey(pub (crate) MontgomeryPoint); -impl From<[u8; 32]> for EphemeralPublic { - /// Given a byte array, construct an x25519 `EphemeralPublic` key - fn from(bytes: [u8; 32]) -> EphemeralPublic { - EphemeralPublic(MontgomeryPoint(bytes)) +impl From<[u8; 32]> for PublicKey { + /// Given a byte array, construct a x25519 `PublicKey`. + fn from(bytes: [u8; 32]) -> PublicKey { + PublicKey(MontgomeryPoint(bytes)) } } -impl EphemeralPublic { - /// View this ephemeral public key as a byte array. +impl PublicKey { + /// View this public key as a byte array. #[inline] pub fn as_bytes(&self) -> &[u8; 32] { self.0.as_bytes() @@ -55,7 +55,7 @@ impl EphemeralSecret { /// Utility function to make it easier to call `x25519()` with /// an ephemeral secret key and montegomery point as input and /// a shared secret as the output. - pub fn diffie_hellman(self, their_public: &EphemeralPublic) -> SharedSecret { + pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret { SharedSecret(self.0 * their_public.0) } @@ -72,11 +72,11 @@ impl EphemeralSecret { } -impl<'a> From<&'a EphemeralSecret> for EphemeralPublic { +impl<'a> From<&'a EphemeralSecret> for PublicKey { /// Given an x25519 `EphemeralSecret` key, compute its corresponding - /// `EphemeralPublic` key. - fn from(secret: &'a EphemeralSecret) -> EphemeralPublic { - EphemeralPublic((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) + /// `PublicKey` key. + fn from(secret: &'a EphemeralSecret) -> PublicKey { + PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) } } From 4caffdcca01d8fdbfa6d440ede698788ee87163c Mon Sep 17 00:00:00 2001 From: DebugSteven Date: Sat, 16 Feb 2019 09:14:43 -0700 Subject: [PATCH 2/8] impl StaticSecret & create DH method that borrows our StaticSecret --- src/x25519.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/x25519.rs b/src/x25519.rs index fa5e889..64e2c0e 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -81,6 +81,46 @@ impl<'a> From<&'a EphemeralSecret> for PublicKey { } +/// A DH static secret key. +pub struct StaticSecret(pub (crate) Scalar); + +/// Overwrite static secret key material with null bytes when it goes out of scope. +impl Drop for StaticSecret { + fn drop(&mut self) { + self.0.clear(); + } +} + +impl StaticSecret { + /// Utility function to make it easier to call `x25519()` with + /// a static secret key and montegomery point as input and + /// a shared secret as the output. + pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret { + SharedSecret(&self.0 * their_public.0) + } + + /// Generate a x25519 `StaticSecret` key. + pub fn new(csprng: &mut T) -> Self + where T: RngCore + CryptoRng + { + let mut bytes = [0u8; 32]; + + csprng.fill_bytes(&mut bytes); + + StaticSecret(clamp_scalar(bytes)) + } + +} + +impl<'a> From<&'a StaticSecret> for PublicKey { + /// Given an x25519 `StaticSecret` key, compute its corresponding + /// `PublicKey` key. + fn from(secret: &'a StaticSecret) -> PublicKey { + PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) + } + +} + /// A DH SharedSecret pub struct SharedSecret(pub (crate) MontgomeryPoint); From ad670a4d03bdc95cb34a31dd60e8137c98150787 Mon Sep 17 00:00:00 2001 From: DebugSteven Date: Sat, 16 Feb 2019 09:42:56 -0700 Subject: [PATCH 3/8] methods to convert a StaticSecret to and from bytes --- src/x25519.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/x25519.rs b/src/x25519.rs index 64e2c0e..2f2a87e 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -110,6 +110,18 @@ impl StaticSecret { StaticSecret(clamp_scalar(bytes)) } + /// Convert a x25519 `StaticSecret` key to its underlying sequence of bytes. + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + +} + +impl From<[u8; 32]> for StaticSecret { + /// Given a byte array, construct a x25519 `StaticSecret`. + fn from(bytes: [u8; 32]) -> StaticSecret { + StaticSecret(Scalar::from_bits(bytes)) + } } impl<'a> From<&'a StaticSecret> for PublicKey { From 29edaa17ac74d151a97209f9045726de5bd3282a Mon Sep 17 00:00:00 2001 From: DebugSteven Date: Sat, 16 Feb 2019 10:22:06 -0700 Subject: [PATCH 4/8] informative doc comments --- src/x25519.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/x25519.rs b/src/x25519.rs index 2f2a87e..bbac39a 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -52,9 +52,8 @@ impl Drop for EphemeralSecret { } impl EphemeralSecret { - /// Utility function to make it easier to call `x25519()` with - /// an ephemeral secret key and montegomery point as input and - /// a shared secret as the output. + /// Perform a Diffie-Hellman key agreement between `self` and + /// `their_public` key to produce a `SharedSecret`. pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret { SharedSecret(self.0 * their_public.0) } @@ -81,7 +80,9 @@ impl<'a> From<&'a EphemeralSecret> for PublicKey { } -/// A DH static secret key. +/// A static secret key for Diffie-Hellman. Unlike an EphemeralSecret, this key +/// does not enforce that it's used only once, and can be saved and loaded from +/// a byte array. pub struct StaticSecret(pub (crate) Scalar); /// Overwrite static secret key material with null bytes when it goes out of scope. @@ -92,9 +93,8 @@ impl Drop for StaticSecret { } impl StaticSecret { - /// Utility function to make it easier to call `x25519()` with - /// a static secret key and montegomery point as input and - /// a shared secret as the output. + /// Perform a Diffie-Hellman key agreement between `self` and + /// `their_public` key to produce a `SharedSecret`. pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret { SharedSecret(&self.0 * their_public.0) } @@ -110,7 +110,7 @@ impl StaticSecret { StaticSecret(clamp_scalar(bytes)) } - /// Convert a x25519 `StaticSecret` key to its underlying sequence of bytes. + /// Save a x25519 `StaticSecret` key's bytes. pub fn to_bytes(&self) -> [u8; 32] { self.0.to_bytes() } @@ -118,7 +118,7 @@ impl StaticSecret { } impl From<[u8; 32]> for StaticSecret { - /// Given a byte array, construct a x25519 `StaticSecret`. + /// Load a `StaticSecret` from a byte array. fn from(bytes: [u8; 32]) -> StaticSecret { StaticSecret(Scalar::from_bits(bytes)) } @@ -172,14 +172,14 @@ fn clamp_scalar(scalar: [u8; 32]) -> Scalar { /// The bare, byte-oriented x25519 function, exactly as specified in RFC7748. /// /// This can be used with [`X25519_BASEPOINT_BYTES`] for people who -/// cannot use the better, safer, and faster ephemeral DH API. +/// cannot use the better, safer, and faster DH API. pub fn x25519(k: [u8; 32], u: [u8; 32]) -> [u8; 32] { (clamp_scalar(k) * MontgomeryPoint(u)).to_bytes() } /// The X25519 basepoint, for use with the bare, byte-oriented x25519 /// function. This is provided for people who cannot use the typed -/// ephemeral DH API for some reason. +/// DH API for some reason. pub const X25519_BASEPOINT_BYTES: [u8; 32] = [ 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, ]; From 6797c0969fbb9573a2a2599c783b1573ddd672c3 Mon Sep 17 00:00:00 2001 From: DebugSteven Date: Sat, 16 Feb 2019 10:57:42 -0700 Subject: [PATCH 5/8] summarize how types are used & how they relate to one another --- src/x25519.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/x25519.rs b/src/x25519.rs index bbac39a..f788207 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -23,7 +23,8 @@ use curve25519_dalek::scalar::Scalar; use rand_core::RngCore; use rand_core::CryptoRng; -/// A DH public key. +/// A `PublicKey` is the corresponding public key converted from +/// an `EphemeralSecret` or a `StaticSecret` key. pub struct PublicKey(pub (crate) MontgomeryPoint); impl From<[u8; 32]> for PublicKey { @@ -41,7 +42,8 @@ impl PublicKey { } } -/// A DH ephemeral secret key. +/// A `EphemeralSecret` is a short lived Diffie-Hellman secret key +/// used to create a `SharedSecret` when given their `PublicKey`. pub struct EphemeralSecret(pub (crate) Scalar); /// Overwrite ephemeral secret key material with null bytes when it goes out of scope. @@ -80,9 +82,9 @@ impl<'a> From<&'a EphemeralSecret> for PublicKey { } -/// A static secret key for Diffie-Hellman. Unlike an EphemeralSecret, this key -/// does not enforce that it's used only once, and can be saved and loaded from -/// a byte array. +/// A `StaticSecret` is a static Diffie-Hellman secret key that +/// can be saved and loaded to create a `SharedSecret` when given +/// their `PublicKey`. pub struct StaticSecret(pub (crate) Scalar); /// Overwrite static secret key material with null bytes when it goes out of scope. @@ -133,7 +135,8 @@ impl<'a> From<&'a StaticSecret> for PublicKey { } -/// A DH SharedSecret +/// A `SharedSecret` is a Diffie-Hellman shared secret that’s generated +/// from your `EphemeralSecret` or `StaticSecret` and their `PublicKey`. pub struct SharedSecret(pub (crate) MontgomeryPoint); /// Overwrite shared secret material with null bytes when it goes out of scope. From e25c72b4761b6f3c07b39d97f22f5bd6f0bdd022 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sat, 16 Feb 2019 10:11:27 -0800 Subject: [PATCH 6/8] Update README tests and ensure they're run in Travis. --- .travis.yml | 1 + README.md | 95 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index a5b5c1f..eceb10e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ rust: env: - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='default' + - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='nightly' - TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='default' - TEST_COMMAND=build EXTRA_FLAGS='--no-default-features' FEATURES='u32_backend nightly' - TEST_COMMAND=build EXTRA_FLAGS='--no-default-features' FEATURES='u64_backend nightly' diff --git a/README.md b/README.md index d1cbb4c..f435094 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ with curve operations provided by This crate provides two levels of API: a bare byte-oriented `x25519` function which matches the function specified in [RFC7748][rfc7748], as -well as a higher-level Rust API for ephemeral Diffie-Hellman. +well as a higher-level Rust API for static and ephemeral Diffie-Hellman. ## Examples @@ -29,50 +29,111 @@ kittens will be able to secretly organise to find their mittens, and then spend the rest of the afternoon nomming some yummy pie! First, Alice uses `EphemeralSecret::new()` and then -`EphemeralPublic::from()` to produce her secret and public keys: +`PublicKey::from()` to produce her secret and public keys: ```rust -extern crate x25519_dalek; extern crate rand_os; - -use x25519_dalek::EphemeralPublic; -use x25519_dalek::EphemeralSecret; use rand_os::OsRng; +extern crate x25519_dalek; +use x25519_dalek::EphemeralSecret; +use x25519_dalek::PublicKey; + +# fn main() { let mut alice_csprng = OsRng::new().unwrap(); let alice_secret = EphemeralSecret::new(&mut alice_csprng); -let alice_public = EphemeralPublic::from(&alice_secret); +let alice_public = PublicKey::from(&alice_secret); +# } ``` Bob does the same: -```rust,ignore +```rust +# extern crate rand_os; +# use rand_os::OsRng; +# +# extern crate x25519_dalek; +# use x25519_dalek::EphemeralSecret; +# use x25519_dalek::PublicKey; +# fn main() { let mut bob_csprng = OsRng::new().unwrap(); let bob_secret = EphemeralSecret::new(&mut bob_csprng); -let bob_public = EphemeralPublic::from(&bob_secret); +let bob_public = PublicKey::from(&bob_secret); +# } ``` Alice meows across the room, telling `alice_public` to Bob, and Bob loudly meows `bob_public` back to Alice. Alice now computes her shared secret with Bob by doing: -```rust,ignore -use x25519_dalek::EphemeralPublic; -use x25519_dalek::EphemeralSecret; - -let shared_secret = EphemeralSecret::diffie_hellman(alice_secret, &bob_public); +```rust +# extern crate rand_os; +# use rand_os::OsRng; +# +# extern crate x25519_dalek; +# use x25519_dalek::EphemeralSecret; +# use x25519_dalek::PublicKey; +# +# fn main() { +# let mut csprng = OsRng::new().unwrap(); +# let alice_secret = EphemeralSecret::new(&mut csprng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(&mut csprng); +# let bob_public = PublicKey::from(&bob_secret); +let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); +# } ``` -Similarly, Bob computes the same shared secret by doing: +Similarly, Bob computes a shared secret by doing: -```rust,ignore -let shared_secret = EphemeralSecret::diffie_hellman(bob_secret, &alice_public); +```rust +# extern crate rand_os; +# use rand_os::OsRng; +# +# extern crate x25519_dalek; +# use x25519_dalek::EphemeralSecret; +# use x25519_dalek::PublicKey; +# +# fn main() { +# let mut csprng = OsRng::new().unwrap(); +# let alice_secret = EphemeralSecret::new(&mut csprng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(&mut csprng); +# let bob_public = PublicKey::from(&bob_secret); +let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); +# } +``` + +These secrets are the same: + +```rust +# extern crate rand_os; +# use rand_os::OsRng; +# +# extern crate x25519_dalek; +# use x25519_dalek::EphemeralSecret; +# use x25519_dalek::PublicKey; +# +# fn main() { +# let mut csprng = OsRng::new().unwrap(); +# let alice_secret = EphemeralSecret::new(&mut csprng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(&mut csprng); +# let bob_public = PublicKey::from(&bob_secret); +# let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); +# let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); +assert_eq!(alice_shared_secret.as_bytes(), bob_shared_secret.as_bytes()); +# } ``` Voilá! Alice and Bob can now use their shared secret to encrypt their meows, for example, by using it to generate a key and nonce for an authenticated-encryption cipher. +This example used the ephemeral DH API, which ensures that secret keys +cannot be reused; Alice and Bob could instead use the static DH API +and load a long-term secret key. + # Installation To install, add the following to your project's `Cargo.toml`: From bcea24308fdb6e5b8dc50d630b716b4aa0980c8b Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sat, 16 Feb 2019 10:17:15 -0800 Subject: [PATCH 7/8] Fix benchmark --- benches/x25519.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/x25519.rs b/benches/x25519.rs index 4660a69..cfded70 100644 --- a/benches/x25519.rs +++ b/benches/x25519.rs @@ -23,13 +23,13 @@ use curve25519_dalek::montgomery::MontgomeryPoint; use rand_os::OsRng; -use x25519_dalek::EphemeralPublic; +use x25519_dalek::PublicKey; use x25519_dalek::EphemeralSecret; fn bench_diffie_hellman(c: &mut Criterion) { let mut csprng: OsRng = OsRng::new().unwrap(); - let bob_secret: EphemeralSecret = EphemeralSecret::new(&mut csprng); - let bob_public: EphemeralPublic = EphemeralPublic::from(&bob_secret); + let bob_secret = EphemeralSecret::new(&mut csprng); + let bob_public = PublicKey::from(&bob_secret); c.bench_function("diffie_hellman", move |b| { b.iter_with_setup( From aee783ae82c5b86b77c83b3d1c3178a2e3fbdeb1 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Sat, 16 Feb 2019 16:31:05 -0500 Subject: [PATCH 8/8] use clamp_scalar for StaticSecret This ensures that the `StaticSecret`'s scalar always has the X25519 bit-twiddles applied. Co-Authored-By: DebugSteven --- src/x25519.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/x25519.rs b/src/x25519.rs index f788207..511a9b5 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -122,7 +122,7 @@ impl StaticSecret { impl From<[u8; 32]> for StaticSecret { /// Load a `StaticSecret` from a byte array. fn from(bytes: [u8; 32]) -> StaticSecret { - StaticSecret(Scalar::from_bits(bytes)) + StaticSecret(clamp_scalar(bytes)) } }