Skip to main content

belt_block/
lib.rs

1//! Pure Rust implementation of the [BelT] block cipher specified in
2//! [STB 34.101.31-2020].
3//!
4//! # ⚠️ Security Warning: Hazmat!
5//!
6//! This crate implements only the low-level block cipher function, and is intended
7//! for use for implementing higher-level constructions *only*. It is NOT
8//! intended for direct use in applications.
9//!
10//! USE AT YOUR OWN RISK!
11//!
12//! [BelT]: https://ru.wikipedia.org/wiki/BelT
13//! [STB 34.101.31-2020]: http://apmi.bsu.by/assets/files/std/belt-spec371.pdf
14
15#![no_std]
16#![doc(
17    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
18    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
19)]
20#![cfg_attr(docsrs, feature(doc_cfg))]
21#![warn(missing_docs, rust_2018_idioms)]
22#![forbid(unsafe_code)]
23
24#[cfg(feature = "cipher")]
25pub use cipher;
26
27use crate::consts::{H5, H13, H21, H29};
28use core::{mem::swap, num::Wrapping};
29
30#[cfg(feature = "cipher")]
31mod cipher_impl;
32mod consts;
33
34#[cfg(feature = "cipher")]
35pub use cipher_impl::BeltBlock;
36
37macro_rules! g {
38    ($($name:ident: ($a:expr, $b:expr, $c:expr, $d:expr)),+) => {
39        $(
40            #[inline]
41            fn $name(Wrapping(u): Wrapping<u32>) -> Wrapping<u32> {
42                Wrapping($a[((u >> 24) & 0xFF) as usize]
43                    ^ $b[((u >> 16) & 0xFF) as usize]
44                    ^ $c[((u >> 8) & 0xFF) as usize]
45                    ^ $d[(u & 0xFF) as usize])
46            }
47        )+
48    }
49}
50
51g!(
52    g5: (H29, H21, H13, H5),
53    g13: (H5, H29, H21, H13),
54    g21: (H13, H5, H29, H21)
55);
56
57#[inline(always)]
58fn key_idx(key: &[u32; 8], i: usize, delta: usize) -> Wrapping<u32> {
59    Wrapping(key[(7 * i - delta - 1) % 8])
60}
61
62/// Raw BelT block encryption function used for implementation of
63/// higher-level algorithms.
64#[inline(always)]
65pub fn belt_block_raw(x: [u32; 4], key: &[u32; 8]) -> [u32; 4] {
66    let mut a = Wrapping(x[0]);
67    let mut b = Wrapping(x[1]);
68    let mut c = Wrapping(x[2]);
69    let mut d = Wrapping(x[3]);
70
71    // Step 5
72    for i in 1..9 {
73        // 5.1) b ← b ⊕ G₅(a ⊞ k[7i-6])
74        b ^= g5(a + key_idx(key, i, 6));
75        // 5.2) c ← c ⊕ G₂₁(d ⊞ k[7i-5])
76        c ^= g21(d + key_idx(key, i, 5));
77        // 5.3) a ← a ⊟ G₁₃(b ⊞ k[7i-4])
78        a -= g13(b + key_idx(key, i, 4));
79        // 5.4) e ← G₂₁(b ⊞ c ⊞ k[7i-3]) ⊕ ⟨i⟩₃₂ ;
80        let e = g21(b + c + key_idx(key, i, 3)) ^ Wrapping(i as u32);
81        // 5.5) b ← b ⊞ e
82        b += e;
83        // 5.6) c ← c ⊟ e
84        c -= e;
85        // 5.7) d ← d ⊞ G₁₃(c ⊞ 𝑘[7i-2])
86        d += g13(c + key_idx(key, i, 2));
87        // 5.8) b ← b ⊕ G₂₁(a ⊞ 𝑘[(7i-1])
88        b ^= g21(a + key_idx(key, i, 1));
89        // 5.9) c ← c ⊕ G₅(d ⊞ 𝑘[7i])
90        c ^= g5(d + key_idx(key, i, 0));
91        // 5.10) a ↔ b
92        swap(&mut a, &mut b);
93        // 5.11) c ↔ d
94        swap(&mut c, &mut d);
95        // 5.12) b ↔ c
96        swap(&mut b, &mut c);
97    }
98
99    // Step 6
100    [b.0, d.0, a.0, c.0]
101}
102
103const BLOCK_SIZE: usize = 16;
104type Block = [u8; BLOCK_SIZE];
105
106/// Wide block encryption as described in section 6.2.3 of the standard.
107///
108/// Returns [`InvalidLengthError`] if `data` is smaller than 32 bytes.
109#[inline]
110pub fn belt_wblock_enc(data: &mut [u8], key: &[u32; 8]) -> Result<(), InvalidLengthError> {
111    if data.len() < 2 * BLOCK_SIZE {
112        return Err(InvalidLengthError);
113    }
114
115    let len = data.len();
116    let n = len.div_ceil(BLOCK_SIZE);
117    for i in 1..(2 * n + 1) {
118        let s = data[..len - 1]
119            .chunks_exact(BLOCK_SIZE)
120            .fold(Block::default(), xor);
121
122        data.copy_within(BLOCK_SIZE.., 0);
123        let (tail1, tail2) = data[len - 2 * BLOCK_SIZE..].split_at_mut(BLOCK_SIZE);
124        tail2.copy_from_slice(&s);
125
126        let s = belt_block_raw(to_u32(&s), key);
127        xor_set(tail1, &from_u32::<16>(&s));
128        xor_set(tail1, &i.to_le_bytes());
129    }
130
131    Ok(())
132}
133
134/// Wide block decryption as described in section 6.2.4 of the standard.
135///
136/// Returns [`InvalidLengthError`] if `data` is smaller than 32 bytes.
137#[inline]
138pub fn belt_wblock_dec(data: &mut [u8], key: &[u32; 8]) -> Result<(), InvalidLengthError> {
139    if data.len() < 2 * BLOCK_SIZE {
140        return Err(InvalidLengthError);
141    }
142
143    let len = data.len();
144    let n = len.div_ceil(BLOCK_SIZE);
145    for i in (1..(2 * n + 1)).rev() {
146        let tail_pos = len - BLOCK_SIZE;
147        let s = Block::try_from(&data[tail_pos..]).unwrap();
148        data.copy_within(..tail_pos, BLOCK_SIZE);
149
150        let s_enc = belt_block_raw(to_u32(&s), key);
151        xor_set(&mut data[tail_pos..], &from_u32::<16>(&s_enc));
152        xor_set(&mut data[tail_pos..], &i.to_le_bytes());
153
154        let r1 = data[..len - 1]
155            .chunks_exact(BLOCK_SIZE)
156            .skip(1)
157            .fold(s, xor);
158        data[..BLOCK_SIZE].copy_from_slice(&r1);
159    }
160    Ok(())
161}
162
163/// Error used when data smaller than 32 bytes is passed to the `belt-wblock` functions.
164#[derive(Debug, Copy, Clone)]
165pub struct InvalidLengthError;
166
167/// Helper function for transforming BelT keys and blocks from a byte array
168/// to an array of `u32`s.
169///
170/// # Panics
171/// If length of `src` is not equal to `4 * N`.
172#[inline(always)]
173fn to_u32<const N: usize>(src: &[u8]) -> [u32; N] {
174    assert_eq!(src.len(), 4 * N);
175    let mut res = [0u32; N];
176    res.iter_mut()
177        .zip(src.chunks_exact(4))
178        .for_each(|(dst, src)| *dst = u32::from_le_bytes(src.try_into().unwrap()));
179    res
180}
181
182#[inline(always)]
183fn from_u32<const N: usize>(src: &[u32]) -> [u8; N] {
184    assert_eq!(N, 4 * src.len());
185    let mut res = [0u8; N];
186    res.chunks_exact_mut(4)
187        .zip(src.iter())
188        .for_each(|(dst, src)| dst.copy_from_slice(&src.to_le_bytes()));
189    res
190}
191
192#[inline(always)]
193fn xor_set(block: &mut [u8], val: &[u8]) {
194    block.iter_mut().zip(val.iter()).for_each(|(a, b)| *a ^= b);
195}
196
197#[inline(always)]
198fn xor(mut block: Block, val: &[u8]) -> Block {
199    block.iter_mut().zip(val.iter()).for_each(|(a, b)| *a ^= b);
200    block
201}