From 7e5d7e8eec1d13baf6b445308f7528497946368f Mon Sep 17 00:00:00 2001 From: Weiliang Li Date: Sat, 4 Nov 2023 00:41:32 +0900 Subject: [PATCH] Revamp features (#113) --- Cargo.toml | 33 ++++++++++++----------- bench/simple.rs | 4 +-- src/config.rs | 15 +++++++---- src/consts.rs | 2 ++ src/elliptic/mod.rs | 55 +++++++-------------------------------- src/elliptic/secp256k1.rs | 2 +- src/elliptic/x25519.rs | 5 +++- src/lib.rs | 7 ++--- src/symmetric/hash.rs | 36 +++++++++++++++++++++++++ src/symmetric/mod.rs | 11 +++++--- tests/integration.rs | 1 + 11 files changed, 93 insertions(+), 78 deletions(-) create mode 100644 src/symmetric/hash.rs diff --git a/Cargo.toml b/Cargo.toml index f75a409..e2bccce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,11 +3,10 @@ name = "ecies" version = "0.2.7" # docs authors = ["Weiliang Li "] -description = "Elliptic Curve Integrated Encryption Scheme for secp256k1/curve25519" +description = "Elliptic Curve Integrated Encryption Scheme for secp256k1" edition = "2021" keywords = [ "secp256k1", - "curve25519", "crypto", "ecc", "ecies", @@ -22,18 +21,18 @@ repository = "https://github.com/ecies/rs" [dependencies] hkdf = {version = "0.12.3", default-features = false} -sha2 = {version = "0.10.7", default-features = false} +sha2 = {version = "0.10.8", default-features = false} # elliptic curves -libsecp256k1 = {version = "0.7.1", default-features = false, features = ["static-context"], optional = true} -x25519-dalek = {version = "2.0.0", default-features = false, features = ["static_secrets"], optional = true} +libsecp256k1 = {version = "0.7.1", default-features = false, features = ["static-context"]} +# x25519-dalek = {version = "2.0.0", default-features = false, features = ["static_secrets"], optional = true} # openssl aes openssl = {version = "0.10.57", default-features = false, optional = true} # pure rust aes -aes-gcm = {version = "0.10.2", default-features = false, optional = true} -typenum = {version = "1.16.0", default-features = false, optional = true} +aes-gcm = {version = "0.10.3", default-features = false, optional = true} +typenum = {version = "1.17.0", default-features = false, optional = true} # chacha20 cipher chacha20poly1305 = {version = "0.10.1", default-features = false, optional = true} @@ -58,17 +57,17 @@ once_cell = {version = "1.18.0", default-features = false, features = ["std"]} [features] default = ["openssl"] -std = ["hkdf/std", "sha2/std", "once_cell/std"] +std = ["hkdf/std", "sha2/std", "once_cell/std", "libsecp256k1/std"] # curve -secp256k1 = ["libsecp256k1"] -x25519 = ["x25519-dalek"] +# secp256k1 = ["libsecp256k1"] +# x25519 = ["x25519-dalek"] # cipher -aes-12bytes-nonce = ["secp256k1"] # with feature "openssl" or "pure". default: 16 bytes -openssl = ["dep:openssl", "secp256k1"] -pure = ["aes-gcm/aes", "typenum", "secp256k1"] -xchacha20 = ["chacha20poly1305", "secp256k1"] +aes-12bytes-nonce = [] # with feature "openssl" or "pure". default: 16 bytes +openssl = ["dep:openssl"] +pure = ["aes-gcm/aes", "typenum"] +xchacha20 = ["chacha20poly1305"] [dev-dependencies] criterion = {version = "0.4.0", default-features = false} @@ -78,9 +77,9 @@ hex = {version = "0.4.3", default-features = false, features = ["alloc"]} wasm-bindgen-test = "0.3.37" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] -futures-util = "0.3.28" -reqwest = "0.11.18" -tokio = {version = "1.32.0", features = ["rt-multi-thread"]} +futures-util = "0.3.29" +reqwest = "0.11.22" +tokio = {version = "1.33.0", features = ["rt-multi-thread"]} [[bench]] harness = false diff --git a/bench/simple.rs b/bench/simple.rs index 24479b9..7c824cb 100644 --- a/bench/simple.rs +++ b/bench/simple.rs @@ -1,7 +1,5 @@ use core::time::Duration; - use criterion::{criterion_group, criterion_main, Criterion}; -use ecies::{decrypt, encrypt, utils::generate_keypair}; const BIG_MSG_SIZE: usize = 100 * 1024 * 1024; const BIGGER_MSG_SIZE: usize = 200 * 1024 * 1024; @@ -10,6 +8,8 @@ const BIG_MSG: [u8; BIG_MSG_SIZE] = [1u8; BIG_MSG_SIZE]; const BIGGER_MSG: [u8; BIGGER_MSG_SIZE] = [2u8; BIGGER_MSG_SIZE]; fn criterion_benchmark(c: &mut Criterion) { + use ecies::{decrypt, encrypt, utils::generate_keypair}; + let (sk, pk) = generate_keypair(); let (sk, pk) = (&sk.serialize(), &pk.serialize()); diff --git a/src/config.rs b/src/config.rs index 0922ce0..fb0807a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,7 +30,7 @@ pub fn is_ephemeral_key_compressed() -> bool { } /// Get ephemeral key size: compressed(33) or uncompressed(65) -#[cfg(not(feature = "x25519"))] +// #[cfg(feature = "secp256k1")] pub fn get_ephemeral_key_size() -> usize { use crate::consts::{COMPRESSED_PUBLIC_KEY_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE}; @@ -41,10 +41,15 @@ pub fn get_ephemeral_key_size() -> usize { } } -#[cfg(feature = "x25519")] -pub fn get_ephemeral_key_size() -> usize { - 32 -} +// #[cfg(feature = "x25519")] +// pub fn get_ephemeral_key_size() -> usize { +// 32 +// } + +// #[cfg(all(not(feature = "x25519"), not(feature = "secp256k1")))] +// pub fn get_ephemeral_key_size() -> usize { +// panic!("Not implemented") +// } /// Get hkdf key derived from compressed shared point or not pub fn is_hkdf_key_compressed() -> bool { diff --git a/src/consts.rs b/src/consts.rs index 1226f22..6670485 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,6 +1,8 @@ /// Compressed public key size +// #[cfg(feature = "secp256k1")] pub use libsecp256k1::util::COMPRESSED_PUBLIC_KEY_SIZE; /// Uncompressed public key size +// #[cfg(feature = "secp256k1")] pub use libsecp256k1::util::FULL_PUBLIC_KEY_SIZE as UNCOMPRESSED_PUBLIC_KEY_SIZE; /// Nonce length. AES (12/16 bytes) or XChaCha20 (24 bytes) diff --git a/src/elliptic/mod.rs b/src/elliptic/mod.rs index 8192959..cfb3213 100644 --- a/src/elliptic/mod.rs +++ b/src/elliptic/mod.rs @@ -1,50 +1,13 @@ -use hkdf::Hkdf; -use sha2::Sha256; - -use crate::compat::Vec; -use crate::consts::{SharedSecret, EMPTY_BYTES}; - -#[cfg(not(feature = "x25519"))] +// #[cfg(feature = "secp256k1")] mod secp256k1; -#[cfg(not(feature = "x25519"))] +// #[cfg(feature = "secp256k1")] pub use secp256k1::{decapsulate, encapsulate, generate_keypair}; -#[cfg(not(feature = "x25519"))] +// #[cfg(feature = "secp256k1")] pub(crate) use secp256k1::{parse_pk, parse_sk, pk_to_vec, Error}; -#[cfg(feature = "x25519")] -mod x25519; -#[cfg(feature = "x25519")] -pub use x25519::{decapsulate, encapsulate, generate_keypair}; -#[cfg(feature = "x25519")] -pub(crate) use x25519::{parse_pk, parse_sk, pk_to_vec, Error}; - -fn hkdf_derive(sender_point: &[u8], shared_point: &[u8]) -> SharedSecret { - let size = sender_point.len() + shared_point.len(); - let mut master = Vec::with_capacity(size); - master.extend(sender_point); - master.extend(shared_point); - hkdf_sha256(&master) -} - -fn hkdf_sha256(master: &[u8]) -> SharedSecret { - let h = Hkdf::::new(None, master); - let mut out = [0u8; 32]; - // never fails because 32 < 255 * chunk_len, which is 32 on SHA256 - h.expand(&EMPTY_BYTES, &mut out).unwrap(); - out -} - -#[cfg(test)] -mod tests { - use super::hkdf_sha256; - - use crate::utils::tests::decode_hex; - - #[test] - fn test_known_hkdf_vector() { - assert_eq!( - hkdf_sha256(b"secret").to_vec(), - decode_hex("2f34e5ff91ec85d53ca9b543683174d0cf550b60d5f52b24c97b386cfcf6cbbf") - ); - } -} +// #[cfg(feature = "x25519")] +// mod x25519; +// #[cfg(feature = "x25519")] +// pub use x25519::{decapsulate, encapsulate, generate_keypair}; +// #[cfg(feature = "x25519")] +// pub(crate) use x25519::{parse_pk, parse_sk, pk_to_vec, Error}; diff --git a/src/elliptic/secp256k1.rs b/src/elliptic/secp256k1.rs index 433c624..b29fedf 100644 --- a/src/elliptic/secp256k1.rs +++ b/src/elliptic/secp256k1.rs @@ -1,10 +1,10 @@ use libsecp256k1::{PublicKey, SecretKey}; use rand_core::OsRng; -use super::hkdf_derive; use crate::compat::Vec; use crate::config::is_hkdf_key_compressed; use crate::consts::SharedSecret; +use crate::symmetric::hkdf_derive; pub use libsecp256k1::Error; diff --git a/src/elliptic/x25519.rs b/src/elliptic/x25519.rs index 42da67c..b8dd0ad 100644 --- a/src/elliptic/x25519.rs +++ b/src/elliptic/x25519.rs @@ -1,15 +1,18 @@ use rand_core::OsRng; use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; -use super::hkdf_derive; use crate::compat::Vec; use crate::consts::SharedSecret; +use crate::symmetric::hkdf_derive; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Error { InvalidMessage, } +#[cfg(feature = "std")] +impl std::error::Error for Error {} + /// Generate a `(SecretKey, PublicKey)` pair pub fn generate_keypair() -> (SecretKey, PublicKey) { let sk = SecretKey::random_from_rng(&mut OsRng); diff --git a/src/lib.rs b/src/lib.rs index afb3f37..741735a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,10 +7,11 @@ extern crate std; #[cfg(not(feature = "std"))] extern crate alloc; -#[cfg(not(feature = "x25519"))] +// #[cfg(all(feature = "secp256k1", not(feature = "x25519")))] pub use libsecp256k1::{PublicKey, SecretKey}; -#[cfg(feature = "x25519")] -pub use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; + +// #[cfg(all(feature = "x25519", not(feature = "secp256k1")))] +// pub use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; /// ECIES configuration pub mod config; diff --git a/src/symmetric/hash.rs b/src/symmetric/hash.rs new file mode 100644 index 0000000..7c40706 --- /dev/null +++ b/src/symmetric/hash.rs @@ -0,0 +1,36 @@ +use hkdf::Hkdf; +use sha2::Sha256; + +use crate::compat::Vec; +use crate::consts::{SharedSecret, EMPTY_BYTES}; + +pub fn hkdf_derive(sender_point: &[u8], shared_point: &[u8]) -> SharedSecret { + let size = sender_point.len() + shared_point.len(); + let mut master = Vec::with_capacity(size); + master.extend(sender_point); + master.extend(shared_point); + hkdf_sha256(&master) +} + +fn hkdf_sha256(master: &[u8]) -> SharedSecret { + let h = Hkdf::::new(None, master); + let mut out = [0u8; 32]; + // never fails because 32 < 255 * chunk_len, which is 32 on SHA256 + h.expand(&EMPTY_BYTES, &mut out).unwrap(); + out +} + +#[cfg(test)] +mod tests { + use super::hkdf_sha256; + + use crate::utils::tests::decode_hex; + + #[test] + fn test_known_vector() { + assert_eq!( + hkdf_sha256(b"secret").to_vec(), + decode_hex("2f34e5ff91ec85d53ca9b543683174d0cf550b60d5f52b24c97b386cfcf6cbbf") + ); + } +} diff --git a/src/symmetric/mod.rs b/src/symmetric/mod.rs index 5b08a5f..4c010a0 100644 --- a/src/symmetric/mod.rs +++ b/src/symmetric/mod.rs @@ -5,14 +5,19 @@ use crate::consts::NONCE_LENGTH; #[cfg(any(feature = "pure", feature = "xchacha20"))] mod aead; -#[cfg(feature = "openssl")] -mod openssl_aes; - #[cfg(any(feature = "pure", feature = "xchacha20"))] use aead::{decrypt, encrypt}; + +#[cfg(feature = "openssl")] +mod openssl_aes; #[cfg(feature = "openssl")] use openssl_aes::{decrypt, encrypt}; +// #[cfg(any(feature = "secp256k1", feature = "x25519"))] +mod hash; +// #[cfg(any(feature = "secp256k1", feature = "x25519"))] +pub(crate) use hash::hkdf_derive; + /// Symmetric encryption wrapper. Openssl AES-256-GCM, pure Rust AES-256-GCM, or XChaCha20-Poly1305 /// Nonces are generated randomly. /// diff --git a/tests/integration.rs b/tests/integration.rs index 68ea845..76e5837 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -4,6 +4,7 @@ not(feature = "x25519"), not(feature = "aes-12bytes-nonce"), not(feature = "xchacha20"), + // feature = "secp256k1" ))] fn is_compatible_with_python() { use futures_util::FutureExt;