feat: works

This commit is contained in:
2023-10-22 11:38:53 +08:00
parent 1e5a5516bb
commit 4e29789792
8 changed files with 149 additions and 27 deletions

View File

@@ -1,3 +1,41 @@
# chacha20-poly1305-stream
ChaCha20 Poly1305 stream encrypt and decrypt library
## Encrypt
```rust
// IMPORTANT! key and nonce SHOULD generate by random
let key = [0u8; 32];
let nonce = [0; 12];
let mut encryptor = ChaCha20Poly1305StreamEncryptor::new(&key, &nonce).unwrap();
let mut ciphertext = vec![];
ciphertext.extend_from_slice(&encryptor.update(b"Hello "));
ciphertext.extend_from_slice(&encryptor.update(b" World"));
ciphertext.extend_from_slice(&encryptor.update(b"!"));
let (last_block, tag) = encryptor.finalize();
ciphertext.extend_from_slice(&last_block);
ciphertext.extend_from_slice(&tag);
println!("Ciphertext: {}", hex::encode(&ciphertext));
```
## Run Example
```shell
$ cargo run --example encrypt_and_decrypt
Finished dev [unoptimized + debuginfo] target(s) in 0.19s
Running `target/debug/examples/encrypt_and_decrypt`
Ciphertext: d7628bd23a71182df7c8fb1852d3dc42b88a61e2fce340d9c5b323884d
Plaintext : Hello World!
```
Benchmark @MacBook Pro (Retina, 15-inch, Late 2013/2 GHz Quad-Core Intel Core i7)
```shell
$ cargo r --release --example bench
ChaCha20Poly1305 encrypt : 287.06 M/s
ChaCha20Poly1305 encrypt/decrypt : 144.93 M/s
```

View File

@@ -0,0 +1,39 @@
use chacha20_poly1305_stream::{ChaCha20Poly1305StreamDecryptor, ChaCha20Poly1305StreamEncryptor};
fn main() {
encrypt();
decrypt();
}
fn encrypt() {
// IMPORTANT! key and nonce SHOULD generate by random
let key = [0u8; 32];
let nonce = [0; 12];
let mut encryptor = ChaCha20Poly1305StreamEncryptor::new(&key, &nonce).unwrap();
let mut ciphertext = vec![];
ciphertext.extend_from_slice(&encryptor.update(b"Hello "));
ciphertext.extend_from_slice(&encryptor.update(b" World"));
ciphertext.extend_from_slice(&encryptor.update(b"!"));
let (last_block, tag) = encryptor.finalize();
ciphertext.extend_from_slice(&last_block);
ciphertext.extend_from_slice(&tag);
println!("Ciphertext: {}", hex::encode(&ciphertext));
}
fn decrypt() {
// IMPORTANT! key and nonce SHOULD generate by random
let key = [0u8; 32];
let nonce = [0; 12];
let cipher_text = hex::decode("d7628bd23a71182df7c8fb1852d3dc42b88a61e2fce340d9c5b323884d").unwrap();
let mut decryptor = ChaCha20Poly1305StreamDecryptor::new(&key, &nonce).unwrap();
let mut plaintext = decryptor.update(&cipher_text);
let last_block = decryptor.finalize().unwrap();
plaintext.extend_from_slice(&last_block);
println!("Plaintext : {}", String::from_utf8_lossy(&plaintext));
}

View File

