#![no_std]
#![deny(missing_docs)]
pub extern crate stream_cipher;
#[cfg(feature = "zeroize")]
pub extern crate zeroize;
use core::fmt;
use stream_cipher::{LoopError, SyncStreamCipher, SyncStreamCipherSeek};
#[cfg(feature = "zeroize")]
use core::ops::Drop;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
pub const KEY_BITS: usize = 256;
pub const KEY_BYTES: usize = KEY_BITS / 8;
pub const KEY_WORDS: usize = KEY_BYTES / 4;
pub const IV_BITS: usize = 64;
pub const IV_BYTES: usize = IV_BITS / 8;
pub const IV_WORDS: usize = IV_BYTES / 4;
pub const STATE_BYTES: usize = 64;
pub const STATE_WORDS: usize = STATE_BYTES / 4;
pub const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
pub trait SalsaFamilyCipher {
fn block(&self, counter: u64) -> [u32; STATE_WORDS];
}
#[derive(Default)]
pub struct Ctr<C: SalsaFamilyCipher> {
cipher: C,
counter: u64,
offset: usize,
block: [u32; STATE_WORDS],
}
impl<C> Ctr<C>
where
C: SalsaFamilyCipher,
{
pub fn new(cipher: C) -> Self {
let block = cipher.block(0);
Self {
cipher,
counter: 0,
offset: 0,
block,
}
}
}
impl<C> SyncStreamCipher for Ctr<C>
where
C: SalsaFamilyCipher,
{
fn try_apply_keystream(&mut self, data: &mut [u8]) -> Result<(), LoopError> {
let datalen = data.len();
let initial_offset = self.offset;
let initial_word_offset = initial_offset % 4;
let initial_word_remaining = 4 - initial_word_offset;
let final_offset = initial_offset + datalen % STATE_BYTES;
let mut i = 0;
if datalen > initial_word_remaining {
let has_initial_words = initial_word_offset != 0;
let initial_word_idx = initial_offset / 4;
let mut word_idx = initial_offset / 4;
if has_initial_words {
let word = self.block[initial_word_idx];
for j in initial_word_offset..4 {
data[i] ^= ((word >> (j * 8)) & 0xff) as u8;
i += 1;
}
word_idx += 1;
}
let (leftover_words, leftover_bytes) =
if (datalen - i) / 4 > STATE_WORDS - (word_idx % STATE_WORDS) {
if word_idx != STATE_WORDS {
for j in word_idx..STATE_WORDS {
let word = self.block[j];
for k in 0..4 {
data[i] ^= ((word >> (k * 8)) & 0xff) as u8;
i += 1;
}
}
}
self.next_block();
let nblocks = (datalen - i) / 64;
let leftover = (datalen - i) % 64;
for _ in 0..nblocks {
for j in 0..STATE_WORDS {
let word = self.block[j];
for k in 0..4 {
data[i] ^= ((word >> (k * 8)) & 0xff) as u8;
i += 1;
}
}
self.next_block();
}
let leftover_words = leftover / 4;
for j in 0..leftover_words {
let word = self.block[j];
for k in 0..4 {
data[i] ^= ((word >> (k * 8)) & 0xff) as u8;
i += 1;
}
}
(leftover_words, leftover % 4)
} else {
let nwords = (datalen - i) / 4;
let leftover_bytes = (datalen - i) % 4;
if has_initial_words && word_idx == STATE_WORDS {
word_idx = 0;
self.next_block();
}
for j in word_idx..word_idx + nwords {
let word = self.block[j];
for k in 0..4 {
data[i] ^= ((word >> (k * 8)) & 0xff) as u8;
i += 1;
}
}
if word_idx + nwords == STATE_WORDS {
self.next_block();
}
((word_idx + nwords) % STATE_WORDS, leftover_bytes)
};
let word = self.block[leftover_words];
for j in 0..leftover_bytes {
data[i] ^= ((word >> (j * 8)) & 0xff) as u8;
i += 1;
}
self.offset = (4 * leftover_words) + leftover_bytes;
} else {
let word_idx = self.offset / 4 % STATE_WORDS;
let word = self.block[word_idx];
for j in initial_word_offset..initial_word_offset + datalen {
data[i] ^= ((word >> (j * 8)) & 0xff) as u8;
i += 1;
}
if final_offset == STATE_BYTES {
self.next_block();
}
}
self.offset = final_offset % STATE_BYTES;
Ok(())
}
}
impl<C> SyncStreamCipherSeek for Ctr<C>
where
C: SalsaFamilyCipher,
{
fn current_pos(&self) -> u64 {
self.counter << 6 | self.offset as u64
}
fn seek(&mut self, pos: u64) {
self.offset = (pos & 0x3f) as usize;
self.counter = pos >> 6;
self.block = self.cipher.block(self.counter);
}
}
impl<C> Ctr<C>
where
C: SalsaFamilyCipher,
{
fn next_block(&mut self) {
self.counter = self.counter.checked_add(1).expect("overflow");
self.block = self.cipher.block(self.counter);
}
}
impl<C> fmt::Debug for Ctr<C>
where
C: SalsaFamilyCipher,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SalsaFamilyState {{ block_idx: {}, offset: {}, ... }}",
self.counter, self.offset
)
}
}
#[cfg(feature = "zeroize")]
impl<C> Zeroize for Ctr<C>
where
C: SalsaFamilyCipher,
{
fn zeroize(&mut self) {
self.block.zeroize();
}
}
#[cfg(feature = "zeroize")]
impl<C> Drop for Ctr<C>
where
C: SalsaFamilyCipher,
{
fn drop(&mut self) {
self.zeroize();
}
}