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

Add more configuration #103

Merged
merged 1 commit into from
Jul 16, 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
1 change: 1 addition & 0 deletions .cspell.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"secp",
"secp256k1",
"ssse",
"struct",
"symm",
"typenum"
],
Expand Down
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: daily
interval: monthly
open-pull-requests-limit: 10
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Changelog

## 0.2.1 ~ 0.2.4

- Add configuration for more compatibility
- Revamp error handling
- Migrate to edition 2021
- Bump dependencies

## 0.2.0

- Revamp documentation
- Optional pure Rust AES backend
- WASM compatibility

## 0.1.1 ~ 0.1.5

- Bump dependencies
- Update documentation
- Fix error handling

## 0.1.0

- First beta version release
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ rand = {version = "0.8.5"}
[features]
default = ["openssl"]
pure = ["aes-gcm", "typenum"]
# default: 16 bytes
aes_12bytes_nonce = []

[dev-dependencies]
criterion = {version = "0.5.1", default-features = false}
Expand Down
42 changes: 27 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,26 +133,38 @@ Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild
```

## Release Notes
## Configuration

### 0.2.1 ~ 0.2.4
You can enable 12 bytes nonce by specify `aes_12bytes_nonce` feature.

- Revamp error handling
- Migrate to edition 2021
- Bump dependencies
```toml
ecies = {version = "0.2", default-features = false, features = ["aes_12bytes_nonce"]}
```

Other behaviors can be configured by global static variable:

### 0.2.0
```rs
pub struct Config {
pub is_ephemeral_key_compressed: bool,
pub is_hkdf_key_compressed: bool,
pub symmetric_algorithm: SymmetricAlgorithm,
}
```

If you set `is_ephemeral_key_compressed: true`, the payload would be like: `33 Bytes + AES` instead of `65 Bytes + AES`.

- Revamp documentation
- Optional pure Rust AES backend
- WASM compatibility
If you set `is_hkdf_key_compressed: true`, the hkdf key would be derived from `ephemeral public key (compressed) + shared public key (compressed)` instead of `ephemeral public key (uncompressed) + shared public key (uncompressed)`.

### 0.1.1 ~ 0.1.5
```rs
update_config(Config {
is_ephemeral_key_compressed: true,
is_hkdf_key_compressed: true,
..Config::default()
});
```

- Bump dependencies
- Update documentation
- Fix error handling
For compatibility, make sure different applications share the same configuration.

### 0.1.0
## Changelog

- First beta version release
See [CHANGELOG.md](./CHANGELOG.md).
15 changes: 4 additions & 11 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,20 @@ use once_cell::sync::Lazy;

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

#[derive(Default)]
pub enum SymmetricAlgorithm {
#[default]
Aes256Gcm,
}

#[derive(Default)]
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
/// Global config variable
pub static ECIES_CONFIG: Lazy<Mutex<Config>> = Lazy::new(|| {
let config: Config = Config::default();
Mutex::new(config)
Expand Down
18 changes: 12 additions & 6 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
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
pub const AES_TAG_LENGTH: usize = 16;
/// AES IV + tag length
pub const AES_IV_PLUS_TAG_LENGTH: usize = AES_IV_LENGTH + AES_TAG_LENGTH;
/// AES nonce length
#[cfg(not(feature = "aes_12bytes_nonce"))]
pub const AES_NONCE_LENGTH: usize = 16;
#[cfg(feature = "aes_12bytes_nonce")]
pub const AES_NONCE_LENGTH: usize = 12;

/// AEAD tag length
pub const AEAD_TAG_LENGTH: usize = 16;

/// Nonce and tag length
pub const NONCE_TAG_LENGTH: usize = AES_NONCE_LENGTH + AEAD_TAG_LENGTH;

/// Empty bytes array
pub const EMPTY_BYTES: [u8; 0] = [];
51 changes: 0 additions & 51 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,57 +190,6 @@ mod tests {
let (sk, pk) = (&sk.serialize(), &pk.serialize_compressed());
test_enc_dec_big(sk, pk);
}

#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_against_python() {
use futures_util::FutureExt;
use hex::encode;
use tokio::runtime::Runtime;

use utils::tests::decode_hex;

const PYTHON_BACKEND: &str = "https://eciespydemo-1-d5397785.deta.app/";

let (sk, pk) = generate_keypair();

let sk_hex = encode(&sk.serialize().to_vec());
let uncompressed_pk = &pk.serialize();
let pk_hex = encode(uncompressed_pk.to_vec());

let client = reqwest::Client::new();
let params = [("data", MSG), ("pub", pk_hex.as_str())];

let rt = Runtime::new().unwrap();
let res = rt
.block_on(
client
.post(PYTHON_BACKEND)
.form(&params)
.send()
.then(|r| r.unwrap().text()),
)
.unwrap();

let server_encrypted = decode_hex(&res);
let local_decrypted = decrypt(&sk.serialize(), server_encrypted.as_slice()).unwrap();
assert_eq!(local_decrypted, MSG.as_bytes());

let local_encrypted = encrypt(uncompressed_pk, MSG.as_bytes()).unwrap();
let params = [("data", encode(local_encrypted)), ("prv", sk_hex)];

let res = rt
.block_on(
client
.post(PYTHON_BACKEND)
.form(&params)
.send()
.then(|r| r.unwrap().text()),
)
.unwrap();

assert_eq!(res, MSG);
}
}

#[cfg(all(test, target_arch = "wasm32"))]
Expand Down
16 changes: 8 additions & 8 deletions src/openssl_aes.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
use rand::{thread_rng, Rng};

use crate::consts::{AES_IV_LENGTH, AES_IV_PLUS_TAG_LENGTH, AES_TAG_LENGTH, EMPTY_BYTES};
use crate::consts::{AEAD_TAG_LENGTH, AES_NONCE_LENGTH, EMPTY_BYTES, NONCE_TAG_LENGTH};

/// AES-256-GCM encryption wrapper
pub fn aes_encrypt(key: &[u8], msg: &[u8]) -> Option<Vec<u8>> {
let cipher = Cipher::aes_256_gcm();

let mut iv = [0u8; AES_IV_LENGTH];
let mut iv = [0u8; AES_NONCE_LENGTH];
thread_rng().fill(&mut iv);

let mut tag = [0u8; AES_TAG_LENGTH];
let mut tag = [0u8; AEAD_TAG_LENGTH];

if let Ok(encrypted) = encrypt_aead(cipher, key, Some(&iv), &EMPTY_BYTES, msg, &mut tag) {
let mut output = Vec::with_capacity(AES_IV_PLUS_TAG_LENGTH + encrypted.len());
let mut output = Vec::with_capacity(NONCE_TAG_LENGTH + encrypted.len());
output.extend(&iv);
output.extend(&tag);
output.extend(encrypted);
Expand All @@ -26,15 +26,15 @@ pub fn aes_encrypt(key: &[u8], msg: &[u8]) -> Option<Vec<u8>> {

/// AES-256-GCM decryption wrapper
pub fn aes_decrypt(key: &[u8], encrypted_msg: &[u8]) -> Option<Vec<u8>> {
if encrypted_msg.len() < AES_IV_PLUS_TAG_LENGTH {
if encrypted_msg.len() < NONCE_TAG_LENGTH {
return None;
}

let cipher = Cipher::aes_256_gcm();

let iv = &encrypted_msg[..AES_IV_LENGTH];
let tag = &encrypted_msg[AES_IV_LENGTH..AES_IV_PLUS_TAG_LENGTH];
let encrypted = &encrypted_msg[AES_IV_PLUS_TAG_LENGTH..];
let iv = &encrypted_msg[..AES_NONCE_LENGTH];
let tag = &encrypted_msg[AES_NONCE_LENGTH..NONCE_TAG_LENGTH];
let encrypted = &encrypted_msg[NONCE_TAG_LENGTH..];

decrypt_aead(cipher, key, Some(iv), &EMPTY_BYTES, encrypted, tag).ok()
}
28 changes: 17 additions & 11 deletions src/pure_aes.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
use aes_gcm::aead::{generic_array::GenericArray, AeadInPlace};
use aes_gcm::{aes::Aes256, AesGcm, KeyInit};
use rand::{thread_rng, Rng};
use typenum::consts::U16;
#[allow(unused_imports)]
use typenum::consts::{U12, U16};

use crate::consts::{AES_IV_LENGTH, AES_IV_PLUS_TAG_LENGTH, EMPTY_BYTES};
use crate::consts::{AES_NONCE_LENGTH, EMPTY_BYTES, NONCE_TAG_LENGTH};

/// AES-256-GCM with 16 bytes Nonce/IV
#[cfg(not(feature = "aes_12bytes_nonce"))]
pub type Aes256Gcm = AesGcm<Aes256, U16>;

/// AES-256-GCM with 12 bytes Nonce/IV
#[cfg(feature = "aes_12bytes_nonce")]
pub type Aes256Gcm = AesGcm<Aes256, U12>;

/// AES-256-GCM encryption wrapper
pub fn aes_encrypt(key: &[u8], msg: &[u8]) -> Option<Vec<u8>> {
let key = GenericArray::from_slice(key);
let key: &GenericArray<u8, _> = GenericArray::from_slice(key);
let aead = Aes256Gcm::new(key);

let mut iv = [0u8; AES_IV_LENGTH];
let mut iv = [0u8; AES_NONCE_LENGTH];
thread_rng().fill(&mut iv);

let nonce = GenericArray::from_slice(&iv);
Expand All @@ -22,8 +28,8 @@ pub fn aes_encrypt(key: &[u8], msg: &[u8]) -> Option<Vec<u8>> {
out.extend(msg);

if let Ok(tag) = aead.encrypt_in_place_detached(nonce, &EMPTY_BYTES, &mut out) {
let mut output = Vec::with_capacity(AES_IV_PLUS_TAG_LENGTH + msg.len());
output.extend(&iv);
let mut output = Vec::with_capacity(NONCE_TAG_LENGTH + msg.len());
output.extend(nonce);
output.extend(tag);
output.extend(out);
Some(output)
Expand All @@ -34,18 +40,18 @@ pub fn aes_encrypt(key: &[u8], msg: &[u8]) -> Option<Vec<u8>> {

/// AES-256-GCM decryption wrapper
pub fn aes_decrypt(key: &[u8], encrypted_msg: &[u8]) -> Option<Vec<u8>> {
if encrypted_msg.len() < AES_IV_PLUS_TAG_LENGTH {
if encrypted_msg.len() < NONCE_TAG_LENGTH {
return None;
}

let key = GenericArray::from_slice(key);
let aead = Aes256Gcm::new(key);

let iv = GenericArray::from_slice(&encrypted_msg[..AES_IV_LENGTH]);
let tag = GenericArray::from_slice(&encrypted_msg[AES_IV_LENGTH..AES_IV_PLUS_TAG_LENGTH]);
let iv = GenericArray::from_slice(&encrypted_msg[..AES_NONCE_LENGTH]);
let tag = GenericArray::from_slice(&encrypted_msg[AES_NONCE_LENGTH..NONCE_TAG_LENGTH]);

let mut out = Vec::with_capacity(encrypted_msg.len() - AES_IV_PLUS_TAG_LENGTH);
out.extend(&encrypted_msg[AES_IV_PLUS_TAG_LENGTH..]);
let mut out = Vec::with_capacity(encrypted_msg.len() - NONCE_TAG_LENGTH);
out.extend(&encrypted_msg[NONCE_TAG_LENGTH..]);

if let Ok(_) = aead.decrypt_in_place_detached(iv, &EMPTY_BYTES, &mut out, tag) {
Some(out)
Expand Down
6 changes: 3 additions & 3 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub(crate) mod tests {
use hex::decode;

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

/// Remove 0x prefix of a hex string
pub fn remove0x(hex: &str) -> &str {
Expand Down Expand Up @@ -102,8 +102,7 @@ pub(crate) mod tests {
#[test]
fn test_attempt_to_decrypt_invalid_message() {
assert!(aes_decrypt(&[], &[]).is_none());

assert!(aes_decrypt(&[], &[0; AES_IV_LENGTH]).is_none());
assert!(aes_decrypt(&[], &[0; 16]).is_none());
}

#[test]
Expand All @@ -129,6 +128,7 @@ pub(crate) mod tests {
}

#[test]
#[cfg(not(feature = "aes_12bytes_nonce"))]
fn test_aes_known_key() {
let text = b"helloworld";
let key = decode_hex("0000000000000000000000000000000000000000000000000000000000000000");
Expand Down
Loading