1use cipher::inout::InOut;
6use cipher::typenum::consts::U16;
7use cipher::{
8 Array, BlockCipherDecBackend, BlockCipherDecClosure, BlockCipherEncBackend,
9 BlockCipherEncClosure, BlockSizeUser,
10};
11use static_assertions::assert_cfg;
12use storage_ptr_slice::{MutPtrByteSlice, PtrByteSlice};
13use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
14
15assert_cfg!(target_endian = "little");
17
18#[derive(IntoBytes, KnownLayout, FromBytes, Immutable, Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(C)]
20pub struct Tweak(pub u128);
21
22impl Tweak {
23 pub fn new(val: u128) -> Self {
24 Self(val)
25 }
26
27 fn update(&mut self) {
28 self.0 = (self.0 << 1) ^ ((self.0 as i128 >> 127) as u128 & 0x87);
29 }
30}
31
32#[cfg(target_arch = "aarch64")]
33fn assert_dcz_block_size_is_64() {
34 static CHECK: std::sync::OnceLock<()> = std::sync::OnceLock::new();
35 CHECK.get_or_init(|| {
36 let dczid: u64;
37 unsafe {
38 core::arch::asm!("mrs {0}, dczid_el0", out(reg) dczid);
39 }
40 let dzp = (dczid >> 4) & 1;
41 assert_eq!(dzp, 0, "DC ZVA is prohibited on this CPU");
42 let bs_bytes = (1usize << (dczid & 0xf)) * 4;
43 assert_eq!(bs_bytes, 64, "Expected DC ZVA block size to be 64 bytes, found {bs_bytes}");
44 });
45}
46
47pub struct XtsProcessor<'a, 'b> {
50 tweak: Tweak,
51 src: PtrByteSlice<'a>,
52 dst: MutPtrByteSlice<'b>,
53}
54
55pub struct XtsInPlaceProcessor<'a> {
58 tweak: Tweak,
59 src: PtrByteSlice<'a>,
60 dst: MutPtrByteSlice<'a>,
61}
62
63fn xts_encrypt_chunk<B: BlockCipherEncBackend<BlockSize = U16>>(
64 backend: &B,
65 mut val: u128,
66 tweak: &Tweak,
67) -> u128 {
68 val ^= tweak.0;
70
71 let arr: &mut Array<u8, U16> = val.as_mut_bytes().try_into().unwrap();
72 backend.encrypt_block(InOut::from(arr));
73
74 val ^ tweak.0
76}
77
78fn xts_encrypt_buffer_out_of_place<B: BlockCipherEncBackend<BlockSize = U16>>(
79 backend: &B,
80 src: PtrByteSlice<'_>,
81 mut dst: MutPtrByteSlice<'_>,
82 tweak: &mut Tweak,
83) {
84 debug_assert_eq!(src.len(), dst.len());
85 debug_assert_eq!(src.as_ptr() as usize % 64, 0);
86 debug_assert_eq!(dst.as_ptr() as usize % 64, 0);
87 debug_assert_eq!(src.len() % 64, 0);
88
89 let mut src_chunks = src.iter_as::<u128>();
90 let mut dst_chunks = dst.iter_as_mut::<u128>();
91
92 while let Some(s0) = src_chunks.next() {
93 let d0 = dst_chunks.next().unwrap();
94 #[cfg(target_arch = "aarch64")]
97 unsafe {
98 core::arch::asm!(
99 "dc zva, {0}",
100 in(reg) d0.as_ptr(),
101 options(nostack, preserves_flags),
102 );
103 }
104 let val0 = xts_encrypt_chunk(backend, s0, tweak);
105 d0.write(val0);
106 tweak.update();
107
108 let s1 = src_chunks.next().unwrap();
109 let d1 = dst_chunks.next().unwrap();
110 let val1 = xts_encrypt_chunk(backend, s1, tweak);
111 d1.write(val1);
112 tweak.update();
113
114 let s2 = src_chunks.next().unwrap();
115 let d2 = dst_chunks.next().unwrap();
116 let val2 = xts_encrypt_chunk(backend, s2, tweak);
117 d2.write(val2);
118 tweak.update();
119
120 let s3 = src_chunks.next().unwrap();
121 let d3 = dst_chunks.next().unwrap();
122 let val3 = xts_encrypt_chunk(backend, s3, tweak);
123 d3.write(val3);
124 tweak.update();
125 }
126}
127
128fn xts_encrypt_buffer<B: BlockCipherEncBackend<BlockSize = U16>>(
129 backend: &B,
130 src: PtrByteSlice<'_>,
131 mut dst: MutPtrByteSlice<'_>,
132 tweak: &mut Tweak,
133) {
134 debug_assert_eq!(src.len(), dst.len());
135 debug_assert!(src.as_ptr().cast::<u128>().is_aligned());
136 debug_assert!(dst.as_ptr().cast::<u128>().is_aligned());
137
138 let src_chunks = src.iter_as::<u128>();
139 let dst_chunks = dst.iter_as_mut::<u128>();
140
141 for (src_chunk, dst_chunk) in src_chunks.zip(dst_chunks) {
142 let val = xts_encrypt_chunk(backend, src_chunk, tweak);
143 dst_chunk.write(val);
144 tweak.update();
145 }
146}
147
148fn xts_decrypt_chunk<B: BlockCipherDecBackend<BlockSize = U16>>(
149 backend: &B,
150 mut val: u128,
151 tweak: &Tweak,
152) -> u128 {
153 val ^= tweak.0;
155
156 let arr: &mut Array<u8, U16> = val.as_mut_bytes().try_into().unwrap();
157 backend.decrypt_block(InOut::from(arr));
158
159 val ^ tweak.0
161}
162
163fn xts_decrypt_buffer_out_of_place<B: BlockCipherDecBackend<BlockSize = U16>>(
164 backend: &B,
165 src: PtrByteSlice<'_>,
166 mut dst: MutPtrByteSlice<'_>,
167 tweak: &mut Tweak,
168) {
169 debug_assert_eq!(src.len(), dst.len());
170 debug_assert_eq!(src.as_ptr() as usize % 64, 0);
171 debug_assert_eq!(dst.as_ptr() as usize % 64, 0);
172 debug_assert_eq!(src.len() % 64, 0);
173
174 let mut src_chunks = src.iter_as::<u128>();
175 let mut dst_chunks = dst.iter_as_mut::<u128>();
176
177 while let Some(s0) = src_chunks.next() {
178 let d0 = dst_chunks.next().unwrap();
179 #[cfg(target_arch = "aarch64")]
182 unsafe {
183 core::arch::asm!(
184 "dc zva, {0}",
185 in(reg) d0.as_ptr(),
186 options(nostack, preserves_flags),
187 );
188 }
189 let val0 = xts_decrypt_chunk(backend, s0, tweak);
190 d0.write(val0);
191 tweak.update();
192
193 let s1 = src_chunks.next().unwrap();
194 let d1 = dst_chunks.next().unwrap();
195 let val1 = xts_decrypt_chunk(backend, s1, tweak);
196 d1.write(val1);
197 tweak.update();
198
199 let s2 = src_chunks.next().unwrap();
200 let d2 = dst_chunks.next().unwrap();
201 let val2 = xts_decrypt_chunk(backend, s2, tweak);
202 d2.write(val2);
203 tweak.update();
204
205 let s3 = src_chunks.next().unwrap();
206 let d3 = dst_chunks.next().unwrap();
207 let val3 = xts_decrypt_chunk(backend, s3, tweak);
208 d3.write(val3);
209 tweak.update();
210 }
211}
212
213fn xts_decrypt_buffer<B: BlockCipherDecBackend<BlockSize = U16>>(
214 backend: &B,
215 src: PtrByteSlice<'_>,
216 mut dst: MutPtrByteSlice<'_>,
217 tweak: &mut Tweak,
218) {
219 debug_assert_eq!(src.len(), dst.len());
220 debug_assert!(src.as_ptr().cast::<u128>().is_aligned());
221 debug_assert!(dst.as_ptr().cast::<u128>().is_aligned());
222
223 let src_chunks = src.iter_as::<u128>();
224 let dst_chunks = dst.iter_as_mut::<u128>();
225
226 for (src_chunk, dst_chunk) in src_chunks.zip(dst_chunks) {
227 let val = xts_decrypt_chunk(backend, src_chunk, tweak);
228 dst_chunk.write(val);
229 tweak.update();
230 }
231}
232
233impl<'a, 'b> XtsProcessor<'a, 'b> {
234 pub fn new(tweak: Tweak, src: PtrByteSlice<'a>, dst: MutPtrByteSlice<'b>) -> Self {
237 assert_eq!(src.len(), dst.len(), "Source and destination lengths must match");
238 assert_eq!(src.len() % 64, 0, "length must be a multiple of 64 bytes");
239 assert_eq!(src.as_ptr() as usize % 64, 0, "src must be 64-byte aligned");
240 assert_eq!(dst.as_ptr() as usize % 64, 0, "dst must be 64-byte aligned");
241 #[cfg(target_arch = "aarch64")]
242 assert_dcz_block_size_is_64();
243 Self { tweak, src, dst }
244 }
245}
246
247impl BlockSizeUser for XtsProcessor<'_, '_> {
248 type BlockSize = U16;
249}
250
251impl BlockCipherEncClosure for XtsProcessor<'_, '_> {
252 fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
253 let Self { mut tweak, src, dst } = self;
254 xts_encrypt_buffer_out_of_place(backend, src, dst, &mut tweak);
255 }
256}
257
258impl BlockCipherDecClosure for XtsProcessor<'_, '_> {
259 fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
260 let Self { mut tweak, src, dst } = self;
261 xts_decrypt_buffer_out_of_place(backend, src, dst, &mut tweak);
262 }
263}
264
265impl<'a> XtsInPlaceProcessor<'a> {
266 pub fn new(tweak: Tweak, buf: MutPtrByteSlice<'a>) -> Self {
268 assert!(buf.as_ptr().cast::<u128>().is_aligned(), "buf must be 16 byte aligned");
269 let len = buf.len();
270 let ptr = buf.as_ptr_slice().as_ptr();
271 let src = unsafe { PtrByteSlice::new(std::ptr::slice_from_raw_parts(ptr, len)) };
276 Self { tweak, src, dst: buf }
277 }
278}
279
280impl BlockSizeUser for XtsInPlaceProcessor<'_> {
281 type BlockSize = U16;
282}
283
284impl BlockCipherEncClosure for XtsInPlaceProcessor<'_> {
285 fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
286 let Self { mut tweak, src, dst } = self;
287 xts_encrypt_buffer(backend, src, dst, &mut tweak);
288 }
289}
290
291impl BlockCipherDecClosure for XtsInPlaceProcessor<'_> {
292 fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
293 let Self { mut tweak, src, dst } = self;
294 xts_decrypt_buffer(backend, src, dst, &mut tweak);
295 }
296}
297
298pub struct XtsCtsProcessor<'a, 'b> {
302 tweak: Tweak,
303 src: PtrByteSlice<'a>,
304 dst: MutPtrByteSlice<'b>,
305}
306
307impl BlockSizeUser for XtsCtsProcessor<'_, '_> {
308 type BlockSize = U16;
309}
310
311impl<'a, 'b> XtsCtsProcessor<'a, 'b> {
312 pub fn new(tweak: Tweak, src: PtrByteSlice<'a>, dst: MutPtrByteSlice<'b>) -> Self {
315 assert_eq!(src.len(), dst.len(), "Source and destination lengths must match");
316 assert!(src.len() >= size_of::<u128>());
317 assert!(src.as_ptr().cast::<u128>().is_aligned(), "src must be 16 byte aligned");
318 assert!(dst.as_ptr().cast::<u128>().is_aligned(), "dst must be 16 byte aligned");
319 Self { tweak, src, dst }
320 }
321
322 pub fn new_in_place(tweak: Tweak, buf: MutPtrByteSlice<'a>) -> XtsCtsProcessor<'a, 'a> {
324 assert!(buf.len() >= size_of::<u128>());
325 assert!(buf.as_ptr().cast::<u128>().is_aligned(), "buf must be 16 byte aligned");
326 let len = buf.len();
327 let ptr = buf.as_ptr_slice().as_ptr();
328 let src = unsafe { PtrByteSlice::new(std::ptr::slice_from_raw_parts(ptr, len)) };
332 XtsCtsProcessor { tweak, src, dst: buf }
333 }
334}
335
336impl BlockCipherEncClosure for XtsCtsProcessor<'_, '_> {
337 fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
338 let Self { mut tweak, src, mut dst } = self;
339 let len = src.len();
340 let base_size = len & (!0x0F);
341
342 if len == base_size {
344 xts_encrypt_buffer(backend, src, dst, &mut tweak);
345 return;
346 }
347
348 if base_size > 16 {
350 let src_base = src.subslice(0..(base_size - 16));
351 let dst_base = dst.subslice_mut(0..(base_size - 16));
352 xts_encrypt_buffer(backend, src_base, dst_base, &mut tweak);
353 }
354
355 let last_full_block_range = (base_size - 16)..base_size;
356 let last_full_block =
357 src.subslice(last_full_block_range.clone()).read().expect("Size validated above");
358 let intermediate_ciphertext = xts_encrypt_chunk(backend, last_full_block, &tweak);
359
360 let extra = len & 0x0F;
361 let intermediate_cipher_bytes = intermediate_ciphertext.to_le_bytes();
362
363 let mut combined_block_bytes = intermediate_cipher_bytes;
367 src.subslice(base_size..len).copy_to_slice(&mut combined_block_bytes[0..extra]);
368
369 dst.subslice_mut(base_size..len).copy_from_slice(&intermediate_cipher_bytes[0..extra]);
371
372 tweak.update();
373
374 let combined_block = u128::from_le_bytes(combined_block_bytes);
375 let full_ciphertext = xts_encrypt_chunk(backend, combined_block, &tweak);
376
377 dst.subslice_mut(last_full_block_range)
379 .write(full_ciphertext)
380 .expect("Size validated above");
381 }
382}
383
384impl BlockCipherDecClosure for XtsCtsProcessor<'_, '_> {
385 fn call<B: BlockCipherDecBackend<BlockSize = Self::BlockSize>>(self, backend: &B) {
386 let Self { mut tweak, src, mut dst } = self;
387 let len = src.len();
388 let base_size = len & (!0x0F);
389
390 if len == base_size {
392 xts_decrypt_buffer(backend, src, dst, &mut tweak);
393 return;
394 }
395
396 if base_size > 16 {
398 let src_base = src.subslice(0..(base_size - 16));
399 let dst_base = dst.subslice_mut(0..(base_size - 16));
400 xts_decrypt_buffer(backend, src_base, dst_base, &mut tweak);
401 }
402
403 let last_full_block_tweak = tweak;
404 let last_full_block_range = (base_size - 16)..base_size;
405
406 tweak.update();
407 let extra = len & 0x0F;
408
409 let full_ciphertext =
411 src.subslice(last_full_block_range.clone()).read().expect("Size validated above");
412 let decrypted_combined_block = xts_decrypt_chunk(backend, full_ciphertext, &tweak);
413 let combined_bytes = decrypted_combined_block.to_le_bytes();
414
415 let mut intermediate_bytes = combined_bytes;
419 src.subslice(base_size..len).copy_to_slice(&mut intermediate_bytes[0..extra]);
420
421 dst.subslice_mut(base_size..len).copy_from_slice(&combined_bytes[0..extra]);
423
424 let last_block_ciphertext = u128::from_le_bytes(intermediate_bytes);
425 let val = xts_decrypt_chunk(backend, last_block_ciphertext, &last_full_block_tweak);
426 dst.subslice_mut(last_full_block_range).write(val).expect("Size validated above");
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use cipher::inout::InOut;
434 use cipher::typenum::consts::U1;
435 use cipher::{Block, ParBlocksSizeUser};
436 use std::cell::RefCell;
437 use test_case::test_case;
438 use zerocopy::{FromBytes, Immutable, IntoBytes};
439
440 struct MockCipher {
441 recorded_blocks: RefCell<Vec<u128>>,
442 key: u128,
443 }
444
445 impl MockCipher {
446 fn new(key: u128) -> Self {
447 Self { recorded_blocks: RefCell::new(Vec::new()), key }
448 }
449 }
450
451 impl BlockSizeUser for MockCipher {
452 type BlockSize = U16;
453 }
454
455 impl ParBlocksSizeUser for MockCipher {
456 type ParBlocksSize = U1;
457 }
458
459 impl BlockCipherEncBackend for MockCipher {
460 fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
461 let mut val =
463 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
464 self.recorded_blocks.borrow_mut().push(val);
465 val ^= self.key;
466 unsafe {
468 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
469 }
470 }
471 }
472
473 impl BlockCipherDecBackend for MockCipher {
474 fn decrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
475 let mut val =
477 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
478 self.recorded_blocks.borrow_mut().push(val);
479 val = val.rotate_right(3);
481 val ^= self.key;
482 unsafe {
484 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
485 }
486 }
487 }
488
489 struct MockNonLinearCipher {
492 key: u128,
493 }
494
495 impl MockNonLinearCipher {
496 fn new(key: u128) -> Self {
497 Self { key }
498 }
499 }
500
501 impl BlockSizeUser for MockNonLinearCipher {
502 type BlockSize = U16;
503 }
504
505 impl ParBlocksSizeUser for MockNonLinearCipher {
506 type ParBlocksSize = U1;
507 }
508
509 impl BlockCipherEncBackend for MockNonLinearCipher {
510 fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
511 let mut val =
512 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
513 val = val.rotate_left(11) ^ self.key;
514 unsafe {
515 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
516 }
517 }
518 }
519
520 impl BlockCipherDecBackend for MockNonLinearCipher {
521 fn decrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
522 let mut val =
523 unsafe { std::ptr::read_unaligned(block.get_in().as_ptr() as *const u128) };
524 val = (val ^ self.key).rotate_right(11);
525 unsafe {
526 std::ptr::write_unaligned(block.get_out().as_mut_ptr() as *mut u128, val);
527 }
528 }
529 }
530
531 #[repr(C)]
532 #[derive(FromBytes, IntoBytes, Immutable)]
533 struct Blocks<const N: usize>([u128; N]);
534
535 #[repr(C, align(64))]
536 struct Aligned64<T>(T);
537
538 static_assertions::const_assert!(std::mem::align_of::<Aligned64<Blocks<1>>>() == 64);
539
540 impl<const N: usize> Default for Blocks<N> {
541 fn default() -> Self {
542 Self([0u128; N])
543 }
544 }
545
546 #[test]
547 fn test_xts_out_of_place() {
548 let mut plaintext: Aligned64<Blocks<4>> = Aligned64(Default::default());
549 for (i, x) in plaintext.0.as_mut_bytes().iter_mut().enumerate() {
550 *x = i as u8;
551 }
552 let mut ciphertext: Aligned64<Blocks<4>> = Aligned64(Default::default());
553
554 let src = PtrByteSlice::from(plaintext.0.as_bytes());
555 let dst = MutPtrByteSlice::from(ciphertext.0.as_mut_bytes());
556
557 let tweak_val = 0x123456789abcdef0123456789abcdef0u128;
558 let tweak = Tweak::new(tweak_val);
559 let key = 0xffeeddccbbaa99887766554433221100u128;
560
561 let processor = XtsProcessor::new(tweak, src, dst);
562 let cipher = MockCipher::new(key);
563
564 BlockCipherEncClosure::call(processor, &cipher);
565
566 let expected_c0 =
570 u128::from_le_bytes(plaintext.0.as_bytes()[0..16].try_into().unwrap()) ^ key;
571 let expected_c1 =
572 u128::from_le_bytes(plaintext.0.as_bytes()[16..32].try_into().unwrap()) ^ key;
573
574 let actual_c0 = u128::from_le_bytes(ciphertext.0.as_bytes()[0..16].try_into().unwrap());
575 let actual_c1 = u128::from_le_bytes(ciphertext.0.as_bytes()[16..32].try_into().unwrap());
576
577 assert_eq!(actual_c0, expected_c0);
578 assert_eq!(actual_c1, expected_c1);
579
580 assert_eq!(cipher.recorded_blocks.borrow().len(), 4);
582
583 let p0 = u128::from_le_bytes(plaintext.0.as_bytes()[0..16].try_into().unwrap());
584 let p1 = u128::from_le_bytes(plaintext.0.as_bytes()[16..32].try_into().unwrap());
585
586 let mut t0 = tweak;
587 assert_eq!(cipher.recorded_blocks.borrow()[0], p0 ^ t0.0);
588 t0.update();
589 assert_eq!(cipher.recorded_blocks.borrow()[1], p1 ^ t0.0);
590 }
591
592 #[test]
593 fn test_xts_in_place() {
594 let mut buf: Blocks<2> = Default::default();
595 for (i, x) in buf.as_mut_bytes().iter_mut().enumerate() {
596 *x = i as u8;
597 }
598
599 let tweak_val = 0x123456789abcdef0123456789abcdef0u128;
600 let tweak = Tweak::new(tweak_val);
601 let key = 0xffeeddccbbaa99887766554433221100u128;
602
603 let p0 = u128::from_le_bytes(buf.as_bytes()[0..16].try_into().unwrap());
605 let p1 = u128::from_le_bytes(buf.as_bytes()[16..32].try_into().unwrap());
606
607 let slice = MutPtrByteSlice::from(buf.as_mut_bytes());
608 let processor = XtsInPlaceProcessor::new(tweak, slice);
609 let cipher = MockCipher::new(key);
610
611 BlockCipherEncClosure::call(processor, &cipher);
612
613 let expected_c0 = p0 ^ key;
615 let expected_c1 = p1 ^ key;
616
617 let actual_c0 = u128::from_le_bytes(buf.as_bytes()[0..16].try_into().unwrap());
618 let actual_c1 = u128::from_le_bytes(buf.as_bytes()[16..32].try_into().unwrap());
619
620 assert_eq!(actual_c0, expected_c0);
621 assert_eq!(actual_c1, expected_c1);
622
623 assert_eq!(cipher.recorded_blocks.borrow().len(), 2);
625 let mut t0 = tweak;
626 assert_eq!(cipher.recorded_blocks.borrow()[0], p0 ^ t0.0);
627 t0.update();
628 assert_eq!(cipher.recorded_blocks.borrow()[1], p1 ^ t0.0);
629 }
630
631 #[test_case(16; "exact_block")]
632 #[test_case(17; "one_byte_more_than_block")]
633 #[test_case(31; "one_byte_less_than_two_blocks")]
634 #[test_case(32; "exact_two_blocks")]
635 #[test_case(80; "bunch_of_blocks_exact")]
636 #[test_case(87; "bunch_of_blocks_cts")]
637 fn test_cts_encrypt_decrypt(len: usize) {
638 let tweak = Tweak::new(0x123456789abcdef0123456789abcdef0);
639 let key = 0xffeeddccbbaa99887766554433221100;
640
641 let mut plaintext_vec = vec![0u128; (len + 15) / 16];
642 let plaintext_bytes = plaintext_vec.as_mut_bytes();
643 for (i, b) in plaintext_bytes[..len].iter_mut().enumerate() {
644 *b = ((i % 255) + 1) as u8;
645 }
646 let plaintext = &plaintext_bytes[..len];
647
648 let mut ciphertext_vec = vec![0u128; (len + 15) / 16];
649 let ciphertext_bytes = ciphertext_vec.as_mut_bytes();
650
651 let mut decrypted_vec = vec![0u128; (len + 15) / 16];
652 let decrypted_bytes = decrypted_vec.as_mut_bytes();
653
654 let cipher = MockNonLinearCipher::new(key);
655
656 {
658 let src = PtrByteSlice::from(plaintext);
659 let dst = MutPtrByteSlice::from(&mut ciphertext_bytes[..len]);
660 let processor = XtsCtsProcessor::new(tweak, src, dst);
661 BlockCipherEncClosure::call(processor, &cipher);
662 }
663
664 let ciphertext = &ciphertext_bytes[..len];
665
666 assert_ne!(ciphertext, plaintext, "Ciphertext should not match plaintext");
668 for chunk_start in (0..len).step_by(16) {
669 let chunk_end = (chunk_start + 16).min(len);
670 assert_ne!(
671 &ciphertext[chunk_start..chunk_end],
672 &plaintext[chunk_start..chunk_end],
673 "Chunk at {chunk_start}..{chunk_end} should not match plaintext"
674 );
675 }
676
677 {
679 let src = PtrByteSlice::from(ciphertext);
680 let dst = MutPtrByteSlice::from(&mut decrypted_bytes[..len]);
681 let processor = XtsCtsProcessor::new(tweak, src, dst);
682 BlockCipherDecClosure::call(processor, &cipher);
683 }
684
685 let decrypted = &decrypted_bytes[..len];
686
687 assert_eq!(decrypted, plaintext, "Decrypted text should match original plaintext");
689 }
690
691 #[test]
692 fn test_cts_encrypt_decrypt_in_place() {
693 const LEN: usize = 87;
694
695 let tweak = Tweak::new(0x123456789abcdef0123456789abcdef0);
696 let key = 0xffeeddccbbaa99887766554433221100;
697
698 let mut buf_vec = vec![0u128; (LEN + 15) / 16];
699 let buf_bytes = buf_vec.as_mut_bytes();
700 for (i, b) in buf_bytes[..LEN].iter_mut().enumerate() {
701 *b = ((i % 255) + 1) as u8;
702 }
703 let original_plaintext = buf_bytes[..LEN].to_vec();
704
705 let cipher = MockNonLinearCipher::new(key);
706
707 {
709 let slice = MutPtrByteSlice::from(&mut buf_bytes[..LEN]);
710 let processor = XtsCtsProcessor::new_in_place(tweak, slice);
711 BlockCipherEncClosure::call(processor, &cipher);
712 }
713
714 assert_ne!(
716 &buf_bytes[..LEN],
717 &original_plaintext[..],
718 "In-place ciphertext should not match plaintext"
719 );
720 for chunk_start in (0..LEN).step_by(16) {
721 let chunk_end = (chunk_start + 16).min(LEN);
722 assert_ne!(
723 &buf_bytes[chunk_start..chunk_end],
724 &original_plaintext[chunk_start..chunk_end],
725 "Chunk at {chunk_start}..{chunk_end} should not match plaintext"
726 );
727 }
728
729 {
731 let slice = MutPtrByteSlice::from(&mut buf_bytes[..LEN]);
732 let processor = XtsCtsProcessor::new_in_place(tweak, slice);
733 BlockCipherDecClosure::call(processor, &cipher);
734 }
735
736 assert_eq!(
738 &buf_bytes[..LEN],
739 &original_plaintext[..],
740 "Decrypted in-place text should match original plaintext"
741 );
742 }
743
744 #[test_case(4; "four_blocks")]
745 #[test_case(8; "eight_blocks")]
746 fn test_cts_matches_normal_xts_on_exact_blocks(num_blocks: usize) {
747 let tweak = Tweak::new(0x9876543210abcdef9876543210abcdef);
748 let key = 0x0123456789abcdef0123456789abcdef;
749
750 let len = num_blocks * 16;
751 let mut plaintext_vec = vec![0u128; num_blocks + 4];
752 let addr = plaintext_vec.as_ptr() as usize;
753 let offset = (64 - (addr % 64)) % 64;
754 let plaintext_bytes = &mut plaintext_vec.as_mut_bytes()[offset..offset + len];
755 for (i, b) in plaintext_bytes.iter_mut().enumerate() {
756 *b = (i as u8).wrapping_mul(17).wrapping_add(3);
757 }
758
759 let mut normal_cts_vec = vec![0u128; num_blocks + 4];
760 let normal_addr = normal_cts_vec.as_ptr() as usize;
761 let normal_offset = (64 - (normal_addr % 64)) % 64;
762 let normal_cts_bytes =
763 &mut normal_cts_vec.as_mut_bytes()[normal_offset..normal_offset + len];
764
765 let mut cts_vec = vec![0u128; num_blocks + 4];
766 let cts_addr = cts_vec.as_ptr() as usize;
767 let cts_offset = (64 - (cts_addr % 64)) % 64;
768 let cts_bytes = &mut cts_vec.as_mut_bytes()[cts_offset..cts_offset + len];
769
770 let cipher = MockNonLinearCipher::new(key);
771
772 {
774 let src = PtrByteSlice::from(&*plaintext_bytes);
775 let dst = MutPtrByteSlice::from(&mut *normal_cts_bytes);
776 let processor = XtsProcessor::new(tweak, src, dst);
777 BlockCipherEncClosure::call(processor, &cipher);
778 }
779
780 {
782 let src = PtrByteSlice::from(&*plaintext_bytes);
783 let dst = MutPtrByteSlice::from(&mut *cts_bytes);
784 let processor = XtsCtsProcessor::new(tweak, src, dst);
785 BlockCipherEncClosure::call(processor, &cipher);
786 }
787
788 assert_eq!(
789 normal_cts_bytes, cts_bytes,
790 "CTS XTS should match normal XTS for {num_blocks} blocks"
791 );
792 }
793
794 #[test_case(1; "one_block")]
795 #[test_case(2; "two_blocks")]
796 #[test_case(5; "five_blocks")]
797 fn test_cts_matches_inplace_xts_on_exact_blocks(num_blocks: usize) {
798 let tweak = Tweak::new(0x9876543210abcdef9876543210abcdef);
799 let key = 0x0123456789abcdef0123456789abcdef;
800
801 let len = num_blocks * 16;
802 let mut plaintext_vec = vec![0u128; num_blocks];
803 let plaintext_bytes = plaintext_vec.as_mut_bytes();
804 for (i, b) in plaintext_bytes[..len].iter_mut().enumerate() {
805 *b = (i as u8).wrapping_mul(17).wrapping_add(3);
806 }
807 let plaintext = &plaintext_bytes[..len];
808
809 let mut normal_cts_vec = vec![0u128; num_blocks];
810 let mut cts_vec = vec![0u128; num_blocks];
811
812 let cipher = MockNonLinearCipher::new(key);
813
814 {
816 let mut slice = MutPtrByteSlice::from(&mut normal_cts_vec.as_mut_bytes()[..len]);
817 slice.copy_from_slice(plaintext);
818 let processor = XtsInPlaceProcessor::new(tweak, slice);
819 BlockCipherEncClosure::call(processor, &cipher);
820 }
821
822 {
824 let src = PtrByteSlice::from(plaintext);
825 let dst = MutPtrByteSlice::from(&mut cts_vec.as_mut_bytes()[..len]);
826 let processor = XtsCtsProcessor::new(tweak, src, dst);
827 BlockCipherEncClosure::call(processor, &cipher);
828 }
829
830 assert_eq!(
831 normal_cts_vec, cts_vec,
832 "CTS XTS should match in-place XTS for {num_blocks} blocks"
833 );
834 }
835
836 #[test]
837 fn test_cts_different_tweaks_different_ciphertext() {
838 const LEN: usize = 87;
839
840 let tweak1 = Tweak::new(0x123456789abcdef0123456789abcdef0);
841 let tweak2 = Tweak::new(0xfeef876543210fedcba9876543210fed);
842 let key = 0xffeeddccbbaa99887766554433221100;
843
844 let mut plaintext_vec = vec![0u128; (LEN + 15) / 16];
845 let plaintext_bytes = plaintext_vec.as_mut_bytes();
846 for (i, b) in plaintext_bytes[..LEN].iter_mut().enumerate() {
847 *b = ((i % 255) + 1) as u8;
848 }
849 let plaintext = &plaintext_bytes[..LEN];
850
851 let mut ciphertext1_vec = vec![0u128; (LEN + 15) / 16];
852 let ciphertext1_bytes = ciphertext1_vec.as_mut_bytes();
853
854 let mut ciphertext2_vec = vec![0u128; (LEN + 15) / 16];
855 let ciphertext2_bytes = ciphertext2_vec.as_mut_bytes();
856
857 let cipher = MockNonLinearCipher::new(key);
858
859 {
861 let src = PtrByteSlice::from(plaintext);
862 let dst = MutPtrByteSlice::from(&mut ciphertext1_bytes[..LEN]);
863 let processor = XtsCtsProcessor::new(tweak1, src, dst);
864 BlockCipherEncClosure::call(processor, &cipher);
865 }
866
867 {
869 let src = PtrByteSlice::from(plaintext);
870 let dst = MutPtrByteSlice::from(&mut ciphertext2_bytes[..LEN]);
871 let processor = XtsCtsProcessor::new(tweak2, src, dst);
872 BlockCipherEncClosure::call(processor, &cipher);
873 }
874
875 assert_ne!(
876 &ciphertext1_bytes[..LEN],
877 &ciphertext2_bytes[..LEN],
878 "Different tweaks should produce different ciphertexts"
879 );
880 }
881
882 #[test]
883 #[should_panic(expected = "Source and destination lengths must match")]
884 fn test_cts_panic_length_mismatch() {
885 let tweak = Tweak::new(0);
886 let src_buf = vec![0u128; 2];
887 let mut dst_buf = vec![0u128; 3];
888 let src = PtrByteSlice::from(src_buf.as_bytes());
889 let dst = MutPtrByteSlice::from(dst_buf.as_mut_bytes());
890 let _ = XtsCtsProcessor::new(tweak, src, dst);
891 }
892
893 #[test]
894 #[should_panic]
895 fn test_cts_panic_too_short() {
896 let tweak = Tweak::new(0);
897 let src_buf = vec![0u128; 2];
898 let mut dst_buf = vec![0u128; 2];
899 let src = PtrByteSlice::from(&src_buf.as_bytes()[..15]);
900 let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[..15]);
901 let _ = XtsCtsProcessor::new(tweak, src, dst);
902 }
903
904 #[test]
905 #[should_panic(expected = "src must be 16 byte aligned")]
906 fn test_cts_panic_unaligned_src() {
907 let tweak = Tweak::new(0);
908 let src_buf = vec![0u128; 3];
909 let mut dst_buf = vec![0u128; 2];
910 let src = PtrByteSlice::from(&src_buf.as_bytes()[1..17]);
911 let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[..16]);
912 let _ = XtsCtsProcessor::new(tweak, src, dst);
913 }
914
915 #[test]
916 #[should_panic(expected = "dst must be 16 byte aligned")]
917 fn test_cts_panic_unaligned_dst() {
918 let tweak = Tweak::new(0);
919 let src_buf = vec![0u128; 2];
920 let mut dst_buf = vec![0u128; 3];
921 let src = PtrByteSlice::from(&src_buf.as_bytes()[..16]);
922 let dst = MutPtrByteSlice::from(&mut dst_buf.as_mut_bytes()[1..17]);
923 let _ = XtsCtsProcessor::new(tweak, src, dst);
924 }
925}