rc5_block/lib.rs
1//! # RC5-RS Cipher Library
2//!
3//! This crate provides a generic, parametric implementation of the RC5 block cipher,
4//! supporting variable word sizes (`u16`, `u32`, `u64`) and multiple modes of operation
5//! (ECB, CBC, CTR). It includes PKCS#7 padding helpers, IV/nonce generators, and
6//! convenient parsing of hex‐encoded parameters.
7//!
8//! ## Features
9//!
10//! - Variable word length: `16-bit`, `32-bit`, `64-bit`.
11//! - Various operation modes:
12//! - ECB
13//! - CBC
14//! - CTR
15//! - Strict padding using PKCS#7 standard.
16//! - Pseudo-random IV/nonce generation utitlities , see [random_iv], [random_nonce_and_counter].
17//! - Hex‐string parsing for IVs and nonces.
18//!
19//! ## Example
20//!
21//! ```rust
22//! use rc5_block::{rc5_cipher, OperationMode};
23//!
24//! // Build a 32‐bit word RC5 cipher with 12 rounds:
25//! let cipher = rc5_cipher::<u32>(b"mykey", 12).unwrap();
26//!
27//! let plaintext = b"Secret message";
28//!
29//! // Encrypt in CBC mode with a random IV:
30//! let iv = rc5_block::random_iv::<u32, 2>();
31//! let ciphertext = cipher.encrypt(plaintext, OperationMode::CBC { iv }).unwrap();
32//!
33//! // Decrypt using the same IV:
34//! let recovered = cipher.decrypt(&ciphertext, OperationMode::CBC { iv }).unwrap();
35//! assert_eq!(recovered, plaintext);
36//! ```
37//!
38//! # Utilities
39//!
40//! This crate provide some extra utilities such as, pseudo-random iv and nonce
41//! generation and PKCS#7 padding function:
42//!
43//! ```rust
44//! // generate a pseudo-random iv-block of block size [u32;2]
45//! let iv = rc5_block::random_iv::<u32, 2>();
46//!
47//! // generate a pseudo-random nonce and counter initialized to zero
48//! // of block size [u32;2]
49//! // Note: Higher part of this block conatins nonce and lower part
50//! // contains counter with initial value set to zero.
51//! let nonce_counter = rc5_block::random_nonce_and_counter::<u32, 2>();
52//! ```
53use hex::FromHexError;
54use std::marker::PhantomData;
55use thiserror::Error;
56
57pub use crate::{
58 modes::OperationMode,
59 rc5::RC5ControlBlock,
60 types::{Version, Word},
61 utils::{pkcs7, random_iv, random_nonce_and_counter},
62};
63
64mod modes;
65mod rc5;
66mod types;
67mod utils;
68
69#[cfg(test)]
70mod tests;
71
72/// Errors returned by the Cipher as reasons during
73/// cipher operations.
74#[derive(Error, Debug)]
75pub enum Reason {
76 #[error("[RC5-Error] Word size mis-match")]
77 WordSize,
78 #[error("[RC5-Error] Invalid PKCS7 padding shceme")]
79 Padding,
80 #[error("[RC5-Error] RC5 key is too long, supported: {supported:?} max, current: {current:?}")]
81 KeyTooLong { current: usize, supported: usize },
82 #[error("[RC5-Error] Invalid RC5-key, received an empty key")]
83 InvalidKey,
84 #[error("[RC5-Error] Rounds out-of-bounds, must be within 0-255, current{0}")]
85 InvalidRounds(usize),
86 #[error("[RC5-Error] Unable to parse Hex-String {0}")]
87 ParseHex(#[from] FromHexError),
88 #[error("[RC5-Error] IV hex string should be equal to block size {0} bytes")]
89 IVinvalid(usize),
90 #[error("[RC5-Error] Nonce/Counter hex string should be equal to word-size {0} bytes")]
91 NonceInvalid(usize),
92}
93
94/// # Cipher
95///
96/// A high‐level cipher wrapper type that contains a control block
97/// It provides byte‐stream handling and cryptographic operation
98/// modes dispatch.
99///
100/// ## Generics
101///
102/// - `B`: Control-Block, e.g. [`RC5ControlBlock<W>`].
103/// - `W`: Underlying type which implements a [Word] trait.
104/// - `N`: number of words per block (for RC5, always 2).
105///
106pub struct Cipher<B, W, const N: usize>
107where
108 W: Word,
109 B: BlockCipher<W, N>,
110{
111 block: B,
112 _marker: PhantomData<W>,
113}
114
115impl<B, W, const N: usize> Cipher<B, W, N>
116where
117 W: Word,
118 B: BlockCipher<W, N>,
119{
120 /// Create a new `Cipher` wrapping the given block‐cipher instance.
121 ///
122 /// ## Example
123 ///
124 /// ```rust
125 /// use rc5_block::{RC5ControlBlock, Cipher};
126 ///
127 /// let rc5_control_block = RC5ControlBlock::<u32>::new("SECRET_KEY", 12).unwrap();
128 /// let cipher = Cipher::new(rc5_control_block);
129 /// ```
130 pub fn new(block: B) -> Self {
131 Self {
132 block,
133 _marker: PhantomData,
134 }
135 }
136
137 /// Encrypt plain-text bytes under selected cryptographic operation mode
138 /// and returns cipher-text bytes.
139 ///
140 /// This takes the plain-text as their bytes reference. It supports various
141 /// encryption flows based on operation modes such as:
142 ///
143 /// - `ECB` : Electronic-code-book mode.
144 /// - `CBC` : Cipher-block-chain mode.
145 /// - `CTR` : Counter mode.
146 ///
147 /// Encryption might fail for various reasons, either due to padding or etc,
148 /// that's why this function is fallible.
149 ///
150 /// It returns ciphered bytes, or [Reason] of failure as an err.
151 pub fn encrypt(&self, pt: &[u8], mode: OperationMode<W, N>) -> Result<Vec<u8>, Reason> {
152 let mut pt = pt.to_vec();
153
154 match mode {
155 modes::OperationMode::ECB => {
156 let bs = self.block.block_size();
157 utils::pkcs7(&mut pt, bs, true)?;
158 let pt_blocks = self.block.generate_blocks(pt);
159 let ct_blocks = modes::ecb_encrypt(&self.block, pt_blocks);
160
161 Ok(self.block.generate_bytes_stream(ct_blocks))
162 }
163 modes::OperationMode::CBC { iv } => {
164 let bs = self.block.block_size();
165 utils::pkcs7(&mut pt, bs, true)?;
166 let pt_blocks = self.block.generate_blocks(pt);
167 let ct_blocks = modes::cbc_encrypt(&self.block, iv, pt_blocks);
168
169 Ok(self.block.generate_bytes_stream(ct_blocks))
170 }
171 modes::OperationMode::CTR { nonce_and_counter } => {
172 Ok(modes::ctr_encrypt(&self.block, nonce_and_counter, &pt))
173 }
174 }
175 }
176
177 /// Decrypt cipher-text bytes under selected cryptographic operation mode
178 /// and returns plain-text bytes.
179 ///
180 /// This takes the plain-text as their bytes reference. It supports various
181 /// encryption flows based on operation modes such as:
182 ///
183 /// - `ECB` : Electronic-code-book mode.
184 /// - `CBC` : Cipher-block-chain mode.
185 /// - `CTR` : Counter mode.
186 ///
187 /// Decryption might fail for various reasons, either due to padding or etc,
188 /// that's why this function is fallible.
189 ///
190 /// It returns plain bytes, or [Reason] of failure as an err.
191 pub fn decrypt(&self, ct: &[u8], mode: OperationMode<W, N>) -> Result<Vec<u8>, Reason> {
192 let ct = ct.to_vec();
193
194 let deciphered_bytes = match mode {
195 OperationMode::ECB => {
196 let ct_blocks = self.block.generate_blocks(ct);
197
198 let bs = self.block.block_size();
199 let pt_blocks = modes::ecb_decrypt(&self.block, ct_blocks);
200 let mut pt_bytes = self.block.generate_bytes_stream(pt_blocks);
201 utils::pkcs7(&mut pt_bytes, bs, false)?;
202
203 pt_bytes
204 }
205 OperationMode::CBC { iv } => {
206 let ct_blocks = self.block.generate_blocks(ct);
207
208 let bs = self.block.block_size();
209 let pt_blocks = modes::cbc_decrypt(&self.block, iv, ct_blocks);
210 let mut pt_bytes = self.block.generate_bytes_stream(pt_blocks);
211 utils::pkcs7(&mut pt_bytes, bs, false)?;
212
213 pt_bytes
214 }
215 OperationMode::CTR { nonce_and_counter } => {
216 modes::ctr_decrypt(&self.block, nonce_and_counter, &ct)
217 }
218 };
219
220 Ok(deciphered_bytes)
221 }
222
223 /// Parse an IV from a hex‐encoded string, validating length = block size.
224 /// Parsing may fail if the hex-string is not equal to blcok size.
225 ///
226 /// Returns a result contain iv block or failure reason as an err.
227 pub fn parse_iv_from_hex<V>(&self, iv_hex: V) -> Result<[W; N], Reason>
228 where
229 V: AsRef<[u8]>,
230 {
231 let iv_bytes = hex::decode(iv_hex)?;
232 let bs = self.control_block().block_size();
233 bail!(iv_bytes.len() != bs, Reason::IVinvalid(bs));
234
235 Ok(*self
236 .control_block()
237 .generate_blocks(iv_bytes)
238 .last()
239 .unwrap())
240 }
241
242 /// Parses nonce and counter from their respective
243 /// hex-encoded strings.
244 ///
245 /// Parse may fail if the hex-string is not equal to
246 /// word-size.
247 pub fn parse_nonce_counter_from_hex<V>(
248 &self,
249 nonce_hex: V,
250 counter_hex: V,
251 ) -> Result<[W; N], Reason>
252 where
253 V: AsRef<[u8]>,
254 {
255 let mut nonce_bytes = hex::decode(nonce_hex)?;
256 let counter_bytes = hex::decode(counter_hex)?;
257 let ws = self.control_block().word_size();
258
259 bail!(
260 nonce_bytes.len() != ws && counter_bytes.len() != ws,
261 Reason::NonceInvalid(ws)
262 );
263 nonce_bytes.extend_from_slice(&counter_bytes);
264
265 Ok(*self
266 .control_block()
267 .generate_blocks(nonce_bytes)
268 .last()
269 .unwrap())
270 }
271
272 /// Returns an immutable access to control-block of block-cipher
273 /// underlying the cipher.
274 pub fn control_block(&self) -> &B {
275 &self.block
276 }
277}
278
279/// A core trait that any block-cipher must implement to work with [Cipher].
280///
281/// Generics in this trait defines:
282///
283/// - `W`: A variable length unit type which implements [Word] trait.
284/// - `N`: A generic constand for number of words per block.
285///
286/// This trait coerces some of the necessary functionalities for block-cipher.
287pub trait BlockCipher<W: Word, const N: usize> {
288 /// Human‐readable version tag, mostly a parametric version.
289 /// e.g. in RC5-block-cipher “RC5-32/12/16”.
290 fn control_block_version(&self) -> String;
291
292 /// Base block-size in bytes for a block-cipher.
293 fn block_size(&self) -> usize;
294
295 /// Word-szie in bytes per block for a block-cipher.
296 fn word_size(&self) -> usize;
297
298 /// Split a byte‐vector into a `Vec` of length-`N` word blocks.
299 /// More generally, it creates a list of blocks from a stream of
300 /// plain bytes.
301 fn generate_blocks(&self, pt: Vec<u8>) -> Vec<[W; N]>;
302
303 /// Generates a stream of bytes from a list of blocks. More
304 /// specefically from `N` word blocks list generates byte-vector.
305 /// Its counterfiet of generate_blocks method.
306 fn generate_bytes_stream(&self, blocks: Vec<[W; N]>) -> Vec<u8>;
307
308 /// Raw encryption, encrypt a single `[W;N]` block.
309 ///
310 /// Returns a cipher `[W;N]` block
311 fn encrypt(&self, pt: [W; N]) -> [W; N];
312
313 /// Raw decryption, decrypt a single `[W;N]` block.
314 ///
315 /// Returns a plain-text `[W;N]` block
316 fn decrypt(&self, ct: [W; N]) -> [W; N];
317}
318
319pub type RC5Cipher<W> = Cipher<RC5ControlBlock<W>, W, 2>;
320
321/// Construct a new RC5 cipher from a raw key and round count.
322///
323/// This is a help function which initializes Cipher with RC5
324/// control-bock.
325pub fn rc5_cipher<W>(key: impl AsRef<[u8]>, rounds: usize) -> Result<RC5Cipher<W>, Reason>
326where
327 W: Word,
328{
329 let control_block = RC5ControlBlock::<W>::new(key, rounds)?;
330 Ok(Cipher::new(control_block))
331}
332
333/// Helper macro to bail out early with a `Reason` error
334/// if any condition is true.
335#[macro_export]
336macro_rules! bail {
337 ($expression:expr, $err:expr) => {
338 if $expression {
339 return Err($err);
340 }
341 };
342 ( $( $cond:expr , $err:expr ),+ $(,)? ) => {
343 $(
344 if $cond {
345 return Err($err);
346 }
347 )+
348 };
349}