diff --git a/README.md b/README.md index f60d5a7..2066ad8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,41 @@ # chacha20-poly1305-stream -ChaCha20 Poly1305 stream encrypt and decrypt library \ No newline at end of file +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 +``` diff --git a/examples/encrypt_and_decrypt.rs b/examples/encrypt_and_decrypt.rs new file mode 100644 index 0000000..68ba36f --- /dev/null +++ b/examples/encrypt_and_decrypt.rs @@ -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)); +} \ No newline at end of file diff --git a/src/as_bytes.rs b/src/as_bytes.rs index 576a506..cbd3a60 100644 --- a/src/as_bytes.rs +++ b/src/as_bytes.rs @@ -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 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::()) } @@ -27,6 +29,7 @@ impl 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::()) } diff --git a/src/lib.rs b/src/lib.rs index 861e5ce..1d8a26b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ // Copyright 2016 chacha20-poly1305-aead Developers +// Copyright 2016 chacha20-poly1305-stream Developers // // Licensed under the Apache License, Version 2.0, or the MIT license : Copy { + #[allow(clippy::wrong_self_convention)] fn from_le(self) -> Self; fn to_le(self) -> Self; diff --git a/src/stream_decryptor.rs b/src/stream_decryptor.rs index b8fcffe..985defc 100644 --- a/src/stream_decryptor.rs +++ b/src/stream_decryptor.rs @@ -16,8 +16,8 @@ pub struct ChaCha20Poly1305StreamDecryptor { impl ChaCha20Poly1305StreamDecryptor { /// New ChaCha20Poly1305StreamDecryptor pub fn new(key: &[u8], nonce: &[u8]) -> Result { - 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, 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) } } } diff --git a/src/stream_encryptor.rs b/src/stream_encryptor.rs index d04d957..991ecbe 100644 --- a/src/stream_encryptor.rs +++ b/src/stream_encryptor.rs @@ -16,8 +16,8 @@ pub struct ChaCha20Poly1305StreamEncryptor { impl ChaCha20Poly1305StreamEncryptor { /// New ChaCha20Poly1305StreamEncryptor pub fn new(key: &[u8], nonce: &[u8]) -> Result { - 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) } } diff --git a/src/stream_util.rs b/src/stream_util.rs index b043046..40bf689 100644 --- a/src/stream_util.rs +++ b/src/stream_util.rs @@ -1,10 +1,24 @@ 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; 4]) { let block = chacha20.next(); buf[0] = buf[0] ^ block[0]; buf[1] = buf[1] ^ block[1]; 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(()) } \ No newline at end of file