1use crate::cipher;
4use crate::cipher::key::SymmetricCipherKey;
5use crate::cipher::{
6 Algorithm, DecryptionContext, EncryptionContext, OperatingMode, UnboundCipherKey,
7 MAX_CIPHER_BLOCK_LEN,
8};
9use crate::error::Unspecified;
10use core::fmt::Debug;
11
12#[non_exhaustive]
14#[derive(Debug, PartialEq, Eq, Clone, Copy)]
15pub(crate) enum PaddingStrategy {
16 PKCS7,
18}
19
20impl PaddingStrategy {
21 fn add_padding<InOut>(self, block_len: usize, in_out: &mut InOut) -> Result<(), Unspecified>
22 where
23 InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
24 {
25 match self {
26 PaddingStrategy::PKCS7 => {
27 let mut padding_buffer = [0u8; MAX_CIPHER_BLOCK_LEN];
28
29 let in_out_len = in_out.as_mut().len();
30 let remainder = in_out_len % block_len;
32 let padding_size = block_len - remainder;
33 let v: u8 = padding_size.try_into().map_err(|_| Unspecified)?;
34 padding_buffer.fill(v);
35 in_out.extend(padding_buffer[0..padding_size].iter());
37 }
38 }
39 Ok(())
40 }
41
42 fn remove_padding(self, block_len: usize, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> {
43 match self {
44 PaddingStrategy::PKCS7 => {
45 let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?;
46
47 if in_out.is_empty() || in_out.len() < block_len {
48 return Err(Unspecified);
49 }
50
51 let padding: u8 = in_out[in_out.len() - 1];
52 if padding == 0 || padding > block_size {
53 return Err(Unspecified);
54 }
55
56 for item in in_out.iter().skip(in_out.len() - padding as usize) {
57 if *item != padding {
58 return Err(Unspecified);
59 }
60 }
61
62 let final_len = in_out.len() - padding as usize;
63 Ok(&mut in_out[0..final_len])
64 }
65 }
66 }
67}
68
69pub struct PaddedBlockEncryptingKey {
71 algorithm: &'static Algorithm,
72 key: SymmetricCipherKey,
73 mode: OperatingMode,
74 padding: PaddingStrategy,
75}
76
77impl PaddedBlockEncryptingKey {
78 pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
89 Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
90 }
91
92 pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
102 Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
103 }
104
105 #[allow(clippy::unnecessary_wraps)]
106 fn new(
107 key: UnboundCipherKey,
108 mode: OperatingMode,
109 padding: PaddingStrategy,
110 ) -> Result<PaddedBlockEncryptingKey, Unspecified> {
111 let algorithm = key.algorithm();
112 let key = key.try_into()?;
113 Ok(Self {
114 algorithm,
115 key,
116 mode,
117 padding,
118 })
119 }
120
121 #[must_use]
123 pub fn algorithm(&self) -> &Algorithm {
124 self.algorithm
125 }
126
127 #[must_use]
129 pub fn mode(&self) -> OperatingMode {
130 self.mode
131 }
132
133 pub fn encrypt<InOut>(&self, in_out: &mut InOut) -> Result<DecryptionContext, Unspecified>
139 where
140 InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
141 {
142 let context = self.algorithm.new_encryption_context(self.mode)?;
143 self.less_safe_encrypt(in_out, context)
144 }
145
146 pub fn less_safe_encrypt<InOut>(
152 &self,
153 in_out: &mut InOut,
154 context: EncryptionContext,
155 ) -> Result<DecryptionContext, Unspecified>
156 where
157 InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
158 {
159 if !self
160 .algorithm()
161 .is_valid_encryption_context(self.mode, &context)
162 {
163 return Err(Unspecified);
164 }
165
166 self.padding
167 .add_padding(self.algorithm().block_len(), in_out)?;
168 cipher::encrypt(
169 self.algorithm(),
170 &self.key,
171 self.mode,
172 in_out.as_mut(),
173 context,
174 )
175 }
176}
177
178impl Debug for PaddedBlockEncryptingKey {
179 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
180 f.debug_struct("PaddedBlockEncryptingKey")
181 .field("algorithm", &self.algorithm)
182 .field("mode", &self.mode)
183 .field("padding", &self.padding)
184 .finish_non_exhaustive()
185 }
186}
187
188pub struct PaddedBlockDecryptingKey {
190 algorithm: &'static Algorithm,
191 key: SymmetricCipherKey,
192 mode: OperatingMode,
193 padding: PaddingStrategy,
194}
195
196impl PaddedBlockDecryptingKey {
197 pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
208 Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
209 }
210
211 pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
226 Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
227 }
228
229 #[allow(clippy::unnecessary_wraps)]
230 fn new(
231 key: UnboundCipherKey,
232 mode: OperatingMode,
233 padding: PaddingStrategy,
234 ) -> Result<PaddedBlockDecryptingKey, Unspecified> {
235 let algorithm = key.algorithm();
236 let key = key.try_into()?;
237 Ok(PaddedBlockDecryptingKey {
238 algorithm,
239 key,
240 mode,
241 padding,
242 })
243 }
244
245 #[must_use]
247 pub fn algorithm(&self) -> &Algorithm {
248 self.algorithm
249 }
250
251 #[must_use]
253 pub fn mode(&self) -> OperatingMode {
254 self.mode
255 }
256
257 pub fn decrypt<'in_out>(
263 &self,
264 in_out: &'in_out mut [u8],
265 context: DecryptionContext,
266 ) -> Result<&'in_out mut [u8], Unspecified> {
267 if !self
268 .algorithm()
269 .is_valid_decryption_context(self.mode, &context)
270 {
271 return Err(Unspecified);
272 }
273
274 let block_len = self.algorithm().block_len();
275 let padding = self.padding;
276 let mut in_out = cipher::decrypt(self.algorithm, &self.key, self.mode, in_out, context)?;
277 in_out = padding.remove_padding(block_len, in_out)?;
278 Ok(in_out)
279 }
280}
281
282impl Debug for PaddedBlockDecryptingKey {
283 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
284 f.debug_struct("PaddedBlockDecryptingKey")
285 .field("algorithm", &self.algorithm)
286 .field("mode", &self.mode)
287 .field("padding", &self.padding)
288 .finish_non_exhaustive()
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use crate::cipher::padded::PaddingStrategy;
295 use crate::cipher::{
296 Algorithm, EncryptionContext, OperatingMode, PaddedBlockDecryptingKey,
297 PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256,
298 };
299 use crate::iv::FixedLength;
300 use crate::test::from_hex;
301
302 fn helper_test_padded_cipher_n_bytes(
303 key: &[u8],
304 alg: &'static Algorithm,
305 mode: OperatingMode,
306 padding: PaddingStrategy,
307 n: usize,
308 ) {
309 let mut input: Vec<u8> = Vec::with_capacity(n);
310 for i in 0..n {
311 let byte: u8 = i.try_into().unwrap();
312 input.push(byte);
313 }
314
315 let cipher_key = UnboundCipherKey::new(alg, key).unwrap();
316 let encrypting_key = PaddedBlockEncryptingKey::new(cipher_key, mode, padding).unwrap();
317
318 let mut in_out = input.clone();
319 let decrypt_iv = encrypting_key.encrypt(&mut in_out).unwrap();
320
321 if n > 5 {
322 assert_ne!(input.as_slice(), in_out);
324 }
325
326 let cipher_key2 = UnboundCipherKey::new(alg, key).unwrap();
327 let decrypting_key = PaddedBlockDecryptingKey::new(cipher_key2, mode, padding).unwrap();
328
329 let plaintext = decrypting_key.decrypt(&mut in_out, decrypt_iv).unwrap();
330 assert_eq!(input.as_slice(), plaintext);
331 }
332
333 #[test]
334 fn test_aes_128_cbc() {
335 let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap();
336 for i in 0..=50 {
337 helper_test_padded_cipher_n_bytes(
338 key.as_slice(),
339 &AES_128,
340 OperatingMode::CBC,
341 PaddingStrategy::PKCS7,
342 i,
343 );
344 }
345 }
346
347 #[test]
348 fn test_aes_256_cbc() {
349 let key =
350 from_hex("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap();
351 for i in 0..=50 {
352 helper_test_padded_cipher_n_bytes(
353 key.as_slice(),
354 &AES_256,
355 OperatingMode::CBC,
356 PaddingStrategy::PKCS7,
357 i,
358 );
359 }
360 }
361
362 macro_rules! padded_cipher_kat {
363 ($name:ident, $alg:expr, $mode:expr, $padding:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => {
364 #[test]
365 fn $name() {
366 let key = from_hex($key).unwrap();
367 let input = from_hex($plaintext).unwrap();
368 let expected_ciphertext = from_hex($ciphertext).unwrap();
369 let mut iv = from_hex($iv).unwrap();
370 let iv = {
371 let slice = iv.as_mut_slice();
372 let mut iv = [0u8; $iv.len() / 2];
373 {
374 let x = iv.as_mut_slice();
375 x.copy_from_slice(slice);
376 }
377 iv
378 };
379
380 let ec = EncryptionContext::Iv128(FixedLength::from(iv));
381
382 let alg = $alg;
383
384 let unbound_key = UnboundCipherKey::new(alg, &key).unwrap();
385
386 let encrypting_key =
387 PaddedBlockEncryptingKey::new(unbound_key, $mode, $padding).unwrap();
388
389 let mut in_out = input.clone();
390
391 let context = encrypting_key.less_safe_encrypt(&mut in_out, ec).unwrap();
392
393 assert_eq!(expected_ciphertext, in_out);
394
395 let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap();
396 let decrypting_key =
397 PaddedBlockDecryptingKey::new(unbound_key2, $mode, $padding).unwrap();
398
399 let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap();
400 assert_eq!(input.as_slice(), plaintext);
401 }
402 };
403 }
404
405 padded_cipher_kat!(
406 test_iv_aes_128_cbc_16_bytes,
407 &AES_128,
408 OperatingMode::CBC,
409 PaddingStrategy::PKCS7,
410 "000102030405060708090a0b0c0d0e0f",
411 "00000000000000000000000000000000",
412 "00112233445566778899aabbccddeeff",
413 "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232"
414 );
415
416 padded_cipher_kat!(
417 test_iv_aes_256_cbc_15_bytes,
418 &AES_256,
419 OperatingMode::CBC,
420 PaddingStrategy::PKCS7,
421 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
422 "00000000000000000000000000000000",
423 "00112233445566778899aabbccddee",
424 "2ddfb635a651a43f582997966840ca0c"
425 );
426
427 padded_cipher_kat!(
428 test_openssl_aes_128_cbc_15_bytes,
429 &AES_128,
430 OperatingMode::CBC,
431 PaddingStrategy::PKCS7,
432 "053304bb3899e1d99db9d29343ea782d",
433 "b5313560244a4822c46c2a0c9d0cf7fd",
434 "a3e4c990356c01f320043c3d8d6f43",
435 "ad96993f248bd6a29760ec7ccda95ee1"
436 );
437
438 padded_cipher_kat!(
439 test_openssl_aes_128_cbc_16_bytes,
440 &AES_128,
441 OperatingMode::CBC,
442 PaddingStrategy::PKCS7,
443 "95af71f1c63e4a1d0b0b1a27fb978283",
444 "89e40797dca70197ff87d3dbb0ef2802",
445 "aece7b5e3c3df1ffc9802d2dfe296dc7",
446 "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b"
447 );
448
449 padded_cipher_kat!(
450 test_openssl_aes_256_cbc_15_bytes,
451 &AES_256,
452 OperatingMode::CBC,
453 PaddingStrategy::PKCS7,
454 "d369e03e9752784917cc7bac1db7399598d9555e691861d9dd7b3292a693ef57",
455 "1399bb66b2f6ad99a7f064140eaaa885",
456 "7385f5784b85bf0a97768ddd896d6d",
457 "4351082bac9b4593ae8848cc9dfb5a01"
458 );
459
460 padded_cipher_kat!(
461 test_openssl_aes_256_cbc_16_bytes,
462 &AES_256,
463 OperatingMode::CBC,
464 PaddingStrategy::PKCS7,
465 "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
466 "24f6076548fb9d93c8f7ed9f6e661ef9",
467 "a39c1fdf77ea3e1f18178c0ec237c70a",
468 "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453"
469 );
470}