#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CfbError {
InvalidFeedbackWidth,
NonAlignedIntermediateCall,
}
macro_rules! kalyna_cfb_variant {
($name:ident, $expanded:ident, $key_bytes:literal, $block_bytes:literal) => {
#[doc = concat!(
"CFB mode over [`super::kalyna::", stringify!($expanded), "`] - see the module doc ",
"comment for the citation and the misuse warning."
)]
pub struct $name {
key: super::kalyna::$expanded,
gamma: [u8; $block_bytes],
feed: [u8; $block_bytes],
q: usize,
used_gamma_len: usize,
}
impl $name {
pub fn new(
key: &[u8; $key_bytes],
iv: &[u8; $block_bytes],
q: usize,
) -> Result<Self, CfbError> {
if q == 0 || q > $block_bytes || !matches!(q, 1 | 8 | 16 | 32 | 64) {
return Err(CfbError::InvalidFeedbackWidth);
}
Ok(Self {
key: super::kalyna::$expanded::new(key),
gamma: *iv,
feed: *iv,
q,
used_gamma_len: $block_bytes,
})
}
pub fn encrypt_in_place(&mut self, buf: &mut [u8]) -> Result<(), CfbError> {
if self.used_gamma_len % self.q != 0 {
return Err(CfbError::NonAlignedIntermediateCall);
}
let mut data_off = 0usize;
let mut offset = self.used_gamma_len;
if offset != 0 {
while offset < self.q && data_off < buf.len() {
buf[data_off] ^= self.gamma[offset];
self.feed[offset] = buf[data_off];
offset += 1;
data_off += 1;
}
if offset == $block_bytes {
self.gamma = self.key.encrypt_block(&self.feed);
offset = $block_bytes - self.q;
}
}
while data_off + self.q <= buf.len() {
for (b, g) in buf[data_off..data_off + self.q]
.iter_mut()
.zip(&self.gamma[offset..offset + self.q])
{
*b ^= *g;
}
self.feed[..$block_bytes].copy_from_slice(&self.gamma);
self.feed[offset..offset + self.q].copy_from_slice(&buf[data_off..data_off + self.q]);
self.gamma = self.key.encrypt_block(&self.feed);
data_off += self.q;
}
while data_off < buf.len() {
buf[data_off] ^= self.gamma[$block_bytes - (buf.len() - data_off)];
self.feed[offset] = buf[data_off];
offset += 1;
data_off += 1;
}
self.used_gamma_len = offset;
Ok(())
}
pub fn decrypt_in_place(&mut self, buf: &mut [u8]) -> Result<(), CfbError> {
if self.used_gamma_len % self.q != 0 {
return Err(CfbError::NonAlignedIntermediateCall);
}
let mut data_off = 0usize;
let mut offset = self.used_gamma_len;
if offset != 0 {
while offset < self.q && data_off < buf.len() {
let ciphertext_byte = buf[data_off];
buf[data_off] ^= self.gamma[offset];
self.feed[offset] = ciphertext_byte;
offset += 1;
data_off += 1;
}
if offset == $block_bytes {
self.gamma = self.key.encrypt_block(&self.feed);
offset = $block_bytes - self.q;
}
}
while data_off + self.q <= buf.len() {
let mut ciphertext_chunk = [0u8; $block_bytes];
ciphertext_chunk[..self.q].copy_from_slice(&buf[data_off..data_off + self.q]);
for (b, g) in buf[data_off..data_off + self.q]
.iter_mut()
.zip(&self.gamma[offset..offset + self.q])
{
*b ^= *g;
}
self.feed[..$block_bytes].copy_from_slice(&self.gamma);
self.feed[offset..offset + self.q].copy_from_slice(&ciphertext_chunk[..self.q]);
self.gamma = self.key.encrypt_block(&self.feed);
data_off += self.q;
}
while data_off < buf.len() {
let ciphertext_byte = buf[data_off];
buf[data_off] ^= self.gamma[$block_bytes - (buf.len() - data_off)];
self.feed[offset] = ciphertext_byte;
offset += 1;
data_off += 1;
}
self.used_gamma_len = offset;
Ok(())
}
}
};
}
kalyna_cfb_variant!(Kalyna128_128Cfb, Kalyna128_128ExpandedKey, 16, 16);
kalyna_cfb_variant!(Kalyna128_256Cfb, Kalyna128_256ExpandedKey, 32, 16);
kalyna_cfb_variant!(Kalyna256_256Cfb, Kalyna256_256ExpandedKey, 32, 32);
kalyna_cfb_variant!(Kalyna256_512Cfb, Kalyna256_512ExpandedKey, 64, 32);
kalyna_cfb_variant!(Kalyna512_512Cfb, Kalyna512_512ExpandedKey, 64, 64);