Skip to content

Commit 2f09195

Browse files
author
bloom
committed
chacha: Add zeroize feature and runtime detection of CPU features
1 parent 691be6b commit 2f09195

File tree

9 files changed

+107
-43
lines changed

9 files changed

+107
-43
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,5 @@ node_modules/
4848

4949
# Rust
5050
target/
51+
52+
#chacha12/Cargo.lock

Cargo.lock

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ chacha12-blake3 = "0.9"
4747
use chacha12_blake3::ChaCha12Blake3;
4848

4949
fn main() {
50-
// OD NOT USE A ALL-ZERO KEY / NONCE, THIS CODE IS FOR DEMONSTRATION ONLY
50+
// DO NOT USE A ALL-ZERO KEY / NONCE, THIS CODE IS FOR DEMONSTRATION ONLY
5151
let key = [0u8; 32];
5252
let nonce = [0u8; 32];
5353
// or with an u64 counter to encrypt up to 2^64 messages with a single key:

benchmarks/benches/chacha.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use chacha20::{ChaCha12, KeyIvInit, cipher::StreamCipher};
33
use criterion::*;
44

55
fn bench(c: &mut Criterion) {
6-
for n in [64, 2000, 64 * 1000, 1000 * 1000, 10 * 1000 * 1000] {
6+
for n in [64, 2048, 64 * 1024, 1024 * 1024, 10 * 1024 * 1024] {
77
let mut group = c.benchmark_group(format!("{}", n));
88
let mut plaintext = vec![0u8; n];
99

benchmarks/benches/encrypt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use chacha20poly1305::{ChaCha20Poly1305, KeyInit};
44
use criterion::*;
55

66
fn bench(c: &mut Criterion) {
7-
for n in [64, 2000, 64 * 1000, 1000 * 1000, 10 * 1000 * 1000] {
7+
for n in [64, 2048, 64 * 1024, 1024 * 1024, 10 * 1024 * 1024] {
88
let mut group = c.benchmark_group(format!("{}", n));
99
let mut plaintext = vec![0u8; n];
1010

chacha12/Cargo.lock

Lines changed: 0 additions & 16 deletions
This file was deleted.

chacha12/Cargo.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,21 @@ edition = "2024"
55
description = "Pure-Rust, SIMD-accelerated ChaCha20 / ChaCha12 / ChaCha8 for any platform"
66
repository = "https://github.com/bloom42/chacha12-blake3"
77
license = "MIT"
8-
keywords = ["cryptography", "chacha20", "blake3", "encryption", "chacha12", "simd", "wasm", "chacha", "webassembly"]
9-
categories = ["cryptography", "wasm"]
8+
keywords = ["cryptography", "chacha20", "encryption", "chacha12", "simd", "wasm", "chacha", "webassembly"]
9+
categories = ["cryptography", "wasm", "simd"]
10+
11+
[features]
12+
default = ["std", "zeroize"]
13+
14+
# enable runtime detection of CPU features (SIMD instructions)
15+
std = []
16+
17+
# erase sensitive secrets from memory
18+
zeroize = ["dep:zeroize"]
1019

1120
[dependencies]
21+
zeroize = { version = "1", features = ["simd", "derive"], optional = true }
22+
1223

1324
[dev-dependencies]
1425
hex = "0.4"

chacha12/README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1-
# ChaCha12
1+
# ChaCha
22

3-
Pure-Rust, SIMD-accelerated ChaCha12 for any platform.
3+
Pure-Rust, SIMD-accelerated ChaCha20 / ChaCha12 / ChaCha8 for any platform.
44

5-
Coming soon.
5+
> **⚠️ Warning ⚠️:** This is a preliminary release, DO NOT USE IN PRODUCTION.
6+
7+
8+
```rust
9+
use chacha12::ChaCha;
10+
11+
fn main() {
12+
// DO NOT USE A ALL-ZERO KEY / NONCE, THIS CODE IS FOR DEMONSTRATION ONLY
13+
let key = [0u8; 32];
14+
let nonce = [0u8; 8];
15+
16+
let mut message = b"Hello World!".to_vec();
17+
18+
let mut cipher = ChaCha::<12>::new(&key, &nonce);
19+
cipher.xor_keystream(&mut message);
20+
}
21+
```

chacha12/src/lib.rs

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
1+
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2+
13
// aarch64 assumes that NEON instructions are always present
24
#[cfg(target_arch = "aarch64")]
35
use crate::chacha_neon::chacha_neon;
46

57
#[cfg(target_arch = "aarch64")]
68
mod chacha_neon;
79

8-
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
10+
#[cfg(any(
11+
all(target_arch = "x86_64", feature = "std"),
12+
all(target_arch = "x86_64", target_feature = "avx2")
13+
))]
914
mod chacha_avx2;
1015

11-
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
16+
#[cfg(any(
17+
all(target_arch = "x86_64", feature = "std"),
18+
all(target_arch = "x86_64", target_feature = "avx2")
19+
))]
1220
use chacha_avx2::chacha_avx2;
1321

14-
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
22+
#[cfg(any(
23+
all(target_arch = "x86_64", feature = "std"),
24+
all(target_arch = "x86_64", target_feature = "avx512f")
25+
))]
1526
mod chacha_avx512;
1627

