Skip to content

Commit

Permalink
Prepare for configuration in the future (#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
kigawas authored Jul 12, 2023
1 parent f3ee379 commit a977ce2
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 57 deletions.
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();
}

0 comments on commit a977ce2

Please sign in to comment.