Skip to main content

rc5_block/
rc5.rs

1use crate::{BlockCipher, Reason, Version, Word, bail};
2
3/// # RC5 control block
4///
5/// This struct represents the internal state of an RC5 cipher with a given word size.
6/// It encapsulates key expansion (`S` table), the number of rounds, and a computed version tag.
7///
8/// Parameters:
9///
10/// - `W`: The word type (e.g., `u16`, `u32`, `u64`), which must implement the [`Word`] trait.
11pub struct RC5ControlBlock<W: Word> {
12    /// RC5 parametric version
13    version: Version,
14
15    /// RC5 key, which holds the raw key and its
16    /// expanded `S-Table`
17    key: RC5Key<W>,
18
19    /// Defines the number of iterations during
20    /// encryption.
21    rounds: usize,
22}
23
24impl<W: Word> RC5ControlBlock<W> {
25    /// RC5 constructor method
26    ///
27    /// It creates an instance of an RC5 control block from
28    /// raw key and rounds.
29    ///
30    /// Returns a result type containing the control block.
31    pub fn new<K>(key: K, rounds: usize) -> Result<Self, Reason>
32    where
33        K: AsRef<[u8]>,
34    {
35        let key = RC5Key::from_raw(key, rounds)?;
36        Ok(Self {
37            rounds,
38            version: Version::from_parametric_vector(vec![
39                1,
40                (W::BYTES * 8) as u8,
41                rounds as u8,
42                key.raw_len() as u8,
43            ]),
44            key,
45        })
46    }
47
48    /// Returns a reference to the expanded S table used for round keys.
49    #[inline]
50    pub fn s_table(&self) -> &[W] {
51        &self.key.s_table
52    }
53
54    // Returns the configured number of RC5 rounds.
55    #[inline]
56    pub fn rounds(&self) -> usize {
57        self.rounds
58    }
59
60    /// Returns a string describing the parametric RC5 version,
61    /// e.g. `"RC5-32/12/16"`.
62    #[inline]
63    pub fn parametric_version(&self) -> String {
64        self.version.version()
65    }
66}
67
68impl<W: Word> BlockCipher<W, 2> for RC5ControlBlock<W> {
69    fn encrypt(&self, pt: [W; 2]) -> [W; 2] {
70        let expanded_key = self.s_table();
71        let [mut word_a, mut word_b] = pt;
72
73        word_a = word_a.wrapping_add(expanded_key[0]);
74        word_b = word_b.wrapping_add(expanded_key[1]);
75
76        for r in 1..=self.rounds() {
77            word_a = ((word_a ^ word_b).rotate_left(word_b)).wrapping_add(expanded_key[2 * r]);
78            word_b = ((word_b ^ word_a).rotate_left(word_a)).wrapping_add(expanded_key[2 * r + 1]);
79        }
80
81        [word_a, word_b]
82    }
83
84    fn decrypt(&self, ct: [W; 2]) -> [W; 2] {
85        let expanded_key = self.s_table();
86        let [mut word_a, mut word_b] = ct;
87
88        for r in (1..=self.rounds()).rev() {
89            word_b = (word_b
90                .wrapping_sub(expanded_key[2 * r + 1])
91                .rotate_right(word_a))
92                ^ word_a;
93
94            word_a = (word_a
95                .wrapping_sub(expanded_key[2 * r])
96                .rotate_right(word_b))
97                ^ word_b;
98        }
99
100        word_b = word_b.wrapping_sub(expanded_key[1]);
101        word_a = word_a.wrapping_sub(expanded_key[0]);
102
103        [word_a, word_b]
104    }
105
106    fn generate_blocks(&self, pt: Vec<u8>) -> Vec<[W; 2]> {
107        let mut blocks = Vec::with_capacity(pt.len() / self.block_size());
108        for chunks in pt.chunks_exact(self.block_size()) {
109            blocks.push([
110                W::from_bytes_slice(&chunks[..W::BYTES]).unwrap(),
111                W::from_bytes_slice(&chunks[W::BYTES..]).unwrap(),
112            ]);
113        }
114
115        blocks
116    }
117
118    fn generate_bytes_stream(&self, blocks: Vec<[W; 2]>) -> Vec<u8> {
119        let mut stream = Vec::with_capacity(blocks.len() * self.block_size());
120        for blcok in blocks.iter() {
121            stream.extend_from_slice(&blcok[0].to_bytes_slice());
122            stream.extend_from_slice(&blcok[1].to_bytes_slice());
123        }
124        stream
125    }
126
127    fn control_block_version(&self) -> String {
128        self.parametric_version()
129    }
130
131    fn block_size(&self) -> usize {
132        W::BYTES * 2
133    }
134
135    fn word_size(&self) -> usize {
136        W::BYTES
137    }
138}
139
140const MAX_ROUNDS: usize = 255;
141const MAX_KEY_BYTES: usize = 255;
142
143/// # RC5Key
144///
145/// Internal rc5 key  container which holds the raw key as
146/// well as expanded s-table of the raw key.
147pub struct RC5Key<W: Word> {
148    raw_key: Vec<u8>,
149    s_table: Vec<W>,
150}
151
152impl<W: Word> RC5Key<W> {
153    /// Creates RC5Key from raw key bytes.
154    /// Reutrns a result type containing Key or an err.
155    pub fn from_raw<K>(raw: K, rounds: usize) -> Result<Self, Reason>
156    where
157        K: AsRef<[u8]>,
158    {
159        let key_bytes = raw.as_ref();
160
161        bail!(
162            key_bytes.is_empty(),
163            Reason::InvalidKey,
164            key_bytes.len() > MAX_KEY_BYTES,
165            Reason::KeyTooLong {
166                current: key_bytes.len(),
167                supported: MAX_KEY_BYTES
168            },
169            rounds > MAX_ROUNDS,
170            Reason::InvalidRounds(rounds)
171        );
172
173        Ok(Self {
174            s_table: expand_key::<W>(key_bytes, rounds),
175            raw_key: key_bytes.to_vec(),
176        })
177    }
178
179    pub fn raw_len(&self) -> usize {
180        self.raw_key.len()
181    }
182}
183
184/// RC5 key expansion function.
185///
186/// Converts a user-supplied key into an expanded S-table using the RC5 mixing algorithm
187/// in little-endian byte order. This table will be used for all encryption and decryption
188/// operations.
189///
190/// see more: [RC5-paper](https://www.grc.com/r&d/rc5.pdf)
191///
192/// # Parameters
193/// - `key`: raw key bytes.
194/// - `rounds`: number of RC5 rounds.
195///
196/// # Returns
197/// A vector containing the expanded key schedule.
198fn expand_key<W: Word>(key: &[u8], rounds: usize) -> Vec<W> {
199    let word_bytes = W::BYTES;
200    let key_length = key.len().max(1);
201
202    let expanded_length = key_length.div_ceil(word_bytes);
203    let mut key_words = vec![W::ZERO; expanded_length];
204
205    // Packing the raw bytes ino list of word-szie
206    // in a little-endian byte order
207    for index in (0..key_length).rev() {
208        let ix = index / word_bytes;
209        key_words[ix] = key_words[ix]
210            .rotate_left(W::from_u8(8))
211            .wrapping_add(W::from_u8(key[index]));
212    }
213
214    let table_size = 2 * (rounds + 1);
215    let mut s_table = vec![W::ZERO; table_size];
216
217    s_table[0] = W::P;
218
219    // Key flattening with magic constants
220    for i in 1..table_size {
221        s_table[i] = s_table[i - 1].wrapping_add(W::Q);
222    }
223
224    let (mut i, mut j) = (0, 0);
225    let (mut a, mut b) = (W::ZERO, W::ZERO);
226
227    // Key mixing, this is done by interlacing key words with
228    // each other.
229    for _ in 0..(3 * table_size.max(expanded_length)) {
230        a = s_table[i]
231            .wrapping_add(a)
232            .wrapping_add(b)
233            .rotate_left(W::from_u8(3));
234
235        b = key_words[j]
236            .wrapping_add(a)
237            .wrapping_add(b)
238            .rotate_left(a.wrapping_add(b));
239
240        s_table[i] = a;
241        key_words[j] = b;
242
243        i = (i + 1) % table_size;
244        j = (j + 1) % expanded_length;
245    }
246
247    s_table
248}