You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
8.2 KiB
240 lines
8.2 KiB
use core::{mem, cmp};
|
|
use arch::*;
|
|
|
|
use super::{Aes128, Aes192, Aes256};
|
|
use block_cipher_trait::BlockCipher;
|
|
use block_cipher_trait::generic_array::GenericArray;
|
|
use block_cipher_trait::generic_array::typenum::U16;
|
|
use stream_cipher::{
|
|
SyncStreamCipher, SyncStreamCipherSeek, NewStreamCipher, LoopError,
|
|
};
|
|
|
|
const BLOCK_SIZE: usize = 16;
|
|
const PAR_BLOCKS: usize = 8;
|
|
const PAR_BLOCKS_SIZE: usize = PAR_BLOCKS*BLOCK_SIZE;
|
|
|
|
#[inline(always)]
|
|
pub fn xor(buf: &mut [u8], key: &[u8]) {
|
|
debug_assert_eq!(buf.len(), key.len());
|
|
for (a, b) in buf.iter_mut().zip(key) {
|
|
*a ^= *b;
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn xor_block8(buf: &mut [u8], ctr: [__m128i; 8]) {
|
|
debug_assert_eq!(buf.len(), PAR_BLOCKS_SIZE);
|
|
unsafe {
|
|
// compiler should unroll this loop
|
|
for i in 0..8 {
|
|
let ptr = buf.as_mut_ptr().offset(16*i) as *mut __m128i;
|
|
let data = _mm_loadu_si128(ptr);
|
|
let data = _mm_xor_si128(data, ctr[i as usize]);
|
|
_mm_storeu_si128(ptr, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn swap_bytes(v: __m128i) -> __m128i {
|
|
unsafe {
|
|
let mask = _mm_set_epi64x(0x08090a0b0c0d0e0f, 0x0001020304050607);
|
|
_mm_shuffle_epi8(v, mask)
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn inc_be(v: __m128i) -> __m128i {
|
|
unsafe { _mm_add_epi64(v, _mm_set_epi64x(1, 0)) }
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn load(val: &GenericArray<u8, U16>) -> __m128i {
|
|
unsafe { _mm_loadu_si128(val.as_ptr() as *const __m128i) }
|
|
}
|
|
|
|
|
|
macro_rules! impl_ctr {
|
|
($name:ident, $cipher:ty, $doc:expr) => {
|
|
#[doc=$doc]
|
|
#[derive(Clone)]
|
|
pub struct $name {
|
|
nonce: __m128i,
|
|
ctr: __m128i,
|
|
cipher: $cipher,
|
|
|
|
// if `leftover_pos` is `None` it means that leftover_buf is not
|
|
// filled with data and current position of the stream cipher
|
|
// can be calculated as counter*BLOCK_SIZE. If it's equal to
|
|
// `Some(pos)`, then buffer is filled with leftover keystream data
|
|
// and current position of the cipher equals to:
|
|
// `(counter - 1)*BLOCK_SIZE + pos`
|
|
// In other words counter is set for the next block calculation
|
|
leftover_buf: [u8; BLOCK_SIZE],
|
|
leftover_pos: Option<u8>,
|
|
}
|
|
|
|
impl $name {
|
|
#[inline(always)]
|
|
fn next_block(&mut self) -> __m128i {
|
|
let block = swap_bytes(self.ctr);
|
|
self.ctr = inc_be(self.ctr);
|
|
self.cipher.encrypt(block)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn next_block8(&mut self) -> [__m128i; 8] {
|
|
let mut ctr = self.ctr;
|
|
let mut block8: [__m128i; 8] = unsafe { mem::uninitialized() };
|
|
for i in 0..8 {
|
|
block8[i] = swap_bytes(ctr);
|
|
ctr = inc_be(ctr);
|
|
}
|
|
self.ctr = ctr;
|
|
|
|
self.cipher.encrypt8(block8)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn get_u64_ctr(&self) -> u64 {
|
|
let (ctr, nonce) = unsafe {(
|
|
mem::transmute::<__m128i, [u64; 2]>(self.ctr)[0],
|
|
mem::transmute::<__m128i, [u64; 2]>(self.nonce)[0],
|
|
)};
|
|
ctr.wrapping_sub(nonce)
|
|
}
|
|
|
|
/// Check if provided data will not overflow counter
|
|
#[inline(always)]
|
|
fn check_data_len(&self, data: &[u8]) -> Result<(), LoopError> {
|
|
let dlen = data.len() - match self.leftover_pos {
|
|
Some(pos) => cmp::min(BLOCK_SIZE - pos as usize, data.len()),
|
|
None => 0,
|
|
};
|
|
let data_blocks = dlen/BLOCK_SIZE +
|
|
if data.len() % BLOCK_SIZE != 0 { 1 } else { 0 };
|
|
let counter = self.get_u64_ctr();
|
|
if counter.checked_add(data_blocks as u64).is_some() {
|
|
Ok(())
|
|
} else {
|
|
Err(LoopError)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl NewStreamCipher for $name {
|
|
type KeySize = <$cipher as BlockCipher>::KeySize;
|
|
type NonceSize = U16;
|
|
|
|
fn new(
|
|
key: &GenericArray<u8, Self::KeySize>,
|
|
nonce: &GenericArray<u8, Self::NonceSize>,
|
|
) -> Self {
|
|
let nonce = swap_bytes(load(nonce));
|
|
let cipher = <$cipher>::new(key);
|
|
Self {
|
|
nonce,
|
|
ctr: nonce,
|
|
cipher,
|
|
leftover_pos: None,
|
|
leftover_buf: [0u8; BLOCK_SIZE],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SyncStreamCipher for $name {
|
|
#[inline]
|
|
fn try_apply_keystream(&mut self, mut data: &mut [u8])
|
|
-> Result<(), LoopError>
|
|
{
|
|
self.check_data_len(data)?;
|
|
// process leftover bytes from the last call if any
|
|
if let Some(pos) = self.leftover_pos {
|
|
let pos = pos as usize;
|
|
// check if input buffer is large enough to be xor'ed
|
|
// with all leftover bytes
|
|
if data.len() >= BLOCK_SIZE - pos {
|
|
let buf = &self.leftover_buf[pos..];
|
|
let (r, l) = {data}.split_at_mut(buf.len());
|
|
data = l;
|
|
xor(r, buf);
|
|
self.leftover_pos = None;
|
|
} else {
|
|
let buf = &self.leftover_buf[pos..pos + data.len()];
|
|
xor(data, buf);
|
|
self.leftover_pos = Some((pos + data.len()) as u8);
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
// process 8 blocks at a time
|
|
while data.len() >= PAR_BLOCKS_SIZE {
|
|
let (r, l) = {data}.split_at_mut(PAR_BLOCKS_SIZE);
|
|
data = l;
|
|
xor_block8(r, self.next_block8());
|
|
}
|
|
|
|
// process one block at a time
|
|
while data.len() >= BLOCK_SIZE {
|
|
let (r, l) = {data}.split_at_mut(BLOCK_SIZE);
|
|
data = l;
|
|
|
|
let block = self.next_block();
|
|
|
|
unsafe {
|
|
let t = _mm_loadu_si128(r.as_ptr() as *const __m128i);
|
|
let res = _mm_xor_si128(block, t);
|
|
_mm_storeu_si128(r.as_mut_ptr() as *mut __m128i, res);
|
|
}
|
|
}
|
|
|
|
// process leftover bytes
|
|
if data.len() != 0 {
|
|
let block = self.next_block();
|
|
self.leftover_buf = unsafe {
|
|
mem::transmute::<__m128i, [u8; BLOCK_SIZE]>(block)
|
|
};
|
|
let n = data.len();
|
|
self.leftover_pos = Some(n as u8);
|
|
for (a, b) in data.iter_mut().zip(&self.leftover_buf[..n]) {
|
|
*a ^= *b;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SyncStreamCipherSeek for $name {
|
|
fn current_pos(&self) -> u64 {
|
|
let bs = BLOCK_SIZE as u64;
|
|
let ctr = self.get_u64_ctr();
|
|
match self.leftover_pos {
|
|
Some(pos) => ctr.wrapping_sub(1)*bs + pos as u64,
|
|
None => ctr*bs,
|
|
}
|
|
}
|
|
|
|
fn seek(&mut self, pos: u64) {
|
|
let n = pos / BLOCK_SIZE as u64;
|
|
let l = pos % BLOCK_SIZE as u64;
|
|
self.ctr = unsafe {
|
|
_mm_add_epi64(self.nonce, _mm_set_epi64x(n as i64, 0))
|
|
};
|
|
if l == 0 {
|
|
self.leftover_pos = None;
|
|
} else {
|
|
self.leftover_buf = unsafe {
|
|
mem::transmute(self.next_block())
|
|
};
|
|
self.leftover_pos = Some(l as u8);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_opaque_debug!($name);
|
|
}
|
|
}
|
|
|
|
impl_ctr!(Aes128Ctr, Aes128, "AES-128 in CTR mode");
|
|
impl_ctr!(Aes192Ctr, Aes192, "AES-192 in CTR mode");
|
|
impl_ctr!(Aes256Ctr, Aes256, "AES-256 in CTR mode");
|
|
|