Skip to content

Commit

Permalink
Add more configuration (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
kigawas authored Jul 16, 2023
1 parent a977ce2 commit 875d8b5
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 113 deletions.
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

0 comments on commit 875d8b5

Please sign in to comment.