17-
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
28+
#[cfg(any(
29+
all(target_arch = "x86_64", feature = "std"),
30+
all(target_arch = "x86_64", target_feature = "avx512f")
31+
))]
1832
use chacha_avx512::chacha_avx512;
1933

2034
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
@@ -23,6 +37,9 @@ mod chacha_wasm_simd128;
2337
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
2438
use chacha_wasm_simd128::chacha_wasm_simd128;
2539

40+
#[cfg(feature = "zeroize")]
41+
use zeroize::{Zeroize, ZeroizeOnDrop};
42+
2643
const CONSTANT: [u32; 4] = [
2744
0x61707865, // "expa"
2845
0x3320646e, // "nd 3"
@@ -33,6 +50,7 @@ const CONSTANT: [u32; 4] = [
3350
/// The number of 32-bit words that compose ChaCha's state
3451
const STATE_WORDS: usize = 16;
3552

53+
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
3654
pub struct ChaCha<const ROUNDS: usize> {
3755
state: [u32; STATE_WORDS],
3856
counter: u64,
@@ -47,7 +65,8 @@ pub struct ChaCha<const ROUNDS: usize> {
4765
/// Then, when calling `xor_keystream` again, we first check if there is sone leftover form the last
4866
/// keystream.
4967
/// NOTE: the `last_keystream_block` is valid only if the previous call to `xor_keystream` had
50-
/// an input.len() % 64 != 0
68+
/// an input.len() % 64 != 0.
69+
/// Otherwise there is no need to preserve the last keystream block.
5170
last_keystream_block: [u8; 64],
5271
last_keystream_block_index: usize,
5372
}
@@ -68,19 +87,21 @@ impl<const ROUNDS: usize> ChaCha<ROUNDS> {
6887
state[14] = u32::from_le_bytes(nonce[0..4].try_into().unwrap());
6988
state[15] = u32::from_le_bytes(nonce[4..8].try_into().unwrap());
7089

71-
ChaCha {
90+
return ChaCha {
7291
state,
7392
counter: 0,
7493
last_keystream_block: [0u8; 64],
7594
last_keystream_block_index: 0,
76-
}
95+
};
7796
}
7897

98+
/// XOR `plaintext` with the ChaCha keystream.
7999
pub fn xor_keystream(&mut self, mut plaintext: &mut [u8]) {
80100
if plaintext.len() == 0 {
81101
return;
82102
}
83103

104+
// first, consume the keystream leftover, if any
84105
if self.last_keystream_block_index != 0 {
85106
let remaining_keystream = &self.last_keystream_block[self.last_keystream_block_index..];
86107

@@ -102,36 +123,63 @@ impl<const ROUNDS: usize> ChaCha<ROUNDS> {
102123
}
103124
self.last_keystream_block_index = plaintext.len() % 64;
104125

126+
// aarch64 assumes that NEON is always available
105127
#[cfg(target_arch = "aarch64")]
106128
if plaintext.len() >= 128 {
107129
self.counter = chacha_neon::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
108130
return;
109131
}
110132

111-
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))]
133+
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
112134
if plaintext.len() >= 128 {
113-
self.counter = chacha_avx512::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
135+
self.counter =
136+
chacha_wasm_simd128::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
114137
return;
115138
}
116139

117-
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))]
140+
// runtime detection of CPU features for x86 and x86_64 when the "std" feature is enabled
141+
#[cfg(feature = "std")]
142+
{
143+
#[cfg(target_arch = "x86_64")]
144+
if is_x86_feature_detected!("avx512f") {
145+
self.counter =
146+
chacha_avx512::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
147+
return;
148+
}
149+
150+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
151+
if is_x86_feature_detected!("avx2") {
152+
self.counter =
153+
chacha_avx2::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
154+
return;
155+
}
156+
}
157+
158+
// compile-time CPU detection
159+
#[cfg(all(target_arch = "x86_64", target_feature = "avx512f", not(feature = "std")))]
118160
if plaintext.len() >= 128 {
119-
self.counter = chacha_avx2::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
161+
self.counter = chacha_avx512::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
120162
return;
121163
}
122164

123-
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
165+
#[cfg(all(
166+
any(target_arch = "x86", target_arch = "x86_64"),
167+
target_feature = "avx2",
168+
not(feature = "std")
169+
))]
124170
if plaintext.len() >= 128 {
125-
self.counter =
126-
chacha_wasm_simd128::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
171+
self.counter = chacha_avx2::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
127172
return;
128173
}
129174

130175
self.counter = chacha_generic::<ROUNDS>(self.state, self.counter, plaintext, &mut self.last_keystream_block);
131176
}
132177

178+
/// Set the ChaCha counter (words 12 and 13). It can be used to move forward and backward in the
179+
/// keystream.
133180
pub fn set_counter(&mut self, counter: u64) {
134181
self.counter = counter;
182+
// setting the counter "realigns" the keystream to the beginning of a block.
135183
self.last_keystream_block_index = 0;
136184
}
137185
}
@@ -197,7 +245,9 @@ fn chacha_generic<const ROUNDS: usize>(
197245
counter = counter.wrapping_add(1);
198246
}
199247

200-
last_keystream_block.copy_from_slice(&keystream);
248+
if plaintext.len() % 64 != 0 {
249+
last_keystream_block.copy_from_slice(&keystream);
250+
}
201251

202252
return counter;
203253
}

0 commit comments

Comments
 (0)