Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare for configuration in the future #102

Merged
merged 1 commit into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .cspell.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
// Version of the setting file. Always 0.2
"version": "0.2",
// language - current active spelling language
"language": "en",
// words - list of words to be always considered correct
"words": [
"bindgen",
"Codacy",
"consts",
"Ctarget",
"ecies",
"eciespy",
"eciesrs",
"helloworld",
"HKDF",
"keypair",
"libsecp",
"reqwest",
"RUSTFLAGS",
"sandybridge",
"secp",
"secp256k1",
"ssse",
"symm",
"typenum"
],
// flagWords - list of words to be always considered incorrect
// This is useful for offensive words and common spelling errors.
// For example "hte" should be "the"
"flagWords": ["hte"],
"ignorePaths": [
".git",
".github",
".gitignore",
".cspell.jsonc",
"LICENSE",
"package.json",
"yarn.lock"
]
}
9 changes: 1 addition & 8 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
{
"spellright.language": ["en"],
"spellright.documentTypes": ["markdown", "latex", "plaintext", "rust", "toml"],
"rust-analyzer.cargo.features": ["pure"],
"rust-analyzer.cargo.noDefaultFeatures": true,
"rust-analyzer.procMacro.enable": true,
"spellright.ignoreFiles": [
"~/.cargo/",
"~/.rustup/",
"**/.gitignore",
"**/.spellignore"
]
"rust-analyzer.linkedProjects": ["./Cargo.toml"]
}
30 changes: 0 additions & 30 deletions .vscode/spellright.dict

This file was deleted.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ repository = "https://github.com/ecies/rs"
[dependencies]
hkdf = "0.12.3"
libsecp256k1 = "0.7.1"
once_cell = "1.18.0"
sha2 = "0.10.7"

# openssl aes
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2019-2022 Weiliang Li
Copyright (c) 2019-2023 Weiliang Li

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
55 changes: 55 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::sync::Mutex;

use once_cell::sync::Lazy;

use crate::consts::{COMPRESSED_PUBLIC_KEY_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE};

pub enum SymmetricAlgorithm {
Aes256Gcm,
}

pub struct Config {
pub is_ephemeral_key_compressed: bool,
pub is_hkdf_key_compressed: bool,
pub symmetric_algorithm: SymmetricAlgorithm,
}

impl Config {
pub fn default() -> Self {
Config {
is_ephemeral_key_compressed: false,
is_hkdf_key_compressed: false,
symmetric_algorithm: SymmetricAlgorithm::Aes256Gcm,
}
}
}

/// Global config
pub static ECIES_CONFIG: Lazy<Mutex<Config>> = Lazy::new(|| {
let config: Config = Config::default();
Mutex::new(config)
});

pub fn update_config(config: Config) {
*ECIES_CONFIG.lock().unwrap() = config;
}

pub fn reset_config() {
update_config(Config::default())
}

pub fn is_ephemeral_key_compressed() -> bool {
ECIES_CONFIG.lock().unwrap().is_ephemeral_key_compressed
}

pub fn get_ephemeral_key_size() -> usize {
if is_ephemeral_key_compressed() {
COMPRESSED_PUBLIC_KEY_SIZE
} else {
UNCOMPRESSED_PUBLIC_KEY_SIZE
}
}

pub fn is_hkdf_key_compressed() -> bool {
ECIES_CONFIG.lock().unwrap().is_hkdf_key_compressed
}
2 changes: 2 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub use libsecp256k1::util::{COMPRESSED_PUBLIC_KEY_SIZE, FULL_PUBLIC_KEY_SIZE as UNCOMPRESSED_PUBLIC_KEY_SIZE};

/// AES IV/nonce length
pub const AES_IV_LENGTH: usize = 16;
/// AES tag length
Expand Down
25 changes: 19 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
//!
//! It's also possible to build to the `wasm32-unknown-unknown` target with the pure Rust backend. Check out [this repo](https://github.com/ecies/rs-wasm) for more details.

pub use libsecp256k1::{util::FULL_PUBLIC_KEY_SIZE, Error as SecpError, PublicKey, SecretKey};
use config::{get_ephemeral_key_size, is_ephemeral_key_compressed};
pub use libsecp256k1::{Error as SecpError, PublicKey, SecretKey};

/// Constant variables
pub mod consts;
Expand All @@ -59,6 +60,9 @@ pub mod types;
/// Utility functions for ecies
pub mod utils;

// ecies configuration
pub mod config;

