use subtle::ConstantTimeEq;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KwError {
InvalidLength,
ChecksumMismatch,
}
const MAX_R: usize = 20;
const MAX_B_SLOTS: usize = 2 * MAX_R + 1;
macro_rules! kalyna_kw_variant {
($name:ident, $expanded:ident, $key_bytes:literal, $block_bytes:literal, $half_bytes:literal) => {
#[doc = concat!(
"KW mode over [`super::kalyna::", stringify!($expanded), "`] - see the module doc ",
"comment for the citation, the two deliberate deviations from `dstu7624.c`, and the ",
"misuse warning."
)]
pub struct $name;
impl $name {
pub fn wrap(
key: &[u8; $key_bytes],
plaintext: &[u8],
out: &mut [u8],
) -> Result<(), KwError> {
let cipher = super::kalyna::$expanded::new(key);
Self::wrap_with_cipher(&cipher, plaintext, out)
}
pub fn wrap_with_cipher(
cipher: &super::kalyna::$expanded,
plaintext: &[u8],
out: &mut [u8],
) -> Result<(), KwError> {
if plaintext.is_empty()
|| !plaintext.len().is_multiple_of($block_bytes)
|| plaintext.len() / $block_bytes > MAX_R
|| out.len() != plaintext.len() + $block_bytes
{
return Err(KwError::InvalidLength);
}
let r = plaintext.len() / $block_bytes;
let n = 2 * (r + 1);
let v = (n - 1) * 6;
let mut big_b = [0u8; $half_bytes];
big_b.copy_from_slice(&plaintext[..$half_bytes]);
let mut b = [[0u8; $half_bytes]; MAX_B_SLOTS];
let mut off = $half_bytes;
let mut idx = 0;
while off < plaintext.len() {
b[idx].copy_from_slice(&plaintext[off..off + $half_bytes]);
idx += 1;
off += $half_bytes;
}
for i in 1..=v {
let mut block = [0u8; $block_bytes];
block[..$half_bytes].copy_from_slice(&big_b);
block[$half_bytes..].copy_from_slice(&b[0]);
let r_block = cipher.encrypt_block(&block);
let mut tweaked = r_block;
#[allow(clippy::cast_possible_truncation)] let tweak = (i as u32).to_le_bytes();
for k in 0..4 {
tweaked[$half_bytes + k] ^= tweak[k];
}
big_b.copy_from_slice(&tweaked[$half_bytes..]);
for j in 0..(n - 2) {
b[j] = b[j + 1];
}
b[n - 2].copy_from_slice(&r_block[..$half_bytes]);
}
out[..$half_bytes].copy_from_slice(&big_b);
let mut off = $half_bytes;
for j in 0..(n - 1) {
out[off..off + $half_bytes].copy_from_slice(&b[j]);
off += $half_bytes;
}
Ok(())
}
pub fn unwrap(
key: &[u8; $key_bytes],
ciphertext: &[u8],
out: &mut [u8],
) -> Result<(), KwError> {
let cipher = super::kalyna::$expanded::new(key);
Self::unwrap_with_cipher(&cipher, ciphertext, out)
}
pub fn unwrap_with_cipher(
cipher: &super::kalyna::$expanded,
ciphertext: &[u8],
out: &mut [u8],
) -> Result<(), KwError> {
if ciphertext.len() < 2 * $block_bytes
|| !ciphertext.len().is_multiple_of($block_bytes)
|| (ciphertext.len() / $block_bytes - 1) > MAX_R
|| out.len() != ciphertext.len() - $block_bytes
{
return Err(KwError::InvalidLength);
}
let r = ciphertext.len() / $block_bytes - 1;
let n = 2 * (r + 1);
let v = (n - 1) * 6;
let mut big_b = [0u8; $half_bytes];
big_b.copy_from_slice(&ciphertext[..$half_bytes]);
let mut b = [[0u8; $half_bytes]; MAX_B_SLOTS];
let mut off = $half_bytes;
for idx in 0..(n - 1) {
b[idx].copy_from_slice(&ciphertext[off..off + $half_bytes]);
off += $half_bytes;
}
for i in (1..=v).rev() {
let last = b[n - 2];
let mut block = [0u8; $block_bytes];
block[..$half_bytes].copy_from_slice(&last);
let mut tweaked_b = big_b;
#[allow(clippy::cast_possible_truncation)] let tweak = (i as u32).to_le_bytes();
for k in 0..4 {
tweaked_b[k] ^= tweak[k];
}
block[$half_bytes..].copy_from_slice(&tweaked_b);
let d = cipher.decrypt_block(&block);
big_b.copy_from_slice(&d[..$half_bytes]);
for j in (1..=(n - 2)).rev() {
b[j] = b[j - 1];
}
b[0].copy_from_slice(&d[$half_bytes..]);
}
let zero = [0u8; $half_bytes];
let checksum_ok = b[n - 3].ct_eq(&zero) & b[n - 2].ct_eq(&zero);
if !bool::from(checksum_ok) {
return Err(KwError::ChecksumMismatch);
}
out[..$half_bytes].copy_from_slice(&big_b);
let mut off = $half_bytes;
for j in 0..(n - 3) {
out[off..off + $half_bytes].copy_from_slice(&b[j]);
off += $half_bytes;
}
Ok(())
}
}
};
}
kalyna_kw_variant!(Kalyna128_128Kw, Kalyna128_128ExpandedKey, 16, 16, 8);
kalyna_kw_variant!(Kalyna128_256Kw, Kalyna128_256ExpandedKey, 32, 16, 8);
kalyna_kw_variant!(Kalyna256_256Kw, Kalyna256_256ExpandedKey, 32, 32, 16);
kalyna_kw_variant!(Kalyna256_512Kw, Kalyna256_512ExpandedKey, 64, 32, 16);
kalyna_kw_variant!(Kalyna512_512Kw, Kalyna512_512ExpandedKey, 64, 64, 32);