1use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit};
34use hkdf::HmacImpl;
35use hybrid_array::Array;
36use rand::Rng;
37use subtle::ConstantTimeEq;
38
39use super::{
40 ChunkDecryptionResult, ChunkEncryptionResult, StreamCreationError, StreamDecryptionError,
41 StreamEncryptionError, StreamingDecryptor, StreamingEncryptor,
42};
43use crate::{
44 Aes256CbcHmacKey, SymmetricCryptoKey,
45 stream::large_memory_buffer::{Buffer, InvalidIndexError},
46 util::PbkdfSha256Hmac,
47};
48
49const AES256_CBC_BLOCK_SIZE: usize = 16;
50const AES256_CBC_IV_SIZE: usize = 16;
51const HMAC_SIZE: usize = 32;
52const HEADER_LENGTH: usize = AES256_CBC_IV_SIZE + HMAC_SIZE;
53const EMISSION_CHUNK_SIZE: usize = 1024 * 1024; type Iv = [u8; AES256_CBC_IV_SIZE];
57type Mac = [u8; HMAC_SIZE];
59type StreamHeaderBytes = [u8; HEADER_LENGTH];
61type CbcCiphertextBlock = [u8; AES256_CBC_BLOCK_SIZE];
62
63struct CbcDecryptor {
64 decryptor: cbc::Decryptor<aes::Aes256>,
65}
66
67impl CbcDecryptor {
68 fn new(key: &[u8; 32], iv: &Iv) -> Self {
69 Self {
70 decryptor: cbc::Decryptor::<aes::Aes256>::new(key.into(), iv.into()),
71 }
72 }
73
74 fn decrypt_block(&mut self, block: &CbcCiphertextBlock) -> CbcPlaintextBlock {
75 let mut block: Array<u8, _> = (*block).into();
76 self.decryptor.decrypt_block(&mut block);
77 CbcPlaintextBlock(
78 block
79 .as_slice()
80 .try_into()
81 .expect("block size checked by type"),
82 )
83 }
84}
85
86struct CbcEncryptor {
87 encryptor: cbc::Encryptor<aes::Aes256>,
88}
89
90impl CbcEncryptor {
91 fn new(key: &[u8; 32], iv: &Iv) -> Self {
92 Self {
93 encryptor: cbc::Encryptor::<aes::Aes256>::new(key.into(), iv.into()),
94 }
95 }
96
97 fn encrypt_block(&mut self, block: &mut CbcPlaintextBlock) -> CbcCiphertextBlock {
98 let mut block_array: Array<u8, _> = block.0.into();
99 self.encryptor.encrypt_block(&mut block_array);
100 block_array
101 .as_slice()
102 .try_into()
103 .expect("block size checked by type")
104 }
105}
106
107struct HmacStreamValidator {
109 hmac: PbkdfSha256Hmac,
110}
111
112impl HmacStreamValidator {
113 fn new(mac_key: &[u8; 32], iv: &Iv) -> Self {
114 let mut hmac = PbkdfSha256Hmac::new_from_slice(mac_key);
115 hmac.update(iv);
116 Self { hmac }
117 }
118
119 fn read_block(&mut self, data: &CbcCiphertextBlock) {
121 self.hmac.update(data);
122 }
123
124 fn end_stream(&self) -> Mac {
126 let mac: Mac = self
129 .hmac
130 .clone()
131 .finalize()
132 .as_slice()
133 .try_into()
134 .expect("hmac output is 32 bytes");
135 mac
136 }
137
138 fn validate_stream_end(&self, expected_mac: &Mac) -> bool {
141 let calculated_mac = self.hmac.clone().finalize();
144 calculated_mac.as_slice().ct_eq(expected_mac).into()
145 }
146}
147
148#[derive(Clone)]
149struct StreamHeader {
150 iv: Iv,
151 mac: Mac,
152}
153
154impl From<&StreamHeader> for StreamHeaderBytes {
155 fn from(header: &StreamHeader) -> Self {
156 let mut out = [0u8; HEADER_LENGTH];
157 out[..AES256_CBC_IV_SIZE].copy_from_slice(&header.iv);
158 out[AES256_CBC_IV_SIZE..].copy_from_slice(&header.mac);
159 out
160 }
161}
162
163impl From<&StreamHeaderBytes> for StreamHeader {
164 fn from(bytes: &StreamHeaderBytes) -> Self {
165 let iv: Iv = bytes[..AES256_CBC_IV_SIZE]
166 .try_into()
167 .expect("slice length checked by type");
168 let mac: Mac = bytes[AES256_CBC_IV_SIZE..]
169 .try_into()
170 .expect("slice length checked by type");
171 StreamHeader { iv, mac }
172 }
173}
174
175enum Pkcs7ValidationResult {
176 Valid(Vec<u8>),
179 Invalid,
180}
181
182struct CbcPlaintextBlock([u8; AES256_CBC_BLOCK_SIZE]);
183
184impl AsRef<[u8]> for CbcPlaintextBlock {
185 fn as_ref(&self) -> &[u8] {
186 &self.0
187 }
188}
189
190impl CbcPlaintextBlock {
191 fn is_valid_pkcs7_padding(&self) -> Pkcs7ValidationResult {
194 let data = &self.0;
195 if data.is_empty() {
196 return Pkcs7ValidationResult::Invalid;
197 }
198 let padding_len = *data.last().expect("data is a fixed-size non-empty array") as usize;
199 if padding_len == 0 || padding_len > AES256_CBC_BLOCK_SIZE {
200 return Pkcs7ValidationResult::Invalid;
201 }
202 if data[data.len() - padding_len..]
203 .iter()
204 .all(|&b| b as usize == padding_len)
205 {
206 Pkcs7ValidationResult::Valid(data[..data.len() - padding_len].to_vec())
207 } else {
208 Pkcs7ValidationResult::Invalid
209 }
210 }
211}
212
213enum DecryptorState {
214 Uninitialized {
215 key: Aes256CbcHmacKey,
216 },
217 Streaming {
218 decryptor: Box<CbcDecryptor>,
219 integrity_validator: HmacStreamValidator,
220 expected_mac: Mac,
221 },
222 Done,
223 Error,
224}
225
226enum DecryptorInitializeWithHeaderError {
227 AlreadyInitialized,
228}
229
230impl DecryptorState {
231 fn initialize(
235 &mut self,
236 header: StreamHeader,
237 ) -> Result<(), DecryptorInitializeWithHeaderError> {
238 match self {
239 Self::Uninitialized { key } => {
240 *self = Self::Streaming {
241 decryptor: Box::new(CbcDecryptor::new(&key.enc_key.0, &header.iv)),
242 integrity_validator: HmacStreamValidator::new(&key.mac_key.0, &header.iv),
243 expected_mac: header.mac,
244 };
245 Ok(())
246 }
247 _ => Err(DecryptorInitializeWithHeaderError::AlreadyInitialized),
248 }
249 }
250}
251
252fn read_block_ciphertext(buffer: &mut Vec<u8>) -> CbcCiphertextBlock {
255 buffer
256 .drain(..AES256_CBC_BLOCK_SIZE)
257 .as_slice()
258 .try_into()
259 .expect("slice length checked by external condition")
260}
261
262fn read_plaintext_block(buffer: &mut Vec<u8>) -> Option<CbcPlaintextBlock> {
265 if buffer.len() < AES256_CBC_BLOCK_SIZE {
266 return None;
267 }
268 Some(CbcPlaintextBlock(
269 buffer
270 .drain(..AES256_CBC_BLOCK_SIZE)
271 .as_slice()
272 .try_into()
273 .expect("slice length checked by condition"),
274 ))
275}
276
277fn read_header(buffer: &mut Vec<u8>) -> Option<StreamHeader> {
278 if buffer.len() >= HEADER_LENGTH {
279 let header_bytes: StreamHeaderBytes = buffer
280 .drain(..HEADER_LENGTH)
281 .as_slice()
282 .try_into()
283 .expect("slice length checked by condition");
284 Some((&header_bytes).into())
285 } else {
286 None
287 }
288}
289
290pub struct StreamingAes256CbcHmacDecryptor {
296 buffer: Vec<u8>,
300 decryptor_state: DecryptorState,
301}
302
303impl StreamingAes256CbcHmacDecryptor {
304 pub(crate) fn try_new(key: &SymmetricCryptoKey) -> Result<Self, StreamCreationError> {
305 let key = match key {
306 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
307 _ => return Err(StreamCreationError::WrongKeyType),
308 };
309 Ok(Self {
310 buffer: Vec::new(),
311 decryptor_state: DecryptorState::Uninitialized { key: key.clone() },
312 })
313 }
314}
315
316impl StreamingDecryptor for StreamingAes256CbcHmacDecryptor {
317 fn update(&mut self, ciphertext_chunk: &[u8], last_block: bool) -> ChunkDecryptionResult {
320 self.buffer.extend_from_slice(ciphertext_chunk);
321
322 if matches!(
325 self.decryptor_state,
326 DecryptorState::Error | DecryptorState::Done
327 ) {
328 return ChunkDecryptionResult::Error(StreamDecryptionError);
329 }
330
331 if matches!(self.decryptor_state, DecryptorState::Uninitialized { .. }) {
335 if self.buffer.len() < HEADER_LENGTH {
336 if last_block {
337 self.decryptor_state = DecryptorState::Error;
338 return ChunkDecryptionResult::Error(StreamDecryptionError);
339 }
340 return ChunkDecryptionResult::NeedMoreData;
341 }
342 let header: StreamHeader =
343 read_header(&mut self.buffer).expect("header length checked by condition above");
344 if self.decryptor_state.initialize(header).is_err() {
345 self.decryptor_state = DecryptorState::Error;
346 return ChunkDecryptionResult::Error(StreamDecryptionError);
347 }
348 }
349
350 if let DecryptorState::Streaming {
352 decryptor,
353 integrity_validator,
354 expected_mac,
355 } = &mut self.decryptor_state
356 {
357 let blocks_to_process = if last_block {
362 self.buffer.len() / AES256_CBC_BLOCK_SIZE
363 } else {
364 self.buffer.len().saturating_sub(1) / AES256_CBC_BLOCK_SIZE
365 };
366
367 let mut decrypted_data = Vec::new();
368 for _ in 0..blocks_to_process {
369 let ciphertext_block: CbcCiphertextBlock = read_block_ciphertext(&mut self.buffer);
370 integrity_validator.read_block(&ciphertext_block);
371 let plaintext_block = decryptor.decrypt_block(&ciphertext_block);
372 decrypted_data.extend_from_slice(plaintext_block.as_ref());
373 }
374
375 if last_block {
376 if !integrity_validator.validate_stream_end(expected_mac) {
379 self.decryptor_state = DecryptorState::Error;
380 return ChunkDecryptionResult::Error(StreamDecryptionError);
381 }
382
383 if decrypted_data.len() < AES256_CBC_BLOCK_SIZE {
385 self.decryptor_state = DecryptorState::Error;
386 return ChunkDecryptionResult::Error(StreamDecryptionError);
387 }
388
389 let tail_start = decrypted_data.len() - AES256_CBC_BLOCK_SIZE;
392 let last_block: CbcPlaintextBlock = CbcPlaintextBlock(
393 decrypted_data
394 .drain(tail_start..)
395 .as_slice()
396 .try_into()
397 .expect("drained one block"),
398 );
399 match last_block.is_valid_pkcs7_padding() {
400 Pkcs7ValidationResult::Valid(plaintext) => {
401 decrypted_data.extend_from_slice(&plaintext);
402 }
403 Pkcs7ValidationResult::Invalid => {
404 self.decryptor_state = DecryptorState::Error;
405 return ChunkDecryptionResult::Error(StreamDecryptionError);
406 }
407 }
408
409 self.decryptor_state = DecryptorState::Done;
410 return ChunkDecryptionResult::FinalDecryptedChunk(decrypted_data);
411 } else {
412 return ChunkDecryptionResult::DecryptedChunk(decrypted_data);
413 }
414 }
415
416 ChunkDecryptionResult::Error(StreamDecryptionError)
418 }
419}
420
421struct CiphertextBuffer {
422 inner: Buffer,
426 size: usize,
429 emitted_bytes: usize,
430}
431
432impl CiphertextBuffer {
433 fn new(capacity: usize) -> Self {
434 Self {
435 inner: Buffer::new(capacity),
436 size: 0,
437 emitted_bytes: 0,
438 }
439 }
440
441 fn append(&mut self, data: &CbcCiphertextBlock) -> Result<(), InvalidIndexError> {
442 self.inner.append(data)?;
443 self.size += data.len();
444 Ok(())
445 }
446
447 fn emit_chunk(&mut self) -> Option<Vec<u8>> {
448 let remaining_bytes = self.size - self.emitted_bytes;
449 if remaining_bytes == 0 {
450 return None;
451 }
452
453 let chunk_size = remaining_bytes.min(EMISSION_CHUNK_SIZE);
454 let chunk = self
455 .inner
456 .index(self.emitted_bytes..self.emitted_bytes + chunk_size)
457 .expect("chunk size is always within bounds of the buffer");
458 self.emitted_bytes += chunk_size;
459 Some(chunk)
460 }
461}
462
463enum EncryptorState {
464 Streaming {
465 encryptor: Box<CbcEncryptor>,
466 integrity_validator: HmacStreamValidator,
467 iv: Iv,
468 },
469 Emitting,
470 Done,
471 Error,
472}
473
474pub struct StreamingAes256CbcHmacEncryptor {
480 ciphertext_buffer: CiphertextBuffer,
483 plaintext_buffer: Vec<u8>,
484 encryptor_state: EncryptorState,
485}
486
487impl StreamingAes256CbcHmacEncryptor {
488 pub(crate) fn try_new(
492 key: &SymmetricCryptoKey,
493 plaintext_size: usize,
494 ) -> Result<Self, StreamCreationError> {
495 let key = match key {
496 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key,
497 _ => return Err(StreamCreationError::WrongKeyType),
498 };
499
500 let mut iv: Iv = [0u8; AES256_CBC_IV_SIZE];
501 rand::rng().fill_bytes(&mut iv);
502
503 let ciphertext_capacity =
506 (plaintext_size / AES256_CBC_BLOCK_SIZE + 1) * AES256_CBC_BLOCK_SIZE;
507
508 Ok(Self {
509 ciphertext_buffer: CiphertextBuffer::new(ciphertext_capacity),
510 plaintext_buffer: Vec::new(),
511 encryptor_state: EncryptorState::Streaming {
512 encryptor: Box::new(CbcEncryptor::new(&key.enc_key.0, &iv)),
513 integrity_validator: HmacStreamValidator::new(&key.mac_key.0, &iv),
514 iv,
515 },
516 })
517 }
518}
519
520impl StreamingEncryptor for StreamingAes256CbcHmacEncryptor {
521 fn update(&mut self, plaintext_chunk: &[u8], last_block: bool) -> ChunkEncryptionResult {
522 let (encryptor, stream_validator, iv): (
523 &mut Box<CbcEncryptor>,
524 &mut HmacStreamValidator,
525 &Iv,
526 ) = match &mut self.encryptor_state {
527 EncryptorState::Error | EncryptorState::Done => {
528 return ChunkEncryptionResult::Error(StreamEncryptionError);
529 }
530 EncryptorState::Emitting => {
531 if let Some(chunk) = self.ciphertext_buffer.emit_chunk() {
532 return ChunkEncryptionResult::EncryptedChunk(chunk);
533 } else {
534 self.encryptor_state = EncryptorState::Done;
535 return ChunkEncryptionResult::FinalEncryptedChunk(Vec::new());
536 }
537 }
538 EncryptorState::Streaming {
539 encryptor,
540 integrity_validator,
541 iv,
542 } => (encryptor, integrity_validator, iv),
543 };
544
545 self.plaintext_buffer.extend_from_slice(plaintext_chunk);
546
547 while let Some(mut block) = read_plaintext_block(&mut self.plaintext_buffer) {
549 let cipher_block = encryptor.encrypt_block(&mut block);
550 stream_validator.read_block(&cipher_block);
551 if self.ciphertext_buffer.append(&cipher_block).is_err() {
552 self.encryptor_state = EncryptorState::Error;
553 return ChunkEncryptionResult::Error(StreamEncryptionError);
554 }
555 }
556
557 if !last_block {
558 return ChunkEncryptionResult::NeedMoreData;
559 }
560
561 let padding_len = AES256_CBC_BLOCK_SIZE - self.plaintext_buffer.len();
564 let mut padding = vec![padding_len as u8; padding_len];
565 self.plaintext_buffer.append(&mut padding);
566
567 let Some(mut block) = read_plaintext_block(&mut self.plaintext_buffer) else {
568 self.encryptor_state = EncryptorState::Error;
571 return ChunkEncryptionResult::Error(StreamEncryptionError);
572 };
573
574 let cipher_block = encryptor.encrypt_block(&mut block);
575 if self.ciphertext_buffer.append(&cipher_block).is_err() {
576 self.encryptor_state = EncryptorState::Error;
577 return ChunkEncryptionResult::Error(StreamEncryptionError);
578 }
579
580 stream_validator.read_block(&cipher_block);
581 let mac = stream_validator.end_stream();
582
583 let header: StreamHeaderBytes = (&StreamHeader { iv: *iv, mac }).into();
584 self.encryptor_state = EncryptorState::Emitting;
585 ChunkEncryptionResult::EncryptedChunk(header.to_vec())
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592
593 const ENC_KEY: [u8; 32] = [0u8; 32];
594 const MAC_KEY: [u8; 32] = [1u8; 32];
595 const PLAINTEXT: &[u8] = b"This is a test vector text for streaming encryption. It is long enough to require multiple CBC blocks.";
596 const STREAM_TEST_VECTOR: &[u8] = &[
597 202, 35, 233, 113, 188, 138, 45, 92, 122, 28, 38, 85, 31, 242, 192, 113, 213, 46, 222, 105,
598 210, 189, 251, 90, 162, 190, 27, 47, 139, 54, 146, 233, 233, 246, 27, 201, 172, 13, 180,
599 105, 69, 177, 113, 72, 154, 138, 43, 75, 53, 193, 235, 62, 28, 217, 137, 81, 167, 40, 33,
600 95, 241, 1, 154, 92, 69, 252, 151, 218, 106, 46, 189, 208, 154, 123, 192, 207, 253, 155,
601 22, 17, 158, 142, 88, 91, 177, 117, 227, 45, 58, 16, 150, 180, 193, 32, 144, 95, 227, 233,
602 60, 7, 98, 197, 200, 144, 179, 213, 220, 95, 242, 17, 112, 115, 211, 97, 90, 250, 210, 141,
603 200, 157, 156, 71, 133, 165, 246, 161, 110, 127, 232, 225, 121, 190, 235, 121, 228, 4, 31,
604 123, 67, 140, 84, 64, 41, 198, 227, 221, 20, 188, 252, 70, 20, 81, 138, 210, 247, 230, 16,
605 233, 229, 154,
606 ];
607
608 fn test_key() -> SymmetricCryptoKey {
609 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey {
610 enc_key: Box::pin(ENC_KEY.into()),
611 mac_key: Box::pin(MAC_KEY.into()),
612 })
613 }
614
615 #[test]
616 #[ignore = "used to generate the test vector constant above; not a unit test"]
617 fn generate_test_vectors() {
618 let key = test_key();
619 let out = encrypt_all(&key, PLAINTEXT, 20);
620 println!("const STREAM_TEST_VECTOR: &[u8] = &{:?};", out);
621 }
622
623 fn encrypt_all(key: &SymmetricCryptoKey, plaintext: &[u8], chunk_size: usize) -> Vec<u8> {
624 let mut encryptor = StreamingAes256CbcHmacEncryptor::try_new(key, plaintext.len())
625 .ok()
626 .expect("encryptor construction");
627
628 let mut plaintext_buffer = plaintext.to_vec();
629 let mut ciphertext_buffer = Vec::new();
630
631 loop {
632 let chunk = plaintext_buffer
633 .drain(..chunk_size.min(plaintext_buffer.len()))
634 .collect::<Vec<u8>>();
635 match encryptor.update(&chunk, plaintext_buffer.is_empty()) {
636 ChunkEncryptionResult::NeedMoreData => {}
637 ChunkEncryptionResult::EncryptedChunk(bytes) => {
638 ciphertext_buffer.extend_from_slice(&bytes);
639 }
640 ChunkEncryptionResult::FinalEncryptedChunk(bytes) => {
641 ciphertext_buffer.extend_from_slice(&bytes);
642 break;
643 }
644 ChunkEncryptionResult::Error(_) => panic!("encryption error"),
645 };
646 }
647
648 ciphertext_buffer
649 }
650
651 fn decrypt_all(key: &SymmetricCryptoKey, ciphertext: &[u8], chunk_size: usize) -> Vec<u8> {
652 let mut decryptor = StreamingAes256CbcHmacDecryptor::try_new(key)
653 .ok()
654 .expect("decryptor construction");
655
656 let mut ciphertext_buffer = ciphertext.to_vec();
657 let mut plaintext_buffer = Vec::new();
658
659 loop {
660 let chunk = ciphertext_buffer
661 .drain(..chunk_size.min(ciphertext_buffer.len()))
662 .collect::<Vec<u8>>();
663 match decryptor.update(&chunk, ciphertext_buffer.is_empty()) {
664 ChunkDecryptionResult::NeedMoreData => {}
665 ChunkDecryptionResult::DecryptedChunk(bytes) => {
666 plaintext_buffer.extend_from_slice(&bytes);
667 }
668 ChunkDecryptionResult::FinalDecryptedChunk(bytes) => {
669 plaintext_buffer.extend_from_slice(&bytes);
670 break;
671 }
672 ChunkDecryptionResult::Error(_) => panic!("decryption error"),
673 };
674 }
675
676 plaintext_buffer
677 }
678
679 #[test]
680 fn streaming_encrypt_decrypt_roundtrip() {
681 let key = test_key();
682 let plaintext = PLAINTEXT;
683 let ciphertext = encrypt_all(&key, plaintext, 11);
684 let roundtripped = decrypt_all(&key, &ciphertext, 9);
685
686 assert_eq!(roundtripped, plaintext);
687 }
688
689 #[test]
690 fn streaming_decrypt_with_truncated_ciphertext_fails() {
691 let key = test_key();
692 let ciphertext = STREAM_TEST_VECTOR;
693 let truncated = &ciphertext[..ciphertext.len() - 10];
694 let mut dec = StreamingAes256CbcHmacDecryptor::try_new(&key)
695 .ok()
696 .expect("decryptor construction");
697 let result = dec.update(truncated, true);
698 assert!(matches!(result, ChunkDecryptionResult::Error(_)));
699 }
700
701 #[test]
702 fn streaming_decrypt_with_modified_ciphertext_fails() {
703 let key = test_key();
704 let mut modified = STREAM_TEST_VECTOR.to_vec();
705 modified[50] ^= 0b0000_0001;
707 let mut dec = StreamingAes256CbcHmacDecryptor::try_new(&key)
708 .ok()
709 .expect("decryptor construction");
710 let result = dec.update(&modified, true);
711 assert!(matches!(result, ChunkDecryptionResult::Error(_)));
712 }
713
714 #[test]
715 fn streaming_decrypt_with_modified_header_fails() {
716 let key = test_key();
717 let mut modified = STREAM_TEST_VECTOR.to_vec();
718 modified[5] ^= 0b0000_0001;
720 let mut dec = StreamingAes256CbcHmacDecryptor::try_new(&key)
721 .ok()
722 .expect("decryptor construction");
723 let result = dec.update(&modified, true);
724 assert!(matches!(result, ChunkDecryptionResult::Error(_)));
725 }
726}