chacha20blake3 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +11 -13
- data/ext/chacha20blake3/Cargo.toml +1 -1
- data/ext/chacha20blake3/src/lib.rs +161 -138
- data/lib/chacha20blake3/version.rb +1 -1
- data/tmp/x86_64-linux/stage/ext/chacha20blake3/Cargo.toml +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bc223f7d13a7c25813924f8de95fd9b98e335a76ed9781fa434a0884d0bfd3a
|
|
4
|
+
data.tar.gz: 43789cf6dbed44f5347a51812d1e181c27d45e91124e59c1c14f24f9c778dfec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b94fd99f4c90238517f12e816d8a34fb620746674aab9f5d942258ac7e3c3769b3fe5b44f3282c6d29bafb24cab7ba10e63525c2769ddd6cd3f658fd058671d
|
|
7
|
+
data.tar.gz: bbeeee226c47688e6398fab4bfc3b17025d2629d3e8e54a9e52f013b751be2be377b8ddd996b730cab208249cf0c94770cf067c92a36e35ecbbf90e54ce8869e
|
data/Cargo.lock
CHANGED
|
@@ -60,7 +60,7 @@ dependencies = [
|
|
|
60
60
|
"arrayvec",
|
|
61
61
|
"cc",
|
|
62
62
|
"cfg-if",
|
|
63
|
-
"constant_time_eq",
|
|
63
|
+
"constant_time_eq 0.4.2",
|
|
64
64
|
"cpufeatures",
|
|
65
65
|
"zeroize",
|
|
66
66
|
]
|
|
@@ -90,22 +90,14 @@ version = "1.0.4"
|
|
|
90
90
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
91
91
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
92
92
|
|
|
93
|
-
[[package]]
|
|
94
|
-
name = "chacha"
|
|
95
|
-
version = "0.1.0"
|
|
96
|
-
source = "git+https://github.com/skerkour/chacha20-blake3?rev=9942bf34c77b03896bb1733079256076ca823e58#9942bf34c77b03896bb1733079256076ca823e58"
|
|
97
|
-
dependencies = [
|
|
98
|
-
"zeroize",
|
|
99
|
-
]
|
|
100
|
-
|
|
101
93
|
[[package]]
|
|
102
94
|
name = "chacha20-blake3"
|
|
103
|
-
version = "0.
|
|
104
|
-
source = "
|
|
95
|
+
version = "0.10.0"
|
|
96
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
97
|
+
checksum = "55d7729707a8889ea911e9c775c5af9eba82e98ed1588e18b0ec7ae3a51c71bd"
|
|
105
98
|
dependencies = [
|
|
106
99
|
"blake3",
|
|
107
|
-
"
|
|
108
|
-
"constant_time_eq",
|
|
100
|
+
"constant_time_eq 0.5.0",
|
|
109
101
|
"zeroize",
|
|
110
102
|
]
|
|
111
103
|
|
|
@@ -137,6 +129,12 @@ version = "0.4.2"
|
|
|
137
129
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
138
130
|
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
|
139
131
|
|
|
132
|
+
[[package]]
|
|
133
|
+
name = "constant_time_eq"
|
|
134
|
+
version = "0.5.0"
|
|
135
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
136
|
+
checksum = "1a1ec0cfec728a79a5109075543131387f911cb4d07716436d7ae20533657a96"
|
|
137
|
+
|
|
140
138
|
[[package]]
|
|
141
139
|
name = "cpufeatures"
|
|
142
140
|
version = "0.3.0"
|
|
@@ -8,7 +8,7 @@ name = "chacha20blake3"
|
|
|
8
8
|
crate-type = ["cdylib", "rlib"]
|
|
9
9
|
|
|
10
10
|
[dependencies]
|
|
11
|
-
chacha20-blake3 =
|
|
11
|
+
chacha20-blake3 = "0.10.0"
|
|
12
12
|
blake3 = { version = "1", features = ["std"] }
|
|
13
13
|
magnus = "0.8"
|
|
14
14
|
getrandom = "0.2"
|
|
@@ -13,9 +13,9 @@ use std::sync::{Mutex, OnceLock};
|
|
|
13
13
|
|
|
14
14
|
const KEY_SIZE: usize = 32;
|
|
15
15
|
const NONCE_SIZE: usize = 24;
|
|
16
|
+
const SESSION_NONCE_SIZE: usize = 8;
|
|
16
17
|
const TAG_SIZE: usize = 32;
|
|
17
18
|
|
|
18
|
-
// Opaque<T> is Send+Sync and is designed for storing Ruby values in statics.
|
|
19
19
|
static DECRYPTION_ERROR: OnceLock<Opaque<ExceptionClass>> = OnceLock::new();
|
|
20
20
|
|
|
21
21
|
fn decryption_error(ruby: &Ruby) -> ExceptionClass {
|
|
@@ -29,27 +29,14 @@ struct Cipher(chacha20_blake3::ChaCha20Blake3);
|
|
|
29
29
|
unsafe impl Send for Cipher {}
|
|
30
30
|
unsafe impl Sync for Cipher {}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
struct Stream {
|
|
36
|
-
cipher: chacha20_blake3::ChaCha20Blake3,
|
|
37
|
-
nonce_prefix: [u8; 16],
|
|
38
|
-
counter_base: u64,
|
|
39
|
-
counter: Mutex<u64>,
|
|
32
|
+
#[magnus::wrap(class = "ChaCha20Blake3::Session", free_immediately, size)]
|
|
33
|
+
struct Session {
|
|
34
|
+
inner: Mutex<chacha20_blake3::Session20>,
|
|
40
35
|
}
|
|
41
36
|
|
|
42
|
-
// Safety:
|
|
43
|
-
|
|
44
|
-
unsafe impl
|
|
45
|
-
unsafe impl Sync for Stream {}
|
|
46
|
-
|
|
47
|
-
fn nonce_for_counter(s: &Stream, counter: u64) -> [u8; 24] {
|
|
48
|
-
let mut nonce = [0u8; 24];
|
|
49
|
-
nonce[..16].copy_from_slice(&s.nonce_prefix);
|
|
50
|
-
nonce[16..].copy_from_slice(&s.counter_base.wrapping_add(counter).to_le_bytes());
|
|
51
|
-
nonce
|
|
52
|
-
}
|
|
37
|
+
// Safety: Mutex<Session20> provides Send+Sync via interior synchronization.
|
|
38
|
+
unsafe impl Send for Session {}
|
|
39
|
+
unsafe impl Sync for Session {}
|
|
53
40
|
|
|
54
41
|
fn validate_key(ruby: &Ruby, key: &[u8]) -> Result<[u8; KEY_SIZE], Error> {
|
|
55
42
|
if key.len() != KEY_SIZE {
|
|
@@ -71,6 +58,19 @@ fn validate_nonce(ruby: &Ruby, nonce: &[u8]) -> Result<[u8; NONCE_SIZE], Error>
|
|
|
71
58
|
Ok(nonce.try_into().unwrap())
|
|
72
59
|
}
|
|
73
60
|
|
|
61
|
+
fn validate_session_nonce(ruby: &Ruby, nonce: &[u8]) -> Result<[u8; SESSION_NONCE_SIZE], Error> {
|
|
62
|
+
if nonce.len() != SESSION_NONCE_SIZE {
|
|
63
|
+
return Err(Error::new(
|
|
64
|
+
ruby.exception_arg_error(),
|
|
65
|
+
format!(
|
|
66
|
+
"session nonce must be exactly {SESSION_NONCE_SIZE} bytes, got {}",
|
|
67
|
+
nonce.len()
|
|
68
|
+
),
|
|
69
|
+
));
|
|
70
|
+
}
|
|
71
|
+
Ok(nonce.try_into().unwrap())
|
|
72
|
+
}
|
|
73
|
+
|
|
74
74
|
fn validate_tag(ruby: &Ruby, tag: &[u8]) -> Result<[u8; TAG_SIZE], Error> {
|
|
75
75
|
if tag.len() != TAG_SIZE {
|
|
76
76
|
return Err(Error::new(
|
|
@@ -222,46 +222,41 @@ fn cipher_decrypt_detached(
|
|
|
222
222
|
Ok(output)
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
fn
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
fn session_initialize(
|
|
226
|
+
ruby: &Ruby,
|
|
227
|
+
rb_enc_key: RString,
|
|
228
|
+
rb_auth_key: RString,
|
|
229
|
+
rb_nonce: RString,
|
|
230
|
+
) -> Result<Session, Error> {
|
|
231
|
+
let (enc_key, auth_key, nonce) = unsafe {
|
|
232
|
+
(
|
|
233
|
+
validate_key(ruby, rb_enc_key.as_slice())?,
|
|
234
|
+
validate_key(ruby, rb_auth_key.as_slice())?,
|
|
235
|
+
validate_session_nonce(ruby, rb_nonce.as_slice())?,
|
|
236
|
+
)
|
|
229
237
|
};
|
|
230
|
-
|
|
238
|
+
rb_enc_key.freeze();
|
|
239
|
+
rb_auth_key.freeze();
|
|
231
240
|
rb_nonce.freeze();
|
|
232
|
-
Ok(
|
|
233
|
-
|
|
234
|
-
nonce_prefix: nonce_arr[..16].try_into().unwrap(),
|
|
235
|
-
counter_base: u64::from_le_bytes(nonce_arr[16..].try_into().unwrap()),
|
|
236
|
-
counter: Mutex::new(0),
|
|
241
|
+
Ok(Session {
|
|
242
|
+
inner: Mutex::new(chacha20_blake3::Session20::new(enc_key, auth_key, nonce)),
|
|
237
243
|
})
|
|
238
244
|
}
|
|
239
245
|
|
|
240
|
-
fn
|
|
246
|
+
fn session_encrypt(ruby: &Ruby, rb_self: &Session, args: &[Value]) -> Result<RString, Error> {
|
|
241
247
|
let parsed = scan_args::<(RString,), (), (), (), RHash, ()>(args)?;
|
|
242
248
|
let (rb_plaintext,) = parsed.required;
|
|
243
249
|
let kw = get_kwargs::<_, (), (Option<RString>,), ()>(parsed.keywords, &[], &["aad"])?;
|
|
244
250
|
let (opt_aad,) = kw.optional;
|
|
245
251
|
|
|
246
|
-
|
|
247
|
-
// with the same nonce.
|
|
248
|
-
let mut counter = rb_self.counter.lock().unwrap();
|
|
252
|
+
let mut session = rb_self.inner.lock().unwrap();
|
|
249
253
|
|
|
250
254
|
let (buf, tag) = unsafe {
|
|
251
|
-
let nonce = nonce_for_counter(rb_self, *counter);
|
|
252
255
|
let mut buf = rb_plaintext.as_slice().to_vec();
|
|
253
256
|
let aad = opt_aad.as_ref().map_or_else(Vec::new, |s| s.as_slice().to_vec());
|
|
254
|
-
let tag =
|
|
257
|
+
let tag = session.encrypt_in_place_detached(&mut buf, &aad);
|
|
255
258
|
(buf, tag)
|
|
256
259
|
};
|
|
257
|
-
// Advance counter. Overflow would reuse the initial nonce, which is
|
|
258
|
-
// catastrophic for a stream cipher.
|
|
259
|
-
*counter = counter.checked_add(1).ok_or_else(|| {
|
|
260
|
-
Error::new(
|
|
261
|
-
ruby.exception_runtime_error(),
|
|
262
|
-
"stream counter exhausted (2^64 messages); create a new Stream to continue",
|
|
263
|
-
)
|
|
264
|
-
})?;
|
|
265
260
|
|
|
266
261
|
let output = ruby.str_buf_new(buf.len() + TAG_SIZE);
|
|
267
262
|
output.cat(&buf);
|
|
@@ -269,18 +264,15 @@ fn stream_encrypt(ruby: &Ruby, rb_self: &Stream, args: &[Value]) -> Result<RStri
|
|
|
269
264
|
Ok(output)
|
|
270
265
|
}
|
|
271
266
|
|
|
272
|
-
fn
|
|
267
|
+
fn session_decrypt(ruby: &Ruby, rb_self: &Session, args: &[Value]) -> Result<RString, Error> {
|
|
273
268
|
let parsed = scan_args::<(RString,), (), (), (), RHash, ()>(args)?;
|
|
274
269
|
let (rb_ciphertext,) = parsed.required;
|
|
275
270
|
let kw = get_kwargs::<_, (), (Option<RString>,), ()>(parsed.keywords, &[], &["aad"])?;
|
|
276
271
|
let (opt_aad,) = kw.optional;
|
|
277
272
|
|
|
278
|
-
|
|
279
|
-
// after successful authentication.
|
|
280
|
-
let mut counter = rb_self.counter.lock().unwrap();
|
|
273
|
+
let mut session = rb_self.inner.lock().unwrap();
|
|
281
274
|
|
|
282
275
|
let buf = unsafe {
|
|
283
|
-
let nonce = nonce_for_counter(rb_self, *counter);
|
|
284
276
|
let mut buf = rb_ciphertext.as_slice().to_vec();
|
|
285
277
|
let aad = opt_aad.as_ref().map_or_else(Vec::new, |s| s.as_slice().to_vec());
|
|
286
278
|
if buf.len() < TAG_SIZE {
|
|
@@ -289,27 +281,54 @@ fn stream_decrypt(ruby: &Ruby, rb_self: &Stream, args: &[Value]) -> Result<RStri
|
|
|
289
281
|
let tag_start = buf.len() - TAG_SIZE;
|
|
290
282
|
let tag: [u8; TAG_SIZE] = buf[tag_start..].try_into().unwrap();
|
|
291
283
|
buf.truncate(tag_start);
|
|
292
|
-
|
|
284
|
+
session
|
|
285
|
+
.decrypt_in_place_detached(&mut buf, &tag, &aad)
|
|
293
286
|
.map_err(|_| Error::new(decryption_error(ruby), "decryption failed"))?;
|
|
294
287
|
buf
|
|
295
288
|
};
|
|
296
|
-
// Advance counter only after successful MAC verification.
|
|
297
|
-
*counter = counter.checked_add(1).ok_or_else(|| {
|
|
298
|
-
Error::new(
|
|
299
|
-
ruby.exception_runtime_error(),
|
|
300
|
-
"stream counter exhausted (2^64 messages); create a new Stream to continue",
|
|
301
|
-
)
|
|
302
|
-
})?;
|
|
303
289
|
|
|
304
290
|
let output = ruby.str_buf_new(buf.len());
|
|
305
291
|
output.cat(&buf);
|
|
306
292
|
Ok(output)
|
|
307
293
|
}
|
|
308
294
|
|
|
309
|
-
fn
|
|
310
|
-
|
|
295
|
+
fn session_block_counter(rb_self: &Session) -> u64 {
|
|
296
|
+
rb_self.inner.lock().unwrap().block_counter()
|
|
311
297
|
}
|
|
312
298
|
|
|
299
|
+
fn blake3_derive_key(ruby: &Ruby, args: &[Value]) -> Result<RString, Error> {
|
|
300
|
+
let parsed = scan_args::<(RString, RString), (), (), (), RHash, ()>(args)?;
|
|
301
|
+
let (rb_context, rb_material) = parsed.required;
|
|
302
|
+
let kw = get_kwargs::<_, (), (Option<usize>,), ()>(parsed.keywords, &[], &["length"])?;
|
|
303
|
+
let (opt_length,) = kw.optional;
|
|
304
|
+
let length = opt_length.unwrap_or(32);
|
|
305
|
+
|
|
306
|
+
if length == 0 || length > 65535 {
|
|
307
|
+
return Err(Error::new(
|
|
308
|
+
ruby.exception_arg_error(),
|
|
309
|
+
format!("length must be 1..65535, got {length}"),
|
|
310
|
+
));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// SAFETY: copy context string before any allocation
|
|
314
|
+
let context = unsafe { std::str::from_utf8(rb_context.as_slice()) }
|
|
315
|
+
.map_err(|_| Error::new(ruby.exception_arg_error(), "context must be valid UTF-8"))?
|
|
316
|
+
.to_owned();
|
|
317
|
+
|
|
318
|
+
let mut output_buf = vec![0u8; length];
|
|
319
|
+
unsafe {
|
|
320
|
+
let mut deriver = blake3::Hasher::new_derive_key(&context);
|
|
321
|
+
deriver.update(rb_material.as_slice());
|
|
322
|
+
let mut reader = deriver.finalize_xof();
|
|
323
|
+
reader.fill(&mut output_buf);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let output = ruby.str_from_slice(&output_buf);
|
|
327
|
+
output.freeze();
|
|
328
|
+
Ok(output)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
313
332
|
fn generate_key(ruby: &Ruby) -> Result<RString, Error> {
|
|
314
333
|
let mut key = [0u8; KEY_SIZE];
|
|
315
334
|
getrandom::getrandom(&mut key).map_err(|e| {
|
|
@@ -342,6 +361,7 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
342
361
|
|
|
343
362
|
module.const_set("KEY_SIZE", KEY_SIZE as u64)?;
|
|
344
363
|
module.const_set("NONCE_SIZE", NONCE_SIZE as u64)?;
|
|
364
|
+
module.const_set("SESSION_NONCE_SIZE", SESSION_NONCE_SIZE as u64)?;
|
|
345
365
|
module.const_set("TAG_SIZE", TAG_SIZE as u64)?;
|
|
346
366
|
|
|
347
367
|
let decryption_error_class =
|
|
@@ -357,122 +377,125 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
357
377
|
cipher_class.define_method("encrypt_detached", method!(cipher_encrypt_detached, -1))?;
|
|
358
378
|
cipher_class.define_method("decrypt_detached", method!(cipher_decrypt_detached, -1))?;
|
|
359
379
|
|
|
360
|
-
let
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
380
|
+
let session_class = module.define_class("Session", ruby.class_object())?;
|
|
381
|
+
session_class.define_singleton_method("new", function!(session_initialize, 3))?;
|
|
382
|
+
session_class.define_method("encrypt", method!(session_encrypt, -1))?;
|
|
383
|
+
session_class.define_method("decrypt", method!(session_decrypt, -1))?;
|
|
384
|
+
session_class.define_method("block_counter", method!(session_block_counter, 0))?;
|
|
365
385
|
|
|
366
386
|
module.define_module_function("generate_key", function!(generate_key, 0))?;
|
|
367
387
|
module.define_module_function("generate_nonce", function!(generate_nonce, 0))?;
|
|
388
|
+
module.define_module_function("derive_key", function!(blake3_derive_key, -1))?;
|
|
368
389
|
|
|
369
390
|
Ok(())
|
|
370
391
|
}
|
|
371
392
|
|
|
372
393
|
#[cfg(test)]
|
|
373
394
|
mod tests {
|
|
374
|
-
use
|
|
375
|
-
use chacha20_blake3::ChaCha20Blake3;
|
|
376
|
-
|
|
377
|
-
fn make_stream(key: [u8; 32], nonce: [u8; 24]) -> Stream {
|
|
378
|
-
Stream {
|
|
379
|
-
cipher: ChaCha20Blake3::new(key),
|
|
380
|
-
nonce_prefix: nonce[..16].try_into().unwrap(),
|
|
381
|
-
counter_base: u64::from_le_bytes(nonce[16..].try_into().unwrap()),
|
|
382
|
-
counter: Mutex::new(0),
|
|
383
|
-
}
|
|
384
|
-
}
|
|
395
|
+
use chacha20_blake3::{ChaCha20Blake3, Session20};
|
|
385
396
|
|
|
386
397
|
#[test]
|
|
387
|
-
fn
|
|
388
|
-
let
|
|
389
|
-
let
|
|
390
|
-
let
|
|
391
|
-
let
|
|
398
|
+
fn session_multi_message_roundtrip() {
|
|
399
|
+
let enc_key = [0x11u8; 32];
|
|
400
|
+
let auth_key = [0x22u8; 32];
|
|
401
|
+
let nonce = [0x33u8; 8];
|
|
402
|
+
let mut enc = Session20::new(enc_key, auth_key, nonce);
|
|
403
|
+
let mut dec = Session20::new(enc_key, auth_key, nonce);
|
|
392
404
|
|
|
393
405
|
let messages: &[&[u8]] = &[b"alpha", b"beta", b"gamma"];
|
|
394
|
-
let ciphertexts: Vec<Vec<u8>> = messages
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
*counter += 1;
|
|
399
|
-
ct
|
|
400
|
-
}).collect();
|
|
406
|
+
let ciphertexts: Vec<Vec<u8>> = messages
|
|
407
|
+
.iter()
|
|
408
|
+
.map(|m| enc.encrypt(m, b""))
|
|
409
|
+
.collect();
|
|
401
410
|
|
|
402
411
|
for (ct, expected) in ciphertexts.iter().zip(messages.iter()) {
|
|
403
|
-
let
|
|
404
|
-
let n = nonce_for_counter(&dec, *counter);
|
|
405
|
-
let pt = dec.cipher.decrypt(&n, ct, b"").expect("decrypt failed");
|
|
406
|
-
*counter += 1;
|
|
412
|
+
let pt = dec.decrypt(ct, b"").expect("decrypt failed");
|
|
407
413
|
assert_eq!(pt.as_slice(), *expected);
|
|
408
414
|
}
|
|
409
415
|
}
|
|
410
416
|
|
|
411
417
|
#[test]
|
|
412
|
-
fn
|
|
413
|
-
let
|
|
414
|
-
let
|
|
415
|
-
let
|
|
416
|
-
|
|
417
|
-
let mut counter = s.counter.lock().unwrap();
|
|
418
|
-
let n1 = nonce_for_counter(&s, *counter);
|
|
419
|
-
let ct1 = s.cipher.encrypt(&n1, b"repeat", b"");
|
|
420
|
-
*counter += 1;
|
|
418
|
+
fn session_same_message_different_ciphertext() {
|
|
419
|
+
let enc_key = [0x44u8; 32];
|
|
420
|
+
let auth_key = [0x55u8; 32];
|
|
421
|
+
let nonce = [0x66u8; 8];
|
|
422
|
+
let mut session = Session20::new(enc_key, auth_key, nonce);
|
|
421
423
|
|
|
422
|
-
let
|
|
423
|
-
let ct2 =
|
|
424
|
+
let ct1 = session.encrypt(b"repeat", b"");
|
|
425
|
+
let ct2 = session.encrypt(b"repeat", b"");
|
|
424
426
|
|
|
425
427
|
assert_ne!(ct1, ct2);
|
|
426
428
|
}
|
|
427
429
|
|
|
428
430
|
#[test]
|
|
429
|
-
fn
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
let
|
|
433
|
-
let mut
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
431
|
+
fn session_block_counter_advances_by_blocks() {
|
|
432
|
+
let enc_key = [0x77u8; 32];
|
|
433
|
+
let auth_key = [0x88u8; 32];
|
|
434
|
+
let nonce = [0x99u8; 8];
|
|
435
|
+
let mut session = Session20::new(enc_key, auth_key, nonce);
|
|
436
|
+
|
|
437
|
+
assert_eq!(session.block_counter(), 0);
|
|
438
|
+
|
|
439
|
+
// 100 bytes = ceil(100/64) = 2 blocks
|
|
440
|
+
session.encrypt(&[0u8; 100], b"");
|
|
441
|
+
assert_eq!(session.block_counter(), 2);
|
|
442
|
+
|
|
443
|
+
// 64 bytes = exactly 1 block
|
|
444
|
+
session.encrypt(&[0u8; 64], b"");
|
|
445
|
+
assert_eq!(session.block_counter(), 3);
|
|
446
|
+
|
|
447
|
+
// 0 bytes = 0 blocks
|
|
448
|
+
session.encrypt(b"", b"");
|
|
449
|
+
assert_eq!(session.block_counter(), 3);
|
|
441
450
|
}
|
|
442
451
|
|
|
443
452
|
#[test]
|
|
444
|
-
fn
|
|
445
|
-
let
|
|
446
|
-
let
|
|
447
|
-
let
|
|
448
|
-
let
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
let
|
|
452
|
-
let n = nonce_for_counter(&enc, *enc_counter);
|
|
453
|
-
let ct = enc.cipher.encrypt(&n, b"hello", b"");
|
|
454
|
-
*enc_counter = enc_counter.wrapping_add(1);
|
|
455
|
-
drop(enc_counter);
|
|
453
|
+
fn session_failed_decrypt_does_not_advance_counter() {
|
|
454
|
+
let enc_key = [0xAAu8; 32];
|
|
455
|
+
let auth_key = [0xBBu8; 32];
|
|
456
|
+
let nonce = [0xCCu8; 8];
|
|
457
|
+
let mut enc = Session20::new(enc_key, auth_key, nonce);
|
|
458
|
+
let mut dec = Session20::new(enc_key, auth_key, nonce);
|
|
459
|
+
|
|
460
|
+
let ct = enc.encrypt(b"hello", b"");
|
|
456
461
|
|
|
457
462
|
// Tamper with ciphertext
|
|
458
463
|
let mut tampered = ct.clone();
|
|
459
464
|
tampered[0] ^= 0xFF;
|
|
460
465
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
// Counter must NOT have advanced
|
|
467
|
-
assert_eq!(*dec_counter, 0);
|
|
468
|
-
|
|
469
|
-
// Original ciphertext should still decrypt at counter 0
|
|
470
|
-
let n = nonce_for_counter(&dec, *dec_counter);
|
|
471
|
-
let pt = dec.cipher.decrypt(&n, &ct, b"").expect("decrypt failed");
|
|
472
|
-
*dec_counter = dec_counter.wrapping_add(1);
|
|
466
|
+
assert!(dec.decrypt(&tampered, b"").is_err());
|
|
467
|
+
assert_eq!(dec.block_counter(), 0);
|
|
468
|
+
|
|
469
|
+
// Original still decrypts
|
|
470
|
+
let pt = dec.decrypt(&ct, b"").expect("decrypt failed");
|
|
473
471
|
assert_eq!(pt.as_slice(), b"hello");
|
|
474
472
|
}
|
|
475
473
|
|
|
474
|
+
#[test]
|
|
475
|
+
fn session_with_aad() {
|
|
476
|
+
let enc_key = [0xDDu8; 32];
|
|
477
|
+
let auth_key = [0xEEu8; 32];
|
|
478
|
+
let nonce = [0xFFu8; 8];
|
|
479
|
+
let mut enc = Session20::new(enc_key, auth_key, nonce);
|
|
480
|
+
let mut dec = Session20::new(enc_key, auth_key, nonce);
|
|
481
|
+
|
|
482
|
+
let ct = enc.encrypt(b"payload", b"header");
|
|
483
|
+
let pt = dec.decrypt(&ct, b"header").expect("decrypt with aad failed");
|
|
484
|
+
assert_eq!(pt.as_slice(), b"payload");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
#[test]
|
|
488
|
+
fn session_wrong_aad_fails() {
|
|
489
|
+
let enc_key = [0x01u8; 32];
|
|
490
|
+
let auth_key = [0x02u8; 32];
|
|
491
|
+
let nonce = [0x03u8; 8];
|
|
492
|
+
let mut enc = Session20::new(enc_key, auth_key, nonce);
|
|
493
|
+
let mut dec = Session20::new(enc_key, auth_key, nonce);
|
|
494
|
+
|
|
495
|
+
let ct = enc.encrypt(b"payload", b"correct");
|
|
496
|
+
assert!(dec.decrypt(&ct, b"wrong").is_err());
|
|
497
|
+
}
|
|
498
|
+
|
|
476
499
|
#[test]
|
|
477
500
|
fn round_trip_encrypt_decrypt() {
|
|
478
501
|
let key = [0x42u8; 32];
|
|
@@ -8,7 +8,7 @@ name = "chacha20blake3"
|
|
|
8
8
|
crate-type = ["cdylib", "rlib"]
|
|
9
9
|
|
|
10
10
|
[dependencies]
|
|
11
|
-
chacha20-blake3 =
|
|
11
|
+
chacha20-blake3 = "0.10.0"
|
|
12
12
|
blake3 = { version = "1", features = ["std"] }
|
|
13
13
|
magnus = "0.8"
|
|
14
14
|
getrandom = "0.2"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chacha20blake3
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -107,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
107
107
|
- !ruby/object:Gem::Version
|
|
108
108
|
version: '0'
|
|
109
109
|
requirements: []
|
|
110
|
-
rubygems_version: 4.0.
|
|
110
|
+
rubygems_version: 4.0.10
|
|
111
111
|
specification_version: 4
|
|
112
112
|
summary: 'Fast DJB-family authenticated encryption: ChaCha20 + BLAKE3 MAC'
|
|
113
113
|
test_files: []
|