#[cfg(feature = "openssl")]
mod openssl_aes;
#[cfg(feature = "pure")]
Expand All @@ -79,8 +83,15 @@ pub fn encrypt(receiver_pub: &[u8], msg: &[u8]) -> Result<Vec<u8>, SecpError> {
let aes_key = encapsulate(&ephemeral_sk, &receiver_pk)?;
let encrypted = aes_encrypt(&aes_key, msg).ok_or(SecpError::InvalidMessage)?;

let mut cipher_text = Vec::with_capacity(FULL_PUBLIC_KEY_SIZE + encrypted.len());
cipher_text.extend(ephemeral_pk.serialize().iter());
let key_size = get_ephemeral_key_size();
let mut cipher_text = Vec::with_capacity(key_size + encrypted.len());

if is_ephemeral_key_compressed() {
cipher_text.extend(ephemeral_pk.serialize_compressed().iter());
} else {
cipher_text.extend(ephemeral_pk.serialize().iter());
}

cipher_text.extend(encrypted);

Ok(cipher_text)
Expand All @@ -94,13 +105,14 @@ pub fn encrypt(receiver_pub: &[u8], msg: &[u8]) -> Result<Vec<u8>, SecpError> {
/// * `msg` - The u8 array reference of the encrypted message
pub fn decrypt(receiver_sec: &[u8], msg: &[u8]) -> Result<Vec<u8>, SecpError> {
let receiver_sk = SecretKey::parse_slice(receiver_sec)?;
let key_size = get_ephemeral_key_size();

if msg.len() < FULL_PUBLIC_KEY_SIZE {
if msg.len() < key_size {
return Err(SecpError::InvalidMessage);
}

let ephemeral_pk = PublicKey::parse_slice(&msg[..FULL_PUBLIC_KEY_SIZE], None)?;
let encrypted = &msg[FULL_PUBLIC_KEY_SIZE..];
let ephemeral_pk = PublicKey::parse_slice(&msg[..key_size], None)?;
let encrypted = &msg[key_size..];

let aes_key = decapsulate(&ephemeral_pk, &receiver_sk)?;

Expand All @@ -111,6 +123,7 @@ pub fn decrypt(receiver_sec: &[u8], msg: &[u8]) -> Result<Vec<u8>, SecpError> {
mod tests {

use super::*;

use utils::generate_keypair;

const MSG: &str = "helloworld";
Expand Down
33 changes: 21 additions & 12 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use hkdf::Hkdf;
use libsecp256k1::{util::FULL_PUBLIC_KEY_SIZE, Error as SecpError, PublicKey, SecretKey};
use libsecp256k1::{Error as SecpError, PublicKey, SecretKey};
use rand::thread_rng;
use sha2::Sha256;

use crate::config::{get_ephemeral_key_size, is_hkdf_key_compressed};
use crate::consts::EMPTY_BYTES;
use crate::types::AesKey;

Expand All @@ -23,26 +24,33 @@ pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey) -> Result<AesKey, SecpEr
let mut shared_point = *peer_pk;
shared_point.tweak_mul_assign(sk)?;

let mut master = Vec::with_capacity(FULL_PUBLIC_KEY_SIZE * 2);
master.extend(PublicKey::from_secret_key(sk).serialize().iter());
master.extend(shared_point.serialize().iter());

hkdf_sha256(master.as_slice())
let pk = PublicKey::from_secret_key(sk);
derive_key(&pk, &shared_point)
}

/// Calculate a shared AES key of our public key and peer's secret key by hkdf
pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey) -> Result<AesKey, SecpError> {
let mut shared_point = *pk;
shared_point.tweak_mul_assign(peer_sk)?;

let mut master = Vec::with_capacity(FULL_PUBLIC_KEY_SIZE * 2);
master.extend(pk.serialize().iter());
master.extend(shared_point.serialize().iter());
derive_key(pk, &shared_point)
}

// private below
fn derive_key(pk: &PublicKey, shared_point: &PublicKey) -> Result<AesKey, SecpError> {
let key_size = get_ephemeral_key_size();
let mut master = Vec::with_capacity(key_size * 2);

if is_hkdf_key_compressed() {
master.extend(pk.serialize_compressed().iter());
master.extend(shared_point.serialize_compressed().iter());
} else {
master.extend(pk.serialize().iter());
master.extend(shared_point.serialize().iter());
}
hkdf_sha256(master.as_slice())
}

// private below
fn hkdf_sha256(master: &[u8]) -> Result<AesKey, SecpError> {
let h = Hkdf::<Sha256>::new(None, master);
let mut out = [0u8; 32];
Expand All @@ -53,11 +61,12 @@ fn hkdf_sha256(master: &[u8]) -> Result<AesKey, SecpError> {

#[cfg(test)]
pub(crate) mod tests {
use hex::decode;

use libsecp256k1::Error;
use rand::{thread_rng, Rng};

// dev dep
use hex::decode;

use super::*;
use crate::consts::{AES_IV_LENGTH, EMPTY_BYTES};

Expand Down
49 changes: 49 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use ecies::{
config::{reset_config, update_config, Config, SymmetricAlgorithm},
decrypt, encrypt,
utils::{decapsulate, encapsulate, generate_keypair},
PublicKey, SecretKey,
};

use hex::decode;

const MSG: &[u8] = "helloworld".as_bytes();

#[test]
fn can_change_behavior_with_config() {
let mut two = [0u8; 32];
let mut three = [0u8; 32];
two[31] = 2u8;
three[31] = 3u8;

let sk2 = SecretKey::parse_slice(&two).unwrap();
let pk2 = PublicKey::from_secret_key(&sk2);
let sk3 = SecretKey::parse_slice(&three).unwrap();
let pk3 = PublicKey::from_secret_key(&sk3);

update_config(Config {
is_ephemeral_key_compressed: false,
is_hkdf_key_compressed: true,
symmetric_algorithm: SymmetricAlgorithm::Aes256Gcm,
});

assert_eq!(encapsulate(&sk2, &pk3), decapsulate(&pk2, &sk3));

assert_eq!(
encapsulate(&sk2, &pk3).map(|v| v.to_vec()).unwrap(),
decode("b192b226edb3f02da11ef9c6ce4afe1c7e40be304e05ae3b988f4834b1cb6c69").unwrap()
);

update_config(Config {
is_ephemeral_key_compressed: true,
is_hkdf_key_compressed: true,
symmetric_algorithm: SymmetricAlgorithm::Aes256Gcm,
});

let (sk, pk) = generate_keypair();
let (sk, pk) = (&sk.serialize(), &pk.serialize_compressed());

assert_eq!(MSG, decrypt(sk, &encrypt(pk, MSG).unwrap()).unwrap().as_slice());

reset_config();
}