#![cfg(target_endian="little")]
#![doc(cfg(target_endian="little"))]
use {
alloc::vec::Vec,
crate::{Bytes, Result},
self::word_array::WordArray,
};
#[cfg(not(feature="std"))]
use crate::io::Write;
#[cfg(feature="std")]
use {
std::io::Write,
crate::IoResult,
};
#[cfg(feature="simd")]
use core::simd::Simd;
#[cfg(test)]
use {
core::mem,
crate::keccak::Hash,
openssl::symm::{Cipher, Crypter, Mode},
};
#[cfg(test)]
#[cfg(feature="std")]
use std::thread;
#[cfg(test)]
mod tests;
mod variant;
pub use self::variant::*;
#[cfg(not(feature="simd"))]
#[doc(cfg(not(feature="simd")))]
pub (crate) mod salsa20;
#[cfg(feature="simd")]
#[doc(cfg(feature="simd"))]
pub (crate) mod salsa20_simd;
pub (crate) mod word_array;
pub type Key = [u8; KEY_SIZE_IN_BYTES];
const KEY_SIZE_IN_BYTES: usize = 32;
pub type IV = [u8; IV_SIZE_IN_BYTES];
const IV_SIZE_IN_BYTES: usize = 16;
pub (crate) const BLOCK_SIZE_IN_BYTES: usize = 64;
pub (crate) type Block = [u8; BLOCK_SIZE_IN_BYTES];
#[cfg(any(feature="simd", test))]
const BLOCK_OF_ZEROS: Block = [0; BLOCK_SIZE_IN_BYTES];
#[cfg(test)]
const KEY_HASH: Hash = Hash::Sha3_256;
#[cfg(test)]
const IV_HASH: Hash = Hash::Shake128;
pub (crate) const TWENTY: Variant = Variant::Twenty;
#[derive(Debug)]
pub struct Chacha<W> where W: Write {
variant: Variant,
data: WordArray,
buffer: Vec<u8>,
output: W,
}
impl<W> Chacha<W> where W: Write {
pub const fn variant(&self) -> &Variant {
&self.variant
}
#[inline]
pub fn encrypt<B>(&mut self, bytes: B) -> Result<usize> where B: AsRef<[u8]> {
let mut bytes = bytes.as_ref();
let result = bytes.len();
if result == 0 {
return Ok(result);
}
if crate::io::fill_buffer(&mut self.buffer, BLOCK_SIZE_IN_BYTES, &mut bytes) {
self.process_buffer_and_write_to_output(None)?;
self.buffer.clear();
}
{
let chunks = bytes.chunks_exact(BLOCK_SIZE_IN_BYTES);
self.buffer.extend(chunks.remainder());
for c in chunks {
self.process_buffer_and_write_to_output(Some(c))?;
}
}
Ok(result)
}
pub fn encrypt_bytes<'a, const N: usize, B, B0>(&mut self, bytes: B) -> Result<Option<usize>>
where B: Into<Bytes<'a, N, B0>>, B0: AsRef<[u8]> + 'a {
let mut result = Some(usize::MIN);
for bytes in bytes.into().as_slice() {
let size = self.encrypt(bytes)?;
if let Some(current) = result.as_mut() {
match current.checked_add(size) {
Some(new) => *current = new,
None => result = None,
};
}
}
Ok(result)
}
#[inline]
fn process_buffer_and_write_to_output(&mut self, buffer: Option<&[u8]>) -> Result<()> {
let buffer = buffer.unwrap_or(&self.buffer);
let buffer_len = match buffer.len() {
0 => return Ok(()),
buffer_len @ 1..=BLOCK_SIZE_IN_BYTES => buffer_len,
other => return Err(err!("Buffer is too large: {other} (max allowed: {max})", other=other, max=BLOCK_SIZE_IN_BYTES)),
};
#[cfg(feature="simd")]
let mut output = salsa20_simd::words_to_bytes(&self.variant, &self.data);
#[cfg(not(feature="simd"))]
let mut output = salsa20::words_to_bytes(&self.variant, &self.data);
{
const TWELFTH: usize = 12;
self.data[TWELFTH] = self.data[TWELFTH].wrapping_add(1);
if self.data[TWELFTH] == 0 {
const THIRTEENTH: usize = TWELFTH + 1;
self.data[THIRTEENTH] = self.data[THIRTEENTH].wrapping_add(1);
}
}
#[cfg(feature="simd")] {
output = {
let mut tmp = BLOCK_OF_ZEROS;
tmp[..buffer_len].copy_from_slice(buffer);
(Simd::from_array(output) ^ Simd::from_array(tmp)).to_array()
};
}
#[cfg(not(feature="simd"))]
(0..buffer_len).for_each(|i| output[i] ^= buffer[i]);
let result = self.output.write_all(&output[..buffer_len]);
#[cfg(feature="std")]
let result = result.map_err(|e| from_io_err!(e));
result
}
#[cfg(feature="std")]
#[inline(always)]
pub (crate) const fn mut_output(&mut self) -> &mut W {
&mut self.output
}
#[must_use]
pub fn finish(mut self) -> Result<W> {
self.process_buffer_and_write_to_output(None)?;
drop(self.buffer);
#[cfg(feature="std")]
from_io_err!(self.output.flush())?;
Ok(self.output)
}
}
#[cfg(feature="std")]
#[doc(cfg(feature="std"))]
impl<W> Write for Chacha<W> where W: Write {
fn write(&mut self, buffer: &[u8]) -> IoResult<usize> {
Ok(self.encrypt(buffer)?)
}
fn flush(&mut self) -> IoResult<()> {
self.output.flush()
}
}
#[test]
fn tests() {
assert_eq!(mem::size_of::<Block>(), mem::size_of::<WordArray>());
assert_eq!(KEY_SIZE_IN_BYTES, 32);
assert_eq!(mem::size_of::<Key>(), KEY_SIZE_IN_BYTES);
assert_eq!(IV_SIZE_IN_BYTES, 16);
assert_eq!(mem::size_of::<IV>(), IV_SIZE_IN_BYTES);
}
#[cfg(test)]
fn encrypt_using_open_ssl_chacha20<K, IV, B>(mode: Mode, key: K, iv: IV, bytes: B) -> Vec<u8>
where K: AsRef<[u8]>, IV: AsRef<[u8]>, B: AsRef<[u8]> {
let bytes = bytes.as_ref();
let mut crypter = Crypter::new(Cipher::chacha20(), mode, key.as_ref(), Some(iv.as_ref())).unwrap();
let mut result = Vec::with_capacity(bytes.len());
let mut output = BLOCK_OF_ZEROS;
for c in bytes.chunks(BLOCK_SIZE_IN_BYTES) {
let count = crypter.update(c, &mut output).unwrap();
result.extend(&output[..count]);
}
{
let count = crypter.finalize(&mut output).unwrap();
result.extend(&output[..count]);
}
result
}
#[test]
fn cmp_to_data_generated_by_open_ssl() -> Result<()> {
const DATA: &[u8] = &[
0x50, 0xa6, 0xb7, 0xec, 0xb4, 0x2c, 0xc8, 0x9a, 0xbd, 0x5c, 0x40, 0xea, 0x5e, 0x30, 0x66, 0x31, 0xbf, 0x93, 0x49, 0x6a, 0xc5, 0x01,
0x56, 0xb3, 0x6f, 0x51, 0x56, 0xe8, 0x56, 0x89, 0xe8, 0x56, 0x08, 0xe5, 0xb0, 0xe6, 0xa0, 0xd9, 0x3c, 0x1c, 0x6a, 0x8a, 0xd7, 0x12,
0x09, 0xf0, 0xac, 0x9d, 0x57, 0xb0, 0x45, 0x33, 0x9d, 0x1f, 0x60, 0x1d, 0x34, 0xf8, 0xa7, 0xa0, 0x4e, 0x42, 0x64, 0xae,
0x98, 0xda, 0xad, 0x4a, 0x94, 0x82, 0xcc, 0x9b, 0x84, 0x82, 0x1f, 0x50, 0x4f, 0xc0, 0x44, 0xba,
];
let key = &KEY_HASH.hash("key");
let iv = &IV_HASH.hash("IV");
let encrypt = |data| TWENTY.encrypt(key, iv, data);
let encrypt_using_open_ssl_chacha20 = |mode, data| encrypt_using_open_ssl_chacha20(mode, key, iv, data);
let encrypted = encrypt(DATA)?;
assert_eq!(DATA.len(), encrypted.len());
assert_ne!(DATA, encrypted);
assert_eq!(encrypted, encrypt_using_open_ssl_chacha20(Mode::Encrypt, DATA));
assert_eq!(DATA, encrypt(&encrypted)?);
assert_eq!(DATA, encrypt_using_open_ssl_chacha20(Mode::Decrypt, &encrypted));
Ok(())
}
#[test]
#[cfg(feature="std")]
fn cmp_to_data_generated_by_open_ssl_using_threads() -> Result<()> {
const INPUT_DATA: &[u8] = &[u8::MIN; BLOCK_SIZE_IN_BYTES + 1];
(-1_i16..=999).map(|index| thread::spawn(move || {
let key = &if index >= 0 {
KEY_HASH.hash(index.to_be_bytes())
} else {
vec![u8::MAX; KEY_SIZE_IN_BYTES]
};
let iv = &if index % 2 == 0 {
vec![index as u8; IV_SIZE_IN_BYTES]
} else {
IV_HASH.hash(index.to_le_bytes())
};
assert_ne!(key, iv);
let encrypt = |data| TWENTY.encrypt(key, iv, data);
let encrypt_using_open_ssl_chacha20 = |mode, data| encrypt_using_open_ssl_chacha20(mode, key, iv, data);
let encrypted_data = encrypt(INPUT_DATA)?;
assert_eq!(INPUT_DATA.len(), encrypted_data.len());
assert_eq!(encrypted_data, encrypt_using_open_ssl_chacha20(Mode::Encrypt, INPUT_DATA));
assert_eq!(INPUT_DATA, encrypt(&encrypted_data)?);
assert_eq!(INPUT_DATA, encrypt_using_open_ssl_chacha20(Mode::Decrypt, &encrypted_data));
Result::Ok(())
})).collect::<Vec<_>>().into_iter().for_each(|t| t.join().unwrap().unwrap());
Ok(())
}