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

Revamp configuration and add xchacha20-poly1305 backend #104

Merged
merged 1 commit into from
Jul 20, 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
9 changes: 7 additions & 2 deletions .cspell.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
// words - list of words to be always considered correct
"words": [
"bindgen",
"chacha",
"Codacy",
"consts",
"Ctarget",
"docsrs",
"ecies",
"eciespy",
"eciesrs",
Expand All @@ -24,12 +26,15 @@
"ssse",
"struct",
"symm",
"typenum"
"typenum",
"xchacha"
],
// 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"],
"flagWords": [
"hte"
],
"ignorePaths": [
".git",
".github",
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@ jobs:
~/.cargo/bin
target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
- name: Install wasm dep
run: cargo install wasm-bindgen-cli || true

- name: Run openssl tests
run: cargo test
- name: Run pure rust tests
run: cargo test --no-default-features --features pure

- name: Install wasm dep
run: cargo install wasm-bindgen-cli || true
- name: Run wasm tests
run: cargo test --no-default-features --features pure --target=wasm32-unknown-unknown

- name: Publish cargo package
run: cargo login $CARGO_LOGIN_TOKEN && cargo publish
env:
Expand Down
37 changes: 21 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
toolchain: [stable, beta, nightly]
feature: [pure, xchacha20, "pure,aes-12bytes-nonce"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
Expand All @@ -41,30 +42,34 @@ jobs:
target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}

- uses: actions-rs/cargo@v1
with:
command: check
if: matrix.os != 'windows-latest'
# install openssl on Windows
- run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
if: matrix.os == 'windows-latest'
- run: New-Item -ItemType "directory" -Path "C:\vcpkg\downloads" -Force
if: matrix.os == 'windows-latest'
- run: Invoke-WebRequest -URI "$env:BASE_URL/$env:FILE_NAME" -OutFile "C:\vcpkg\downloads\$env:FILE_NAME"
env:
BASE_URL: https://github.com/microsoft/vcpkg/files/12073957
FILE_NAME: nasm-2.16.01-win64.zip
if: matrix.os == 'windows-latest'
- run: vcpkg install openssl:x64-windows-static-md
if: matrix.os == 'windows-latest'

- uses: actions-rs/cargo@v1
with:
command: fmt
if: matrix.os != 'windows-latest'

- uses: actions-rs/cargo@v1
with:
command: clippy
if: matrix.os != 'windows-latest'
- name: Check and run lint
run: cargo check && cargo fmt && cargo clippy

- name: Run openssl tests
run: cargo test
if: matrix.os != 'windows-latest'
- name: Run openssl 12 bytes nonce tests
run: cargo test --features aes-12bytes-nonce

- name: Run pure rust tests
run: cargo test --no-default-features --features pure
run: cargo test --no-default-features --features ${{ matrix.feature }}

- name: Install wasm dep
run: cargo install wasm-bindgen-cli || true
- name: Run wasm tests
run: cargo test --no-default-features --features pure --target=wasm32-unknown-unknown
run: cargo test --no-default-features --features ${{ matrix.feature }} --target=wasm32-unknown-unknown

- name: Check cargo package
run: cargo publish --dry-run
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Changelog

## 0.2.1 ~ 0.2.4
## 0.2.1 ~ 0.2.5

- Revamp documentation
- Revamp configuration and add xchacha20-poly1305 backend
- Add configuration for more compatibility
- Revamp error handling
- Migrate to edition 2021
Expand Down
18 changes: 11 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ecies"
version = "0.2.4"
version = "0.2.5"
# docs
authors = ["Weiliang Li <[email protected]>"]
description = "Elliptic Curve Integrated Encryption Scheme for secp256k1 in Rust"
Expand All @@ -21,16 +21,19 @@ repository = "https://github.com/ecies/rs"

[dependencies]
hkdf = "0.12.3"
libsecp256k1 = "0.7.1"
libsecp256k1 = {version = "0.7.1", default-features = false, features = ["static-context"]}
once_cell = "1.18.0"
sha2 = "0.10.7"

# openssl aes
openssl = {version = "0.10.55", optional = true}

# pure rust aes
aes-gcm = {version = "0.10.2", optional = true}
typenum = {version = "1.16.0", optional = true}
aes-gcm = {version = "0.10.2", default-features = false, optional = true}
typenum = {version = "1.16.0", default-features = false, optional = true}

# chacha20 cipher
chacha20poly1305 = {version = "0.10.1", default-features = false, optional = true}

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = {version = "0.2.10", features = ["js"]}
Expand All @@ -41,9 +44,10 @@ rand = {version = "0.8.5"}

[features]
default = ["openssl"]
pure = ["aes-gcm", "typenum"]
# default: 16 bytes
aes_12bytes_nonce = []

aes-12bytes-nonce = [] # with feature "openssl" or "pure". default: 16 bytes
pure = ["aes-gcm/aes", "typenum"]
xchacha20 = ["chacha20poly1305/std"]

[dev-dependencies]
criterion = {version = "0.5.1", default-features = false}
Expand Down
135 changes: 76 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,44 @@ to speed up AES encryption/decryption. This would be no longer necessary when [`

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.

## Configuration

You can enable 12 bytes nonce by specify `aes-12bytes-nonce` feature.

```toml
ecies = {version = "0.2", features = ["aes-12bytes-nonce"]} # it also works for "pure"
```

You can also enable a pure Rust [XChaCha20-Poly1305](https://github.com/RustCrypto/AEADs/tree/master/chacha20poly1305) backend.

```toml
ecies = {version = "0.2", default-features = false, features = ["xchacha20"]}
```

Other behaviors can be configured by global static variable:

```rust
pub struct Config {
pub is_ephemeral_key_compressed: bool,
pub is_hkdf_key_compressed: bool
}
```

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

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)`.

```rust
use ecies::config::{Config, update_config};

update_config(Config {
is_ephemeral_key_compressed: true,
is_hkdf_key_compressed: true
});
```

For compatibility, make sure different applications share the same configuration.

## Security

### Why AES-GCM-256 and HKDF-SHA256
Expand All @@ -66,6 +104,10 @@ AEAD scheme like AES-GCM-256 should be your first option for symmetric ciphers,

For key derivation functions on shared points between two asymmetric keys, HKDFs are [proven](https://github.com/ecies/py/issues/82) to be more secure than simple hash functions like SHA256.

### Why XChaCha20-Poly1305 instead of AES-GCM-256

XChaCha20-Poly1305 is a competitive alternative to AES-256-GCM because it's fast and constant-time without hardware acceleration (resistent to cache-timing attacks). It also has longer nonce length.

### Cross-language compatibility

All functionalities are mutually checked among [different languages](https://github.com/ecies): Python, Rust, JavaScript and Golang.
Expand All @@ -74,97 +116,72 @@ All functionalities are mutually checked among [different languages](https://git

Following dependencies are audited:

- [aes-gcm](https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/)
- [aes-gcm and chacha20poly1305](https://research.nccgroup.com/2020/02/26/public-report-rustcrypto-aes-gcm-and-chacha20poly1305-implementation-review/)
- [OpenSSL](https://ostif.org/the-ostif-and-quarkslab-audit-of-openssl-is-complete/)

## Benchmark

The result shows that the pure Rust backend is around 20% ~ 50% slower compared to OpenSSL on MacBook Pro mid-2015 (2.8 GHz Quad-Core Intel Core i7).
On MacBook Pro Mid 2015 (15-inch, 2.8 GHz Quad-Core Intel Core i7) on July 19, 2023.

### OpenSSL backend
### AES backend (OpenSSL)

```bash
$ cargo bench --no-default-features --features openssl
encrypt 100M time: [110.25 ms 115.77 ms 120.22 ms]
change: [-10.123% -3.0504% +4.2342%] (p = 0.44 > 0.05)
No change in performance detected.
encrypt 100M time: [100.21 ms 100.79 ms 101.80 ms]

encrypt 200M time: [435.22 ms 450.50 ms 472.17 ms]
change: [-7.5254% +3.6572% +14.508%] (p = 0.56 > 0.05)
No change in performance detected.
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild

decrypt 100M time: [60.439 ms 66.276 ms 70.959 ms]
change: [+0.1986% +7.7620% +15.995%] (p = 0.08 > 0.05)
No change in performance detected.
encrypt 200M time: [377.84 ms 384.42 ms 390.58 ms]
Found 2 outliers among 10 measurements (20.00%)
2 (20.00%) high mild

decrypt 200M time: [182.10 ms 185.85 ms 190.63 ms]
change: [-4.8452% +5.2114% +16.370%] (p = 0.40 > 0.05)
No change in performance detected.
decrypt 100M time: [52.430 ms 55.605 ms 60.900 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high severe

decrypt 200M time: [157.87 ms 158.98 ms 160.01 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild
```

### Pure Rust backend
### AES backend (Pure Rust)

```bash
$ export RUSTFLAGS="-Ctarget-cpu=sandybridge -Ctarget-feature=+aes,+sse2,+sse4.1,+ssse3"
$ cargo bench --no-default-features --features pure
encrypt 100M time: [196.85 ms 201.97 ms 205.67 ms]
change: [-9.8235% -7.9098% -5.9849%] (p = 0.00 < 0.05)
Performance has improved.
encrypt 100M time: [196.63 ms 205.63 ms 222.25 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) low severe

encrypt 200M time: [554.62 ms 585.01 ms 599.71 ms]
change: [-15.036% -11.698% -8.6460%] (p = 0.00 < 0.05)
Performance has improved.

decrypt 100M time: [131.26 ms 134.39 ms 140.54 ms]
change: [-3.9509% +2.9485% +10.198%] (p = 0.42 > 0.05)
No change in performance detected.
1 (10.00%) high severe

decrypt 200M time: [288.13 ms 296.64 ms 311.78 ms]
change: [-16.887% -13.038% -8.6679%] (p = 0.00 < 0.05)
Performance has improved.
Benchmarking encrypt 200M: Warming up for 3.0000 s
encrypt 200M time: [587.78 ms 590.71 ms 592.46 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild
```

## Configuration

You can enable 12 bytes nonce by specify `aes_12bytes_nonce` feature.
decrypt 100M time: [144.78 ms 145.54 ms 147.17 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild

```toml
ecies = {version = "0.2", default-features = false, features = ["aes_12bytes_nonce"]}
decrypt 200M time: [363.14 ms 364.48 ms 365.74 ms]
```

Other behaviors can be configured by global static variable:

```rs
pub struct Config {
pub is_ephemeral_key_compressed: bool,
pub is_hkdf_key_compressed: bool,
pub symmetric_algorithm: SymmetricAlgorithm,
}
```
### XChaCha20 backend
```bash
$ cargo bench --no-default-features --features xchacha20
encrypt 100M time: [149.52 ms 150.06 ms 150.59 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high mild

If you set `is_ephemeral_key_compressed: true`, the payload would be like: `33 Bytes + AES` instead of `65 Bytes + AES`.
encrypt 200M time: [482.27 ms 484.95 ms 487.45 ms]
Found 3 outliers among 10 measurements (30.00%)
2 (20.00%) low severe
1 (10.00%) high severe

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)`.
decrypt 100M time: [98.232 ms 100.37 ms 105.65 ms]
Found 1 outliers among 10 measurements (10.00%)
1 (10.00%) high severe

```rs
update_config(Config {
is_ephemeral_key_compressed: true,
is_hkdf_key_compressed: true,
..Config::default()
});
decrypt 200M time: [265.62 ms 268.02 ms 269.85 ms]
```

For compatibility, make sure different applications share the same configuration.

## Changelog

See [CHANGELOG.md](./CHANGELOG.md).
Loading