@@ -8,6 +8,7 @@
use std::mem;
use std::slice;
#[allow(clippy::missing_safety_doc)]
pub unsafe trait Safe {}
pub trait AsBytes {
@@ -19,6 +20,7 @@ impl<T: Safe> AsBytes for [T] {
#[inline]
fn as_bytes(&self) -> &[u8] {
unsafe {
#[allow(clippy::manual_slice_size_calculation)]
slice::from_raw_parts(self.as_ptr() as *const u8,
self.len() * mem::size_of::<T>())
}
@@ -27,6 +29,7 @@ impl<T: Safe> AsBytes for [T] {
#[inline]
fn as_mut_bytes(&mut self) -> &mut [u8] {
unsafe {
#[allow(clippy::manual_slice_size_calculation)]
slice::from_raw_parts_mut(self.as_mut_ptr() as *mut u8,
self.len() * mem::size_of::<T>())
}

View File

@@ -1,4 +1,5 @@
// Copyright 2016 chacha20-poly1305-aead Developers
// Copyright 2016 chacha20-poly1305-stream Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
@@ -112,3 +113,30 @@ fn test_enc_dec() {
let plaintext_decrypted = chacha20_poly1305_aad_decrypt(&key, &nonce, aad, &ciphertext).unwrap();
assert_eq!(plaintext, plaintext_decrypted);
}
#[test]
fn test_rfc_7539() {
let key = hex::decode(
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f").unwrap();
let nonce = hex::decode("070000004041424344454647").unwrap();
let aad = hex::decode("50515253c0c1c2c3c4c5c6c7").unwrap();
let plaintext = hex::decode(
"4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f6620\
2739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666\
f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e").unwrap();
let expected_ciphertext = hex::decode(
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca\
9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091\
b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116").unwrap();
let expected_tag = hex::decode("1ae10b594f09e26a7e902ecbd0600691").unwrap();
let mut encryptor = ChaCha20Poly1305StreamEncryptor::new(&key, &nonce).unwrap();
encryptor.init_adata(&aad);
let mut ciphertext = encryptor.update(&plaintext);
let (last_block, tag) = encryptor.finalize();
ciphertext.extend_from_slice(&last_block);
assert_eq!(expected_ciphertext, ciphertext);
assert_eq!(expected_tag, tag);
}

View File

@@ -12,6 +12,7 @@ use crate::simd_opt;
pub use crate::simdty::u32x4;
pub trait Vector4<T>: Copy {
#[allow(clippy::wrong_self_convention)]
fn from_le(self) -> Self;
fn to_le(self) -> Self;

View File

@@ -16,8 +16,8 @@ pub struct ChaCha20Poly1305StreamDecryptor {
impl ChaCha20Poly1305StreamDecryptor {
/// New ChaCha20Poly1305StreamDecryptor
pub fn new(key: &[u8], nonce: &[u8]) -> Result<Self, String> {
if key.len() != 32 { return Err("Bad key length".to_string()); }
if nonce.len() != 12 { return Err("Bad nonce length".to_string()); }
stream_util::verify_key_nonce_length(key, nonce)?;
let mut chacha20 = ChaCha20::new(key, nonce);
let poly1305 = Poly1305::new(&chacha20.next().as_bytes()[..32]);
Ok(Self {
@@ -43,17 +43,17 @@ impl ChaCha20Poly1305StreamDecryptor {
let mut buf = [u32x4::default(); 4];
let b_len = buf.as_bytes().len();
let valid_message_len = if self.message_buffer.len() >= 16 { self.message_buffer.len() - 16 } else { 0 };
let b_count = valid_message_len / b_len;
let payload_len = if self.message_buffer.len() >= 16 { self.message_buffer.len() - 16 } else { 0 };
let b_count = payload_len / b_len;
if b_count == 0 {
return vec![];
}
let mut decrypted = Vec::with_capacity(b_len * b_count);
for i in 0..b_count {
let encrypted = &self.message_buffer[(b_len * i)..(b_len * (i + 1))];
self.poly1305.padded_blocks(encrypted);
buf.as_mut_bytes().copy_from_slice(encrypted);
let b_encrypted = &self.message_buffer[(b_len * i)..(b_len * (i + 1))];
self.poly1305.padded_blocks(b_encrypted);
buf.as_mut_bytes().copy_from_slice(b_encrypted);
stream_util::next_chacha20_xor(&mut self.chacha20, &mut buf);
decrypted.extend_from_slice(buf.as_bytes());
}
@@ -64,33 +64,33 @@ impl ChaCha20Poly1305StreamDecryptor {
/// Finalize decrypt
pub fn finalize(mut self) -> Result<Vec<u8>, String> {
let mut last_block = vec![];
if self.message_buffer.len() < 16 {
return Err("Bad tag length".to_string());
}
let message_buffer_len = self.message_buffer.len() - 16;
if message_buffer_len > 0 {
let mut last_decrypted_block = vec![];
let last_payload_len = self.message_buffer.len() - 16;
if last_payload_len > 0 {
let mut buf = [u32x4::default(); 4];
let buf_bytes = buf.as_mut_bytes();
let encrypted = &self.message_buffer[..message_buffer_len];
buf_bytes[..message_buffer_len].copy_from_slice(encrypted);
let last_b_encrypted = &self.message_buffer[..last_payload_len];
buf_bytes[..last_payload_len].copy_from_slice(last_b_encrypted);
stream_util::next_chacha20_xor(&mut self.chacha20, &mut buf);
let last_block_bytes = &buf.as_bytes()[..message_buffer_len];
self.poly1305.padded_blocks(encrypted);
last_block.extend_from_slice(last_block_bytes);
self.message_len += last_block.len() as u64;
let last_b_decrypted = &buf.as_bytes()[..last_payload_len];
self.poly1305.padded_blocks(last_b_encrypted);
last_decrypted_block.extend_from_slice(last_b_decrypted);
self.message_len += last_decrypted_block.len() as u64;
}
let message_tag = &self.message_buffer[message_buffer_len..];
let message_tag = &self.message_buffer[last_payload_len..];
self.poly1305.block([self.adata_len.to_le(), self.message_len.to_le()].as_bytes());
let mut tag = [0; 16];
tag.clone_from_slice(self.poly1305.tag().as_bytes());
let poly1305_tag = self.poly1305.tag();
let tag = poly1305_tag.as_bytes();
if message_tag != tag {
Err(format!("Tag mismatch, expected: {}, actual: {}",
hex::encode(tag), hex::encode(message_tag)))
} else {
Ok(last_block)
Ok(last_decrypted_block)
}
}
}

View File

@@ -16,8 +16,8 @@ pub struct ChaCha20Poly1305StreamEncryptor {
impl ChaCha20Poly1305StreamEncryptor {
/// New ChaCha20Poly1305StreamEncryptor
pub fn new(key: &[u8], nonce: &[u8]) -> Result<Self, String> {
if key.len() != 32 { return Err("Bad key length".to_string()); }
if nonce.len() != 12 { return Err("Bad nonce length".to_string()); }
stream_util::verify_key_nonce_length(key, nonce)?;
let mut chacha20 = ChaCha20::new(key, nonce);
let poly1305 = Poly1305::new(&chacha20.next().as_bytes()[..32]);
Ok(Self {
@@ -76,10 +76,9 @@ impl ChaCha20Poly1305StreamEncryptor {
}
self.poly1305.block([self.adata_len.to_le(), self.message_len.to_le()].as_bytes());
let tag = self.poly1305.tag().as_bytes().to_vec();
let mut tag = [0; 16];
tag.clone_from_slice(self.poly1305.tag().as_bytes());
(last_block, tag.to_vec())
(last_block, tag)
}
}

View File

@@ -1,6 +1,10 @@
use crate::chacha20::ChaCha20;
use crate::simdty::Simd4;
const CHACHA20_POLY1305_KEY_LEN: usize = 32;
const CHACHA20_POLY1305_NONCE_LEN: usize = 12;
#[inline]
pub(crate) fn next_chacha20_xor(chacha20: &mut ChaCha20, buf: &mut [Simd4<u32>; 4]) {
let block = chacha20.next();
buf[0] = buf[0] ^ block[0];
@@ -8,3 +12,13 @@ pub(crate) fn next_chacha20_xor(chacha20: &mut ChaCha20, buf: &mut [Simd4<u32>;
buf[2] = buf[2] ^ block[2];
buf[3] = buf[3] ^ block[3];
}
pub(crate) fn verify_key_nonce_length(key: &[u8], nonce: &[u8]) -> Result<(), String> {
if key.len() != CHACHA20_POLY1305_KEY_LEN {
return Err("Bad key length".to_string());
}
if nonce.len() != CHACHA20_POLY1305_NONCE_LEN {
return Err("Bad nonce length".to_string());
}
Ok(())
}