diff --git a/Cargo.lock b/Cargo.lock index 883a0f9d..07a000a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ dependencies = [ [[package]] name = "belt-block" -version = "0.1.1" +version = "0.1.2" dependencies = [ "cipher", "hex-literal", diff --git a/belt-block/CHANGELOG.md b/belt-block/CHANGELOG.md index 0be19c0f..56bf092e 100644 --- a/belt-block/CHANGELOG.md +++ b/belt-block/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.1.2 (2023-04-15) +### Added +- `belt_wblock_enc`, `belt_wblock_dec`, and `to_u32` functions ([#362]) + +[#362]: https://github.com/RustCrypto/block-ciphers/pull/362 + ## 0.1.1 (2022-09-23) ### Added - `belt_block_raw` function and `cipher` crate feature (enabled by default) ([#333]) diff --git a/belt-block/Cargo.toml b/belt-block/Cargo.toml index 27fa3601..36f8d91b 100644 --- a/belt-block/Cargo.toml +++ b/belt-block/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "belt-block" -version = "0.1.1" +version = "0.1.2" description = "belt-block block cipher implementation" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" diff --git a/belt-block/src/cipher_impl.rs b/belt-block/src/cipher_impl.rs index 974b200b..4a8e5c26 100644 --- a/belt-block/src/cipher_impl.rs +++ b/belt-block/src/cipher_impl.rs @@ -1,4 +1,4 @@ -use crate::{belt_block_raw, g13, g21, g5, key_idx}; +use crate::{belt_block_raw, from_u32, g13, g21, g5, key_idx, to_u32}; use cipher::consts::{U16, U32}; use cipher::{inout::InOut, AlgorithmName, Block, BlockCipher, Key, KeyInit, KeySizeUser}; use core::{fmt, mem::swap, num::Wrapping}; @@ -17,34 +17,25 @@ impl BeltBlock { /// Encryption as described in section 6.1.3 #[inline] fn encrypt(&self, mut block: InOut<'_, '_, Block>) { - let block_in = block.get_in(); // Steps 1 and 4 - let x = [ - get_u32(block_in, 0), - get_u32(block_in, 1), - get_u32(block_in, 2), - get_u32(block_in, 3), - ]; - + let x = to_u32(block.get_in()); let y = belt_block_raw(x, &self.key); let block_out = block.get_out(); // 6) Y ← b ‖ d ‖ a ‖ c - for i in 0..4 { - set_u32(block_out, &y, i); - } + *block_out = from_u32(&y).into(); } /// Decryption as described in section 6.1.4 #[inline] fn decrypt(&self, mut block: InOut<'_, '_, Block>) { let key = &self.key; - let block_in = block.get_in(); + let block_in: [u32; 4] = to_u32(block.get_in()); // Steps 1 and 4 - let mut a = Wrapping(get_u32(block_in, 0)); - let mut b = Wrapping(get_u32(block_in, 1)); - let mut c = Wrapping(get_u32(block_in, 2)); - let mut d = Wrapping(get_u32(block_in, 3)); + let mut a = Wrapping(block_in[0]); + let mut b = Wrapping(block_in[1]); + let mut c = Wrapping(block_in[2]); + let mut d = Wrapping(block_in[3]); // Step 5 for i in (1..9).rev() { @@ -77,9 +68,7 @@ impl BeltBlock { let block_out = block.get_out(); // 6) 𝑋 ← c ‖ a ‖ d ‖ b let x = [c.0, a.0, d.0, b.0]; - for i in 0..4 { - set_u32(block_out, &x, i); - } + *block_out = from_u32(&x).into(); } } @@ -91,18 +80,7 @@ impl KeySizeUser for BeltBlock { impl KeyInit for BeltBlock { fn new(key: &Key) -> Self { - Self { - key: [ - get_u32(key, 0), - get_u32(key, 1), - get_u32(key, 2), - get_u32(key, 3), - get_u32(key, 4), - get_u32(key, 5), - get_u32(key, 6), - get_u32(key, 7), - ], - } + Self { key: to_u32(key) } } } @@ -133,13 +111,3 @@ cipher::impl_simple_block_encdec!( cipher.decrypt(block); } ); - -#[inline(always)] -fn get_u32(block: &[u8], i: usize) -> u32 { - u32::from_le_bytes(block[4 * i..][..4].try_into().unwrap()) -} - -#[inline(always)] -fn set_u32(block: &mut [u8], val: &[u32; 4], i: usize) { - block[4 * i..][..4].copy_from_slice(&val[i].to_le_bytes()); -} diff --git a/belt-block/src/lib.rs b/belt-block/src/lib.rs index 33f4c37b..3baf0679 100644 --- a/belt-block/src/lib.rs +++ b/belt-block/src/lib.rs @@ -99,3 +99,103 @@ pub fn belt_block_raw(x: [u32; 4], key: &[u32; 8]) -> [u32; 4] { // Step 6 [b.0, d.0, a.0, c.0] } + +const BLOCK_SIZE: usize = 16; +type Block = [u8; BLOCK_SIZE]; + +/// Wide block encryption as described in section 6.2.3 of the standard. +/// +/// Returns [`InvalidLengthError`] if `data` is smaller than 32 bytes. +#[inline] +pub fn belt_wblock_enc(data: &mut [u8], key: &[u32; 8]) -> Result<(), InvalidLengthError> { + if data.len() < 2 * BLOCK_SIZE { + return Err(InvalidLengthError); + } + + let len = data.len(); + let n = (len + BLOCK_SIZE - 1) / BLOCK_SIZE; + for i in 1..(2 * n + 1) { + let s = data[..len - 1] + .chunks_exact(BLOCK_SIZE) + .fold(Block::default(), xor); + + data.copy_within(BLOCK_SIZE.., 0); + let (tail1, tail2) = data[len - 2 * BLOCK_SIZE..].split_at_mut(BLOCK_SIZE); + tail2.copy_from_slice(&s); + + let s = belt_block_raw(to_u32(&s), key); + xor_set(tail1, &from_u32::<16>(&s)); + xor_set(tail1, &i.to_le_bytes()); + } + + Ok(()) +} + +/// Wide block decryption as described in section 6.2.4 of the standard. +/// +/// Returns [`InvalidLengthError`] if `data` is smaller than 32 bytes. +#[inline] +pub fn belt_wblock_dec(data: &mut [u8], key: &[u32; 8]) -> Result<(), InvalidLengthError> { + if data.len() < 2 * BLOCK_SIZE { + return Err(InvalidLengthError); + } + + let len = data.len(); + let n = (len + BLOCK_SIZE - 1) / BLOCK_SIZE; + for i in (1..(2 * n + 1)).rev() { + let tail_pos = len - BLOCK_SIZE; + let s = Block::try_from(&data[tail_pos..]).unwrap(); + data.copy_within(..tail_pos, BLOCK_SIZE); + + let s_enc = belt_block_raw(to_u32(&s), key); + xor_set(&mut data[tail_pos..], &from_u32::<16>(&s_enc)); + xor_set(&mut data[tail_pos..], &i.to_le_bytes()); + + let r1 = data[..len - 1] + .chunks_exact(BLOCK_SIZE) + .skip(1) + .fold(s, xor); + data[..BLOCK_SIZE].copy_from_slice(&r1); + } + Ok(()) +} + +/// Error used when data smaller than 32 bytes is passed to the `belt-wblock` functions. +#[derive(Debug, Copy, Clone)] +pub struct InvalidLengthError; + +/// Helper function for transforming BelT keys and blocks from a byte array +/// to an array of `u32`s. +/// +/// # Panics +/// If length of `src` is not equal to `4 * N`. +#[inline(always)] +pub fn to_u32(src: &[u8]) -> [u32; N] { + assert_eq!(src.len(), 4 * N); + let mut res = [0u32; N]; + res.iter_mut() + .zip(src.chunks_exact(4)) + .for_each(|(dst, src)| *dst = u32::from_le_bytes(src.try_into().unwrap())); + res +} + +#[inline(always)] +fn from_u32(src: &[u32]) -> [u8; N] { + assert_eq!(N, 4 * src.len()); + let mut res = [0u8; N]; + res.chunks_exact_mut(4) + .zip(src.iter()) + .for_each(|(dst, src)| dst.copy_from_slice(&src.to_le_bytes())); + res +} + +#[inline(always)] +fn xor_set(block: &mut [u8], val: &[u8]) { + block.iter_mut().zip(val.iter()).for_each(|(a, b)| *a ^= b); +} + +#[inline(always)] +fn xor(mut block: Block, val: &[u8]) -> Block { + block.iter_mut().zip(val.iter()).for_each(|(a, b)| *a ^= b); + block +} diff --git a/belt-block/tests/mod.rs b/belt-block/tests/mod.rs index 00dbf2b8..d0135e05 100644 --- a/belt-block/tests/mod.rs +++ b/belt-block/tests/mod.rs @@ -1,42 +1,33 @@ +//! Example vectors from STB 34.101.31 (2020): +//! http://apmi.bsu.by/assets/files/std/belt-spec371.pdf +use belt_block::{belt_block_raw, belt_wblock_dec, belt_wblock_enc, to_u32}; #[cfg(feature = "cipher")] -use belt_block::BeltBlock; -#[cfg(feature = "cipher")] -use cipher::{BlockDecrypt, BlockEncrypt, KeyInit}; +use belt_block::{ + cipher::{BlockDecrypt, BlockEncrypt, KeyInit}, + BeltBlock, +}; use hex_literal::hex; -fn get_u32(block: &[u8], i: usize) -> u32 { - u32::from_le_bytes(block[4 * i..][..4].try_into().unwrap()) -} - -/// Example vectors from STB 34.101.31 (2020): -/// http://apmi.bsu.by/assets/files/std/belt-spec371.pdf #[test] fn belt_block() { // Table A.1 - let key1 = hex!("E9DEE72C 8F0C0FA6 2DDB49F4 6F739647 06075316 ED247A37 39CBA383 03A98BF6"); + let key1 = hex!( + "E9DEE72C 8F0C0FA6 2DDB49F4 6F739647" + "06075316 ED247A37 39CBA383 03A98BF6" + ); let pt1 = hex!("B194BAC8 0A08F53B 366D008E 584A5DE4"); let ct1 = hex!("69CCA1C9 3557C9E3 D66BC3E0 FA88FA6E"); // Table A.2 - let key2 = hex!("92BD9B1C E5D14101 5445FBC9 5E4D0EF2 682080AA 227D642F 2687F934 90405511"); + let key2 = hex!( + "92BD9B1C E5D14101 5445FBC9 5E4D0EF2" + "682080AA 227D642F 2687F934 90405511" + ); let pt2 = hex!("0DC53006 00CAB840 B38448E5 E993F421"); let ct2 = hex!("E12BDC1A E28257EC 703FCCF0 95EE8DF1"); for (key, pt, ct) in [(key1, pt1, ct1), (key2, pt2, ct2)] { - let mut k = [0u32; 8]; - for i in 0..8 { - k[i] = get_u32(&key, i); - } - let mut x = [0u32; 4]; - for i in 0..4 { - x[i] = get_u32(&pt, i); - } - let mut y = [0u32; 4]; - for i in 0..4 { - y[i] = get_u32(&ct, i); - } - - let res = belt_block::belt_block_raw(x, &k); - assert_eq!(res, y); + let res = belt_block_raw(to_u32(&pt), &to_u32(&key)); + assert_eq!(res, to_u32(&ct)); #[cfg(feature = "cipher")] { @@ -49,3 +40,87 @@ fn belt_block() { } } } + +#[test] +fn belt_wblock() { + // Table A.6 + let k1 = hex!( + "E9DEE72C 8F0C0FA6 2DDB49F4 6F739647" + "06075316 ED247A37 39CBA383 03A98BF6" + ); + let x1 = hex!( + "B194BAC8 0A08F53B 366D008E 584A5DE4" + "8504FA9D 1BB6C7AC 252E72C2 02FDCE0D" + "5BE3D612 17B96181 FE6786AD 716B890B" + ); + let y1 = hex!( + "49A38EE1 08D6C742 E52B774F 00A6EF98" + "B106CBD1 3EA4FB06 80323051 BC04DF76" + "E487B055 C69BCF54 1176169F 1DC9F6C8" + ); + let x2 = hex!( + "B194BAC8 0A08F53B 366D008E 584A5DE4" + "8504FA9D 1BB6C7AC 252E72C2 02FDCE0D" + "5BE3D612 17B96181 FE6786AD 716B89" + ); + let y2 = hex!( + "F08EF22D CAA06C81 FB127219 74221CA7" + "AB82C628 56FCF2F9 FCA006E0 19A28F16" + "E5821A51 F5735946 25DBAB8F 6A5C94" + ); + + // Table A.7 + let k2 = hex!( + "92BD9B1C E5D14101 5445FBC9 5E4D0EF2" + "682080AA 227D642F 2687F934 90405511" + ); + let y3 = hex!( + "E12BDC1A E28257EC 703FCCF0 95EE8DF1" + "C1AB7638 9FE678CA F7C6F860 D5BB9C4F" + "F33C657B 637C306A DD4EA779 9EB23D31" + ); + let x3 = hex!( + "92632EE0 C21AD9E0 9A39343E 5C07DAA4" + "889B03F2 E6847EB1 52EC99F7 A4D9F154" + "B5EF68D8 E4A39E56 7153DE13 D72254EE" + ); + let x4 = hex!( + "DF3F8822 30BAAFFC 92F05660 32117231" + "0E3CB218 2681EF43 102E6717 5E177BD7" + "5E93E4E8" + ); + let y4 = hex!( + "E12BDC1A E28257EC 703FCCF0 95EE8DF1" + "C1AB7638 9FE678CA F7C6F860 D5BB9C4F" + "F33C657B" + ); + + let tests = [ + (k1, &x1[..], &y1[..]), + (k1, &x2[..], &y2[..]), + (k2, &x3[..], &y3[..]), + (k2, &x4[..], &y4[..]), + ]; + for (key, x, y) in tests { + let k = to_u32(&key); + let mut t = x.to_vec(); + belt_wblock_enc(&mut t, &k).unwrap(); + assert_eq!(t, y); + belt_wblock_dec(&mut t, &k).unwrap(); + assert_eq!(t, x) + } + + // synthetic round-trip tests + let k = to_u32(&k1); + let x: Vec = (0u8..255).collect(); + for i in 32..x.len() { + let mut t = x[..i].to_vec(); + for _ in 0..16 { + belt_wblock_enc(&mut t, &k).unwrap(); + } + for _ in 0..16 { + belt_wblock_dec(&mut t, &k).unwrap(); + } + assert_eq!(t, x[..i]); + } +}