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 ISO10126,
18 PKCS7,
20}
21
22impl PaddingStrategy {
23 fn add_padding<InOut>(self, block_len: usize, in_out: &mut InOut) -> Result<(), Unspecified>
24 where
25 InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
26 {
27 match self {
28 PaddingStrategy::ISO10126 | PaddingStrategy::PKCS7 => {
30 let mut padding_buffer = [0u8; MAX_CIPHER_BLOCK_LEN];
31
32 let in_out_len = in_out.as_mut().len();
33 let remainder = in_out_len % block_len;
35 let padding_size = block_len - remainder;
36 let v: u8 = padding_size.try_into().map_err(|_| Unspecified)?;
37 padding_buffer.fill(v);
38 in_out.extend(padding_buffer[0..padding_size].iter());
40 }
41 }
42 Ok(())
43 }
44
45 fn remove_padding(self, block_len: usize, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> {
46 if in_out.is_empty() || in_out.len() < block_len {
47 return Err(Unspecified);
48 }
49 match self {
50 PaddingStrategy::ISO10126 => {
51 let padding: u8 = in_out[in_out.len() - 1];
52 if padding == 0 || padding as usize > block_len {
53 return Err(Unspecified);
54 }
55
56 let final_len = in_out.len() - padding as usize;
58 Ok(&mut in_out[0..final_len])
59 }
60 PaddingStrategy::PKCS7 => {
61 let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?;
62
63 let padding: u8 = in_out[in_out.len() - 1];
64 if padding == 0 || padding > block_size {
65 return Err(Unspecified);
66 }
67
68 for item in in_out.iter().skip(in_out.len() - padding as usize) {
69 if *item != padding {
70 return Err(Unspecified);
71 }
72 }
73
74 let final_len = in_out.len() - padding as usize;
75 Ok(&mut in_out[0..final_len])
76 }
77 }
78 }
79}
80
81pub struct PaddedBlockEncryptingKey {
83 algorithm: &'static Algorithm,
84 key: SymmetricCipherKey,
85 mode: OperatingMode,
86 padding: PaddingStrategy,
87}
88
89impl PaddedBlockEncryptingKey {
90 pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
107 Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
108 }
109
110 pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
126 Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
127 }
128
129 fn new(
130 key: UnboundCipherKey,
131 mode: OperatingMode,
132 padding: PaddingStrategy,
133 ) -> Result<PaddedBlockEncryptingKey, Unspecified> {
134 let algorithm = key.algorithm();
135 if !algorithm.supports_mode(mode) {
136 return Err(Unspecified);
137 }
138 let key = key.try_into()?;
139 Ok(Self {
140 algorithm,
141 key,
142 mode,
143 padding,
144 })
145 }
146
147 #[must_use]
149 pub fn algorithm(&self) -> &Algorithm {
150 self.algorithm
151 }
152
153 #[must_use]
155 pub fn mode(&self) -> OperatingMode {
156 self.mode
157 }
158
159 pub fn encrypt<InOut>(&self, in_out: &mut InOut) -> Result<DecryptionContext, Unspecified>
165 where
166 InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
167 {
168 let context = self.algorithm.new_encryption_context(self.mode)?;
169 self.less_safe_encrypt(in_out, context)
170 }
171
172 pub fn less_safe_encrypt<InOut>(
178 &self,
179 in_out: &mut InOut,
180 context: EncryptionContext,
181 ) -> Result<DecryptionContext, Unspecified>
182 where
183 InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
184 {
185 if !self
186 .algorithm()
187 .is_valid_encryption_context(self.mode, &context)
188 {
189 return Err(Unspecified);
190 }
191
192 self.padding
193 .add_padding(self.algorithm().block_len(), in_out)?;
194 cipher::encrypt(
195 self.algorithm(),
196 &self.key,
197 self.mode,
198 in_out.as_mut(),
199 context,
200 )
201 }
202}
203
204impl Debug for PaddedBlockEncryptingKey {
205 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
206 f.debug_struct("PaddedBlockEncryptingKey")
207 .field("algorithm", &self.algorithm)
208 .field("mode", &self.mode)
209 .field("padding", &self.padding)
210 .finish_non_exhaustive()
211 }
212}
213
214pub struct PaddedBlockDecryptingKey {
216 algorithm: &'static Algorithm,
217 key: SymmetricCipherKey,
218 mode: OperatingMode,
219 padding: PaddingStrategy,
220}
221
222impl PaddedBlockDecryptingKey {
223 pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
240 Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
241 }
242
243 pub fn cbc_iso10126(key: UnboundCipherKey) -> Result<Self, Unspecified> {
263 Self::new(key, OperatingMode::CBC, PaddingStrategy::ISO10126)
264 }
265
266 pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
287 Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
288 }
289
290 fn new(
291 key: UnboundCipherKey,
292 mode: OperatingMode,
293 padding: PaddingStrategy,
294 ) -> Result<PaddedBlockDecryptingKey, Unspecified> {
295 let algorithm = key.algorithm();
296 if !algorithm.supports_mode(mode) {
297 return Err(Unspecified);
298 }
299 let key = key.try_into()?;
300 Ok(PaddedBlockDecryptingKey {
301 algorithm,
302 key,
303 mode,
304 padding,
305 })
306 }
307
308 #[must_use]
310 pub fn algorithm(&self) -> &Algorithm {
311 self.algorithm
312 }
313
314 #[must_use]
316 pub fn mode(&self) -> OperatingMode {
317 self.mode
318 }
319
320 pub fn decrypt<'in_out>(
326 &self,
327 in_out: &'in_out mut [u8],
328 context: DecryptionContext,
329 ) -> Result<&'in_out mut [u8], Unspecified> {
330 if !self
331 .algorithm()
332 .is_valid_decryption_context(self.mode, &context)
333 {
334 return Err(Unspecified);
335 }
336
337 let block_len = self.algorithm().block_len();
338 let padding = self.padding;
339 let mut in_out = cipher::decrypt(self.algorithm, &self.key, self.mode, in_out, context)?;
340 in_out = padding.remove_padding(block_len, in_out)?;
341 Ok(in_out)
342 }
343}
344
345impl Debug for PaddedBlockDecryptingKey {
346 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
347 f.debug_struct("PaddedBlockDecryptingKey")
348 .field("algorithm", &self.algorithm)
349 .field("mode", &self.mode)
350 .field("padding", &self.padding)
351 .finish_non_exhaustive()
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use crate::cipher::padded::PaddingStrategy;
358 use crate::cipher::{
359 Algorithm, EncryptionContext, OperatingMode, PaddedBlockDecryptingKey,
360 PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256,
361 };
362 use crate::iv::FixedLength;
363 use crate::test::from_hex;
364
365 fn helper_test_padded_cipher_n_bytes(
366 key: &[u8],
367 alg: &'static Algorithm,
368 mode: OperatingMode,
369 padding: PaddingStrategy,
370 n: usize,
371 ) {
372 let mut input: Vec<u8> = Vec::with_capacity(n);
373 for i in 0..n {
374 let byte: u8 = i.try_into().unwrap();
375 input.push(byte);
376 }
377
378 let cipher_key = UnboundCipherKey::new(alg, key).unwrap();
379 let encrypting_key = PaddedBlockEncryptingKey::new(cipher_key, mode, padding).unwrap();
380
381 let mut in_out = input.clone();
382 let decrypt_iv = encrypting_key.encrypt(&mut in_out).unwrap();
383
384 if n > 5 {
385 assert_ne!(input.as_slice(), in_out);
387 }
388
389 let cipher_key2 = UnboundCipherKey::new(alg, key).unwrap();
390 let decrypting_key = PaddedBlockDecryptingKey::new(cipher_key2, mode, padding).unwrap();
391
392 let plaintext = decrypting_key.decrypt(&mut in_out, decrypt_iv).unwrap();
393 assert_eq!(input.as_slice(), plaintext);
394 }
395
396 #[test]
397 fn test_unpad_iso10126() {
398 let mut input = from_hex("01020304050607fedcba9805").unwrap();
399 let padding = PaddingStrategy::ISO10126;
400 let block_len = 8;
401
402 let unpadded = padding.remove_padding(block_len, &mut input).unwrap();
403 assert_eq!(unpadded, &mut [1, 2, 3, 4, 5, 6, 7]);
404 }
405
406 #[test]
407 fn test_aes_128_cbc() {
408 let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap();
409 for i in 0..=50 {
410 helper_test_padded_cipher_n_bytes(
411 key.as_slice(),
412 &AES_128,
413 OperatingMode::CBC,
414 PaddingStrategy::PKCS7,
415 i,
416 );
417 }
418 }
419
420 #[test]
421 fn test_aes_256_cbc() {
422 let key =
423 from_hex("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap();
424 for i in 0..=50 {
425 helper_test_padded_cipher_n_bytes(
426 key.as_slice(),
427 &AES_256,
428 OperatingMode::CBC,
429 PaddingStrategy::PKCS7,
430 i,
431 );
432 }
433 }
434
435 macro_rules! padded_cipher_kat {
436 ($name:ident, $alg:expr, $mode:expr, $padding:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => {
437 #[test]
438 fn $name() {
439 let key = from_hex($key).unwrap();
440 let input = from_hex($plaintext).unwrap();
441 let expected_ciphertext = from_hex($ciphertext).unwrap();
442 let mut iv = from_hex($iv).unwrap();
443 let iv = {
444 let slice = iv.as_mut_slice();
445 let mut iv = [0u8; $iv.len() / 2];
446 {
447 let x = iv.as_mut_slice();
448 x.copy_from_slice(slice);
449 }
450 iv
451 };
452
453 let ec = EncryptionContext::Iv128(FixedLength::from(iv));
454
455 let alg = $alg;
456
457 let unbound_key = UnboundCipherKey::new(alg, &key).unwrap();
458
459 let encrypting_key =
460 PaddedBlockEncryptingKey::new(unbound_key, $mode, $padding).unwrap();
461
462 let mut in_out = input.clone();
463
464 let context = encrypting_key.less_safe_encrypt(&mut in_out, ec).unwrap();
465
466 if ($padding == PaddingStrategy::ISO10126) {
467 assert_ne!(input, in_out[..input.len()]);
470 } else {
471 assert_eq!(expected_ciphertext, in_out);
472 }
473
474 let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap();
475 let decrypting_key =
476 PaddedBlockDecryptingKey::new(unbound_key2, $mode, $padding).unwrap();
477
478 let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap();
479 assert_eq!(input.as_slice(), plaintext);
480 }
481 };
482 }
483
484 padded_cipher_kat!(
485 test_iv_aes_128_cbc_16_bytes,
486 &AES_128,
487 OperatingMode::CBC,
488 PaddingStrategy::PKCS7,
489 "000102030405060708090a0b0c0d0e0f",
490 "00000000000000000000000000000000",
491 "00112233445566778899aabbccddeeff",
492 "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232"
493 );
494
495 padded_cipher_kat!(
496 test_iv_aes_256_cbc_15_bytes,
497 &AES_256,
498 OperatingMode::CBC,
499 PaddingStrategy::PKCS7,
500 "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
501 "00000000000000000000000000000000",
502 "00112233445566778899aabbccddee",
503 "2ddfb635a651a43f582997966840ca0c"
504 );
505
506 padded_cipher_kat!(
507 test_openssl_aes_128_cbc_15_bytes,
508 &AES_128,
509 OperatingMode::CBC,
510 PaddingStrategy::PKCS7,
511 "053304bb3899e1d99db9d29343ea782d",
512 "b5313560244a4822c46c2a0c9d0cf7fd",
513 "a3e4c990356c01f320043c3d8d6f43",
514 "ad96993f248bd6a29760ec7ccda95ee1"
515 );
516
517 padded_cipher_kat!(
518 test_openssl_aes_128_cbc_iso10126_15_bytes,
519 &AES_128,
520 OperatingMode::CBC,
521 PaddingStrategy::ISO10126,
522 "053304bb3899e1d99db9d29343ea782d",
523 "b5313560244a4822c46c2a0c9d0cf7fd",
524 "a3e4c990356c01f320043c3d8d6f43",
525 "ad96993f248bd6a29760ec7ccda95ee1"
526 );
527
528 padded_cipher_kat!(
529 test_openssl_aes_128_cbc_iso10126_16_bytes,
530 &AES_128,
531 OperatingMode::CBC,
532 PaddingStrategy::ISO10126,
533 "053304bb3899e1d99db9d29343ea782d",
534 "b83452fc9c80215a6ecdc505b5154c90",
535 "736e65616b7920726163636f6f6e7321",
536 "44563399c6bb2133e013161dc5bd4fa8ce83ef997ddb04bbbbe3632b68e9cde0"
537 );
538
539 padded_cipher_kat!(
540 test_openssl_aes_128_cbc_16_bytes,
541 &AES_128,
542 OperatingMode::CBC,
543 PaddingStrategy::PKCS7,
544 "95af71f1c63e4a1d0b0b1a27fb978283",
545 "89e40797dca70197ff87d3dbb0ef2802",
546 "aece7b5e3c3df1ffc9802d2dfe296dc7",
547 "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b"
548 );
549
550 padded_cipher_kat!(
551 test_openssl_aes_256_cbc_15_bytes,
552 &AES_256,
553 OperatingMode::CBC,
554 PaddingStrategy::PKCS7,
555 "d369e03e9752784917cc7bac1db7399598d9555e691861d9dd7b3292a693ef57",
556 "1399bb66b2f6ad99a7f064140eaaa885",
557 "7385f5784b85bf0a97768ddd896d6d",
558 "4351082bac9b4593ae8848cc9dfb5a01"
559 );
560
561 padded_cipher_kat!(
562 test_openssl_aes_256_cbc_16_bytes,
563 &AES_256,
564 OperatingMode::CBC,
565 PaddingStrategy::PKCS7,
566 "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
567 "24f6076548fb9d93c8f7ed9f6e661ef9",
568 "a39c1fdf77ea3e1f18178c0ec237c70a",
569 "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453"
570 );
571}