use cipher::{KeyInit, KeySizeUser, consts::{U8, U32}};
use crate::sbox::{Sbox, SBOX_CRYPTOPRO};
const SEQ_ENCRYPT: [usize; 32] = [
0,1,2,3,4,5,6,7,
0,1,2,3,4,5,6,7,
0,1,2,3,4,5,6,7,
7,6,5,4,3,2,1,0,
];
const SEQ_DECRYPT: [usize; 32] = [
0,1,2,3,4,5,6,7,
7,6,5,4,3,2,1,0,
7,6,5,4,3,2,1,0,
7,6,5,4,3,2,1,0,
];
#[derive(Clone)]
pub struct Gost28147 {
subkeys: [u32; 8],
sbox: Sbox,
}
impl Gost28147 {
pub fn with_sbox(key: &[u8; 32], sbox: &Sbox) -> Self {
let mut subkeys = [0u32; 8];
for i in 0..8 {
subkeys[i] = u32::from_le_bytes(key[4*i..4*i+4].try_into().unwrap());
}
Self { subkeys, sbox: *sbox }
}
#[inline]
fn apply_sbox(&self, val: u32) -> u32 {
let mut result = 0u32;
for i in 0..8 {
let nibble = ((val >> (4 * i)) & 0xF) as usize;
result |= (self.sbox[i][nibble] as u32) << (4 * i);
}
result
}
#[inline]
fn round_fn(&self, n1: u32, k: u32) -> u32 {
self.apply_sbox(n1.wrapping_add(k)).rotate_left(11)
}
fn xcrypt(&self, seq: &[usize; 32], block: &[u8; 8]) -> [u8; 8] {
let mut n1 = u32::from_le_bytes(block[0..4].try_into().unwrap());
let mut n2 = u32::from_le_bytes(block[4..8].try_into().unwrap());
for &ki in seq {
let t = self.round_fn(n1, self.subkeys[ki]);
let new_n1 = t ^ n2;
n2 = n1;
n1 = new_n1;
}
let mut out = [0u8; 8];
out[0..4].copy_from_slice(&n2.to_le_bytes());
out[4..8].copy_from_slice(&n1.to_le_bytes());
out
}
#[inline]
pub fn encrypt_block_raw(&self, block: &[u8; 8]) -> [u8; 8] {
self.xcrypt(&SEQ_ENCRYPT, block)
}
#[inline]
pub fn decrypt_block_raw(&self, block: &[u8; 8]) -> [u8; 8] {
self.xcrypt(&SEQ_DECRYPT, block)
}
}
impl KeySizeUser for Gost28147 {
type KeySize = U32;
}
impl KeyInit for Gost28147 {
fn new(key: &cipher::Key<Self>) -> Self {
let bytes: &[u8] = key.as_ref();
Self::with_sbox(bytes.try_into().unwrap(), &SBOX_CRYPTOPRO)
}
}
cipher::impl_simple_block_encdec!(
<> Gost28147, U8, state, block,
encrypt: {
let pt: [u8; 8] = block.get_in().as_slice().try_into().unwrap();
let ct = state.encrypt_block_raw(&pt);
block.get_out().copy_from_slice(&ct);
}
decrypt: {
let ct: [u8; 8] = block.get_in().as_slice().try_into().unwrap();
let pt = state.decrypt_block_raw(&ct);
block.get_out().copy_from_slice(&pt);
}
);
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
use crate::sbox::{SBOX_CRYPTOPRO, SBOX_TEST};
const KEY1: [u8; 32] = [
0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF,
0xFE,0xDC,0xBA,0x98,0x76,0x54,0x32,0x10,
0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF,
0xFE,0xDC,0xBA,0x98,0x76,0x54,0x32,0x10,
];
const KEY2: [u8; 32] = [0xFF; 32]; const KEY3: [u8; 32] = [0x00; 32];
#[test]
fn test_param_encrypt_zeros() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_TEST);
assert_eq!(
c.encrypt_block_raw(&[0x00;8]),
[0xDB,0x52,0x68,0xBC,0x9D,0x83,0x2A,0xA7]
);
}
#[test]
fn test_param_encrypt_ordered() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_TEST);
assert_eq!(
c.encrypt_block_raw(&[0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF]),
[0xD5,0xC1,0xEE,0x12,0x31,0xD2,0xC7,0x98]
);
}
#[test]
fn cryptopro_encrypt_zeros() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO);
assert_eq!(
c.encrypt_block_raw(&[0x00;8]),
[0x7C,0x43,0x32,0x42,0x6A,0x32,0x4D,0xB8]
);
}
#[test]
fn cryptopro_encrypt_ordered() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO);
assert_eq!(
c.encrypt_block_raw(&[0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF]),
[0x1A,0x27,0x35,0x04,0x3D,0x46,0x10,0x5B]
);
}
#[test]
fn cryptopro_encrypt_all_ff() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO);
assert_eq!(
c.encrypt_block_raw(&[0xFF;8]),
[0x43,0x6A,0xDD,0x97,0xC6,0xD3,0xDC,0xAE]
);
}
#[test]
fn cryptopro_encrypt_zero_key() {
let c = Gost28147::with_sbox(&KEY3, &SBOX_CRYPTOPRO);
assert_eq!(
c.encrypt_block_raw(&[0x00;8]),
[0x8D,0x99,0x78,0x5F,0x93,0x60,0x3F,0xD9]
);
}
#[test]
fn roundtrip_test_param() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_TEST);
for pt in [
[0x00u8;8],
[0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF],
[0xFF;8],
[0xDE,0xAD,0xBE,0xEF,0xCA,0xFE,0xBA,0xBE],
] {
assert_eq!(c.decrypt_block_raw(&c.encrypt_block_raw(&pt)), pt,
"roundtrip failed for {pt:02X?}");
}
}
#[test]
fn roundtrip_cryptopro() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO);
for pt in [
[0x00u8;8],
[0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF],
[0xFF;8],
[0xDE,0xAD,0xBE,0xEF,0xCA,0xFE,0xBA,0xBE],
] {
assert_eq!(c.decrypt_block_raw(&c.encrypt_block_raw(&pt)), pt);
}
}
#[test]
fn trait_encrypt_decrypt_roundtrip() {
let c = Gost28147::new(&KEY1.into());
let pt: [u8; 8] = [0xAA,0xBB,0xCC,0xDD,0x11,0x22,0x33,0x44];
let mut buf = cipher::Block::<Gost28147>::clone_from_slice(&pt);
c.encrypt_block(&mut buf);
assert_ne!(buf.as_slice(), &pt, "ciphertext must differ from plaintext");
c.decrypt_block(&mut buf);
assert_eq!(buf.as_slice(), &pt, "decryption must restore plaintext");
}
#[test]
fn ciphertext_differs_from_plaintext() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO);
let pt = [0x42u8; 8];
assert_ne!(c.encrypt_block_raw(&pt), pt);
}
#[test]
fn different_keys_produce_different_ciphertexts() {
let pt = [0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEFu8];
let ct1 = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO).encrypt_block_raw(&pt);
let ct2 = Gost28147::with_sbox(&KEY2, &SBOX_CRYPTOPRO).encrypt_block_raw(&pt);
let ct3 = Gost28147::with_sbox(&KEY3, &SBOX_CRYPTOPRO).encrypt_block_raw(&pt);
assert_ne!(ct1, ct2);
assert_ne!(ct1, ct3);
assert_ne!(ct2, ct3);
}
#[test]
fn different_sboxes_produce_different_ciphertexts() {
let pt = [0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEFu8];
let ct_test = Gost28147::with_sbox(&KEY1, &SBOX_TEST).encrypt_block_raw(&pt);
let ct_crypto = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO).encrypt_block_raw(&pt);
assert_ne!(ct_test, ct_crypto);
}
#[test]
fn wrong_key_decryption_gives_wrong_plaintext() {
let pt = [0x00u8; 8];
let ct = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO).encrypt_block_raw(&pt);
let wrong_dec = Gost28147::with_sbox(&KEY2, &SBOX_CRYPTOPRO).decrypt_block_raw(&ct);
assert_eq!(wrong_dec, [0x36,0xCC,0x46,0x76,0x7C,0xE9,0x5A,0x4C]);
assert_ne!(wrong_dec, pt);
}
#[test]
fn decrypt_random_ciphertext_not_all_zeros() {
let c = Gost28147::with_sbox(&KEY1, &SBOX_CRYPTOPRO);
let garbage = [0xDE,0xAD,0xBE,0xEF,0xCA,0xFE,0xBA,0xBEu8];
let dec = c.decrypt_block_raw(&garbage);
assert_ne!(dec, [0u8; 8]);
}
}