1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Implementation of the XTS-AES tweakable block cipher.
16 //!
17 //! See NIST docs [here](https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/1619-2007-NIST-Submission.pdf)
18 //! and [here](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38e.pdf).
19
20 #![no_std]
21
22 #[cfg(feature = "std")]
23 extern crate std;
24
25 use array_ref::{array_mut_ref, array_ref};
26 use core::fmt;
27 use core::marker::PhantomData;
28
29 use crypto_provider::aes::{Aes, AesBlock, AesCipher, AesDecryptCipher, AesEncryptCipher};
30 use crypto_provider::{
31 aes::{AesKey, BLOCK_SIZE},
32 CryptoProvider,
33 };
34
35 use ldt_tbc::{
36 TweakableBlockCipher, TweakableBlockCipherDecrypter, TweakableBlockCipherEncrypter,
37 TweakableBlockCipherKey,
38 };
39
40 #[cfg(test)]
41 mod tweak_tests;
42
43 /// XTS-AES as per NIST docs
44 /// [here](https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/1619-2007-NIST-Submission.pdf)
45 /// and
46 /// [here](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38e.pdf).
47 ///
48 /// Data encrypted with XTS is divided into data units, each of which corresponds to one tweak
49 /// (assigned consecutively). If using a numeric tweak (`u128`), the tweak must be converted to
50 /// little endian bytes (e.g. with `u128::to_le_bytes()`).
51 ///
52 /// Inside each data unit, there are one or more 16-byte blocks. The length of a data unit SHOULD
53 /// not to exceed 2^20 blocks (16MB) in order to maintain security properties; see
54 /// [appendix D](https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/1619-2007-NIST-Submission.pdf).
55 /// The last block (if there is more than one block) may have fewer than 16 bytes, in which case
56 /// ciphertext stealing will be applied.
57 ///
58 /// Each block in a data unit has its own block number, generated consecutively, incorporated into
59 /// the tweak used to encrypt that block.
60 ///
61 /// There is no support for partial bytes (bit lengths that aren't a multiple of 8).
62 pub struct XtsAes<A: Aes<Key = K::BlockCipherKey>, K: XtsKey + TweakableBlockCipherKey> {
63 _marker: PhantomData<A>,
64 _marker2: PhantomData<K>,
65 }
66
67 impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey + TweakableBlockCipherKey>
68 TweakableBlockCipher<BLOCK_SIZE> for XtsAes<A, K>
69 {
70 type EncryptionCipher = XtsEncrypter<A, K>;
71 type DecryptionCipher = XtsDecrypter<A, K>;
72 type Tweak = Tweak;
73 type Key = K;
74 }
75
76 /// The XtsAes128 implementation
77 pub type XtsAes128<C> = XtsAes<<C as CryptoProvider>::Aes128, XtsAes128Key>;
78
79 /// The XtsAes256 implementation
80 pub type XtsAes256<C> = XtsAes<<C as CryptoProvider>::Aes256, XtsAes256Key>;
81
82 /// Struct which provides Xts Aes Encrypt operations
83 #[repr(C)]
84 pub struct XtsEncrypter<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> {
85 main_encryption_cipher: A::EncryptCipher,
86 tweak_encryption_cipher: A::EncryptCipher,
87 _marker: PhantomData<K>,
88 }
89
90 /// Struct which provides Xts Aes Decrypt operations
91 #[repr(C)]
92 pub struct XtsDecrypter<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> {
93 main_decryption_cipher: A::DecryptCipher,
94 tweak_encryption_cipher: A::EncryptCipher,
95 _marker: PhantomData<K>,
96 }
97
98 #[allow(clippy::expect_used)]
99 impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsEncrypter<A, K> {
100 /// Encrypt a data unit in place, using sequential block numbers for each block.
101 /// `data_unit` must be at least [BLOCK_SIZE] bytes, and fewer than
102 /// `BLOCK_SIZE * 2^20` bytes.
encrypt_data_unit(&self, tweak: Tweak, data_unit: &mut [u8]) -> Result<(), XtsError>103 pub fn encrypt_data_unit(&self, tweak: Tweak, data_unit: &mut [u8]) -> Result<(), XtsError> {
104 let (standalone_blocks, last_complete_block, partial_last_block) =
105 data_unit_parts(data_unit)?;
106
107 let mut tweaked_xts = self.tweaked(tweak);
108
109 standalone_blocks.chunks_mut(BLOCK_SIZE).for_each(|block| {
110 // Until array_chunks is stabilized, using a macro to get array refs out of slice chunks
111 // Won't panic because we are only processing complete blocks
112 #[allow(clippy::indexing_slicing)]
113 tweaked_xts.encrypt_block(array_mut_ref!(block, 0, BLOCK_SIZE));
114 tweaked_xts.advance_to_next_block_num();
115 });
116
117 if partial_last_block.is_empty() {
118 #[allow(clippy::indexing_slicing)]
119 tweaked_xts.encrypt_block(array_mut_ref!(last_complete_block, 0, BLOCK_SIZE));
120 // nothing to do for the last block since it's empty
121 } else {
122 // This implements ciphertext stealing for the last two blocks which allows processing
123 // of a message length not divisible by block size without any expansion or padding.
124 // b is in bytes not bits; we do not consider partial bytes
125 let b = partial_last_block.len();
126
127 // Per spec: CC = encrypt P_{m-1} (the last complete block)
128 let cc = {
129 let mut last_complete_block_plaintext: AesBlock =
130 last_complete_block.try_into().expect("complete block");
131 tweaked_xts.encrypt_block(&mut last_complete_block_plaintext);
132 tweaked_xts.advance_to_next_block_num();
133 last_complete_block_plaintext
134 };
135
136 // Copy b bytes of P_m before we overwrite them with C_m
137 let mut partial_last_block_plaintext: AesBlock = [0; BLOCK_SIZE];
138 partial_last_block_plaintext
139 .get_mut(0..b)
140 .expect("this should never be hit, since a partial block is always smaller than a complete block")
141 .copy_from_slice(partial_last_block);
142
143 // C_m = first b bytes of CC
144 partial_last_block.copy_from_slice(
145 cc.get(0..b)
146 .expect("b is the len of partial_last_block so it will always be in bounds"),
147 );
148
149 // PP = P_m || last (16 - b) bytes of CC
150 let mut pp = {
151 // the first b bytes have already been written as C_m, so it's safe to overwrite
152 let mut cc = cc;
153 cc.get_mut(0..b)
154 .expect("partial block len should always be less than a complete block")
155 .copy_from_slice(
156 partial_last_block_plaintext
157 .get(0..b)
158 .expect("b is in range of block length"),
159 );
160 cc
161 };
162
163 // C_{m-1} = encrypt PP
164 tweaked_xts.encrypt_block(&mut pp);
165 last_complete_block.copy_from_slice(&pp[..]);
166 }
167
168 Ok(())
169 }
170
171 /// Returns an [`XtsEncrypterTweaked`] configured with the specified tweak and a block number of 0.
tweaked(&self, tweak: Tweak) -> XtsEncrypterTweaked<A>172 fn tweaked(&self, tweak: Tweak) -> XtsEncrypterTweaked<A> {
173 let mut bytes = tweak.bytes;
174 self.tweak_encryption_cipher.encrypt(&mut bytes);
175
176 XtsEncrypterTweaked {
177 tweak_state: TweakState::new(bytes),
178 enc_cipher: &self.main_encryption_cipher,
179 }
180 }
181 }
182
183 #[allow(clippy::expect_used)]
184 impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey> XtsDecrypter<A, K> {
185 /// Decrypt a data unit in place, using sequential block numbers for each block.
186 /// `data_unit` must be at least [BLOCK_SIZE] bytes, and fewer than
187 /// `BLOCK_SIZE * 2^20` bytes.
decrypt_data_unit(&self, tweak: Tweak, data_unit: &mut [u8]) -> Result<(), XtsError>188 pub fn decrypt_data_unit(&self, tweak: Tweak, data_unit: &mut [u8]) -> Result<(), XtsError> {
189 let (standalone_blocks, last_complete_block, partial_last_block) =
190 data_unit_parts(data_unit)?;
191
192 let mut tweaked_xts = self.tweaked(tweak);
193
194 standalone_blocks.chunks_mut(BLOCK_SIZE).for_each(|block| {
195 #[allow(clippy::indexing_slicing)]
196 tweaked_xts.decrypt_block(array_mut_ref!(block, 0, BLOCK_SIZE));
197 tweaked_xts.advance_to_next_block_num();
198 });
199
200 if partial_last_block.is_empty() {
201 #[allow(clippy::indexing_slicing)]
202 tweaked_xts.decrypt_block(array_mut_ref!(last_complete_block, 0, BLOCK_SIZE));
203 } else {
204 let b = partial_last_block.len();
205
206 // tweak state is currently at m-1 block, so capture m-1 state for later use
207 let tweak_state_m_1 = tweaked_xts.current_tweak();
208
209 // Per spec: PP = encrypt C_{m-1} (the last complete block) at block num m
210 let pp = {
211 tweaked_xts.advance_to_next_block_num();
212 // Block num is now at m
213 // We need C_m later, so make a copy to avoid overwriting
214 let mut last_complete_block_ciphertext: AesBlock =
215 last_complete_block.try_into().expect("complete block");
216 tweaked_xts.decrypt_block(&mut last_complete_block_ciphertext);
217 tweaked_xts.set_tweak(tweak_state_m_1);
218 last_complete_block_ciphertext
219 };
220
221 // Copy b bytes of C_m before we overwrite them with P_m
222 let mut partial_last_block_ciphertext: AesBlock = [0; BLOCK_SIZE];
223 partial_last_block_ciphertext
224 .get_mut(0..b)
225 .expect("this should never be hit, since a partial block is always smaller than a complete block")
226 .copy_from_slice(partial_last_block);
227
228 // P_m = first b bytes of PP
229 partial_last_block.copy_from_slice(
230 pp.get(0..b)
231 .expect("b is the len of partial_last_block so it will always be in bounds"),
232 );
233
234 // CC = C_m | CP (last 16-b bytes of PP)
235 let cc = {
236 let cp = pp.get(b..).expect(
237 "b is in bounds since a partial block is always smaller than a complete block",
238 );
239 last_complete_block
240 .get_mut(0..b)
241 .expect("partial block length within bounds of complete block")
242 .copy_from_slice(
243 partial_last_block_ciphertext
244 .get(0..b)
245 .expect("partial block length within bounds of complete block"),
246 );
247 last_complete_block
248 .get_mut(b..)
249 .expect("partial block length within bounds of complete block")
250 .copy_from_slice(cp);
251 last_complete_block
252 };
253
254 // decrypting at block num = m -1
255 #[allow(clippy::indexing_slicing)]
256 tweaked_xts.decrypt_block(array_mut_ref!(cc, 0, BLOCK_SIZE));
257 }
258
259 Ok(())
260 }
261
262 /// Returns an [`XtsDecrypterTweaked`] configured with the specified tweak and a block number of 0.
tweaked(&self, tweak: Tweak) -> XtsDecrypterTweaked<A>263 fn tweaked(&self, tweak: Tweak) -> XtsDecrypterTweaked<A> {
264 let mut bytes = tweak.bytes;
265 self.tweak_encryption_cipher.encrypt(&mut bytes);
266
267 XtsDecrypterTweaked {
268 tweak_state: TweakState::new(bytes),
269 dec_cipher: &self.main_decryption_cipher,
270 }
271 }
272 }
273
274 type DataUnitPartsResult<'a> = Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8]), XtsError>;
275
276 /// Returns `(standalone blocks, last complete block, partial last block)`.
data_unit_parts(data_unit: &mut [u8]) -> DataUnitPartsResult277 fn data_unit_parts(data_unit: &mut [u8]) -> DataUnitPartsResult {
278 if data_unit.len() < BLOCK_SIZE {
279 return Err(XtsError::DataTooShort);
280 } else if data_unit.len() > MAX_XTS_SIZE {
281 return Err(XtsError::DataTooLong);
282 }
283 // complete_blocks >= 1
284 let complete_blocks = data_unit.len() / BLOCK_SIZE;
285 // standalone_units >= 0 blocks, suffix = last complete block + possible partial block.
286 let (standalone_blocks, suffix) = data_unit.split_at_mut((complete_blocks - 1) * BLOCK_SIZE);
287 let (last_complete_block, partial_last_block) = suffix.split_at_mut(BLOCK_SIZE);
288 Ok((standalone_blocks, last_complete_block, partial_last_block))
289 }
290
291 impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey + TweakableBlockCipherKey>
292 TweakableBlockCipherEncrypter<BLOCK_SIZE> for XtsEncrypter<A, K>
293 {
294 type Key = K;
295 type Tweak = Tweak;
296
297 /// Build an [XtsEncrypter] with the provided [Aes] and the provided key.
new(key: &Self::Key) -> Self298 fn new(key: &Self::Key) -> Self {
299 XtsEncrypter {
300 main_encryption_cipher: A::EncryptCipher::new(key.key_1()),
301 tweak_encryption_cipher: A::EncryptCipher::new(key.key_2()),
302 _marker: Default::default(),
303 }
304 }
305
306 #[allow(clippy::expect_used)]
encrypt(&self, tweak: Self::Tweak, block: &mut [u8; BLOCK_SIZE])307 fn encrypt(&self, tweak: Self::Tweak, block: &mut [u8; BLOCK_SIZE]) {
308 // We're encrypting precisely one block, so the block number won't advance, and there is no
309 // need to apply ciphertext stealing
310 self.tweaked(tweak).encrypt_block(block)
311 }
312 }
313
314 impl<A: Aes<Key = K::BlockCipherKey>, K: XtsKey + TweakableBlockCipherKey>
315 TweakableBlockCipherDecrypter<BLOCK_SIZE> for XtsDecrypter<A, K>
316 {
317 type Key = K;
318 type Tweak = Tweak;
319
new(key: &K) -> Self320 fn new(key: &K) -> Self {
321 XtsDecrypter {
322 main_decryption_cipher: A::DecryptCipher::new(key.key_1()),
323 tweak_encryption_cipher: A::EncryptCipher::new(key.key_2()),
324 _marker: Default::default(),
325 }
326 }
327
328 #[allow(clippy::expect_used)]
decrypt(&self, tweak: Self::Tweak, block: &mut [u8; BLOCK_SIZE])329 fn decrypt(&self, tweak: Self::Tweak, block: &mut [u8; BLOCK_SIZE]) {
330 // We don't need the ciphertext stealing here since the input size is always exactly one block
331 self.tweaked(tweak).decrypt_block(block)
332 }
333 }
334
335 /// Errors that can occur during XTS encryption/decryption.
336 #[derive(Debug, PartialEq, Eq)]
337 pub enum XtsError {
338 /// The data is less than one AES block
339 DataTooShort,
340 /// The data is longer than 2^20 blocks, at which point XTS security degrades
341 DataTooLong,
342 }
343
344 /// XTS spec recommends to not go beyond 2^20 blocks.
345 const MAX_XTS_SIZE: usize = (1 << 20) * BLOCK_SIZE;
346
347 /// An XTS key comprised of two keys for the underlying block cipher.
348 pub trait XtsKey: for<'a> TryFrom<&'a [u8], Error = Self::TryFromError> {
349 /// The key used by the block cipher underlying XTS
350 type BlockCipherKey;
351 /// The error returned when `TryFrom<&[u8]>` fails.
352 type TryFromError: fmt::Debug;
353
354 /// Returns the first of the two block cipher keys.
key_1(&self) -> &Self::BlockCipherKey355 fn key_1(&self) -> &Self::BlockCipherKey;
356 /// Returns the second of the two block cipher keys.
key_2(&self) -> &Self::BlockCipherKey357 fn key_2(&self) -> &Self::BlockCipherKey;
358 }
359
360 const AES_128_KEY_SIZE: usize = 16;
361
362 /// An XTS-AES-128 key.
363 pub struct XtsAes128Key {
364 key_1: <Self as XtsKey>::BlockCipherKey,
365 key_2: <Self as XtsKey>::BlockCipherKey,
366 }
367
368 impl XtsKey for XtsAes128Key {
369 type BlockCipherKey = crypto_provider::aes::Aes128Key;
370 type TryFromError = XtsKeyTryFromSliceError;
371
key_1(&self) -> &Self::BlockCipherKey372 fn key_1(&self) -> &Self::BlockCipherKey {
373 &self.key_1
374 }
375
key_2(&self) -> &Self::BlockCipherKey376 fn key_2(&self) -> &Self::BlockCipherKey {
377 &self.key_2
378 }
379 }
380
381 impl TryFrom<&[u8]> for XtsAes128Key {
382 type Error = XtsKeyTryFromSliceError;
383
try_from(slice: &[u8]) -> Result<Self, Self::Error>384 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
385 try_split_concat_key::<AES_128_KEY_SIZE>(slice)
386 .map(|(key_1, key_2)| Self { key_1: key_1.into(), key_2: key_2.into() })
387 .ok_or_else(XtsKeyTryFromSliceError::new)
388 }
389 }
390
391 #[allow(clippy::expect_used)]
392 impl From<&[u8; 32]> for XtsAes128Key {
from(array: &[u8; 32]) -> Self393 fn from(array: &[u8; 32]) -> Self {
394 let arr1: [u8; AES_128_KEY_SIZE] =
395 array[..AES_128_KEY_SIZE].try_into().expect("array is correctly sized");
396 let arr2: [u8; AES_128_KEY_SIZE] =
397 array[AES_128_KEY_SIZE..].try_into().expect("array is correctly sized");
398 Self {
399 key_1: crypto_provider::aes::Aes128Key::from(arr1),
400 key_2: crypto_provider::aes::Aes128Key::from(arr2),
401 }
402 }
403 }
404
405 impl TweakableBlockCipherKey for XtsAes128Key {
406 type ConcatenatedKeyArray = [u8; 64];
407
408 // Allow index slicing, since a panic will be impossible to hit
409 #[allow(clippy::indexing_slicing)]
split_from_concatenated(key: &Self::ConcatenatedKeyArray) -> (Self, Self)410 fn split_from_concatenated(key: &Self::ConcatenatedKeyArray) -> (Self, Self) {
411 ((array_ref!(key, 0, 32)).into(), (array_ref!(key, 32, 32)).into())
412 }
413
concatenate_with(&self, other: &Self) -> Self::ConcatenatedKeyArray414 fn concatenate_with(&self, other: &Self) -> Self::ConcatenatedKeyArray {
415 let mut out = [0; 64];
416 out[..16].copy_from_slice(self.key_1().as_slice());
417 out[16..32].copy_from_slice(self.key_2().as_slice());
418 out[32..48].copy_from_slice(other.key_1().as_slice());
419 out[48..].copy_from_slice(other.key_2().as_slice());
420
421 out
422 }
423 }
424
425 const AES_256_KEY_SIZE: usize = 32;
426
427 /// An XTS-AES-256 key.
428 pub struct XtsAes256Key {
429 key_1: <Self as XtsKey>::BlockCipherKey,
430 key_2: <Self as XtsKey>::BlockCipherKey,
431 }
432
433 impl XtsKey for XtsAes256Key {
434 type BlockCipherKey = crypto_provider::aes::Aes256Key;
435 type TryFromError = XtsKeyTryFromSliceError;
436
key_1(&self) -> &Self::BlockCipherKey437 fn key_1(&self) -> &Self::BlockCipherKey {
438 &self.key_1
439 }
440
key_2(&self) -> &Self::BlockCipherKey441 fn key_2(&self) -> &Self::BlockCipherKey {
442 &self.key_2
443 }
444 }
445
446 impl TryFrom<&[u8]> for XtsAes256Key {
447 type Error = XtsKeyTryFromSliceError;
448
try_from(slice: &[u8]) -> Result<Self, Self::Error>449 fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
450 try_split_concat_key::<AES_256_KEY_SIZE>(slice)
451 .map(|(key_1, key_2)| Self { key_1: key_1.into(), key_2: key_2.into() })
452 .ok_or_else(XtsKeyTryFromSliceError::new)
453 }
454 }
455
456 #[allow(clippy::expect_used)]
457 impl From<&[u8; 64]> for XtsAes256Key {
from(array: &[u8; 64]) -> Self458 fn from(array: &[u8; 64]) -> Self {
459 let arr1: [u8; AES_256_KEY_SIZE] =
460 array[..AES_256_KEY_SIZE].try_into().expect("array is correctly sized");
461 let arr2: [u8; AES_256_KEY_SIZE] =
462 array[AES_256_KEY_SIZE..].try_into().expect("array is correctly sized");
463 Self {
464 key_1: crypto_provider::aes::Aes256Key::from(arr1),
465 key_2: crypto_provider::aes::Aes256Key::from(arr2),
466 }
467 }
468 }
469
470 impl TweakableBlockCipherKey for XtsAes256Key {
471 type ConcatenatedKeyArray = [u8; 128];
472
473 // Allow index slicing, since a panic will be impossible to hit
474 #[allow(clippy::indexing_slicing)]
split_from_concatenated(key: &Self::ConcatenatedKeyArray) -> (Self, Self)475 fn split_from_concatenated(key: &Self::ConcatenatedKeyArray) -> (Self, Self) {
476 ((array_ref!(key, 0, 64)).into(), (array_ref!(key, 64, 64)).into())
477 }
478
concatenate_with(&self, other: &Self) -> Self::ConcatenatedKeyArray479 fn concatenate_with(&self, other: &Self) -> Self::ConcatenatedKeyArray {
480 let mut out = [0; 128];
481 out[..32].copy_from_slice(self.key_1().as_slice());
482 out[32..64].copy_from_slice(self.key_2().as_slice());
483 out[64..96].copy_from_slice(other.key_1().as_slice());
484 out[96..].copy_from_slice(other.key_2().as_slice());
485
486 out
487 }
488 }
489
490 /// The error returned when converting from a slice fails.
491 #[derive(Debug)]
492 pub struct XtsKeyTryFromSliceError {
493 _private: (),
494 }
495
496 impl XtsKeyTryFromSliceError {
new() -> Self497 fn new() -> Self {
498 Self { _private: () }
499 }
500 }
501
502 /// The tweak for an XTS-AES cipher.
503 #[derive(Clone)]
504 pub struct Tweak {
505 bytes: AesBlock,
506 }
507
508 impl Tweak {
509 /// Little-endian content of the tweak.
le_bytes(&self) -> AesBlock510 pub fn le_bytes(&self) -> AesBlock {
511 self.bytes
512 }
513 }
514
515 impl From<AesBlock> for Tweak {
from(bytes: AesBlock) -> Self516 fn from(bytes: AesBlock) -> Self {
517 Self { bytes }
518 }
519 }
520
521 impl From<u128> for Tweak {
from(n: u128) -> Self522 fn from(n: u128) -> Self {
523 Self { bytes: n.to_le_bytes() }
524 }
525 }
526
527 /// An XTS tweak advanced to a particular block num.
528 #[derive(Clone)]
529 pub(crate) struct TweakState {
530 /// The block number inside the data unit. Should not exceed 2^20.
531 block_num: u32,
532 /// Original tweak multiplied by the primitive polynomial `block_num` times as per section 5.2
533 tweak: AesBlock,
534 }
535
536 impl TweakState {
537 /// Create a TweakState from the provided state with block_num = 0.
new(tweak: [u8; BLOCK_SIZE]) -> TweakState538 fn new(tweak: [u8; BLOCK_SIZE]) -> TweakState {
539 TweakState { block_num: 0, tweak }
540 }
541
542 /// Advance the tweak state in the data unit to the next block without encrypting
543 /// or decrypting the intermediate blocks.
544 #[allow(clippy::indexing_slicing)]
advance_to_next_block(&mut self)545 fn advance_to_next_block(&mut self) {
546 // Conceptual left shift across the bytes.
547 // Most significant byte: if shift would carry, XOR in the coefficients of primitive
548 // polynomial in F_2^128 (x^128 = x^7 + x^2 + x + 1) => 0b1000_0111 in binary.
549 let mut target = [0_u8; BLOCK_SIZE];
550 target[0] = (self.tweak[0] << 1) ^ ((self.tweak[BLOCK_SIZE - 1] >> 7) * 0b1000_0111);
551 for (j, byte) in target.iter_mut().enumerate().skip(1) {
552 *byte = (self.tweak[j] << 1) ^ (self.tweak[j - 1] >> 7);
553 }
554 self.tweak = target;
555 self.block_num += 1;
556 }
557
558 /// Advance the tweak state in the data unit to the `block_num`'th block without encrypting
559 /// or decrypting the intermediate blocks.
560 ///
561 /// `block_num` should not exceed 2^20.
562 ///
563 /// # Panics
564 /// - If `block_num` is less than the current block num
565 #[cfg(test)]
advance_to_block(&mut self, block_num: u32)566 fn advance_to_block(&mut self, block_num: u32) {
567 // It's a programmer error; nothing to recover from
568 assert!(self.block_num <= block_num);
569
570 // Multiply by the primitive polynomial as many times as needed, as per section 5.2
571 // of IEEE spec
572 #[allow(clippy::indexing_slicing)]
573 for _ in 0..(block_num - self.block_num) {
574 self.advance_to_next_block()
575 }
576 }
577 }
578
579 /// An XTS-AES cipher configured with an initial tweak that can be advanced through the block
580 /// numbers for that tweak's data unit.
581 ///
582 /// Encryption or decryption is per-block only; ciphertext stealing is not implemented at this
583 /// level.
584 struct XtsEncrypterTweaked<'a, A: Aes> {
585 tweak_state: TweakState,
586 enc_cipher: &'a A::EncryptCipher,
587 }
588
589 impl<'a, A: Aes> XtsEncrypterTweaked<'a, A> {
advance_to_next_block_num(&mut self)590 fn advance_to_next_block_num(&mut self) {
591 self.tweak_state.advance_to_next_block()
592 }
593
594 /// Encrypt a block in place using the configured tweak and current block number.
encrypt_block(&self, block: &mut AesBlock)595 fn encrypt_block(&self, block: &mut AesBlock) {
596 array_xor(block, &self.tweak_state.tweak);
597 self.enc_cipher.encrypt(block);
598 array_xor(block, &self.tweak_state.tweak);
599 }
600 }
601
602 /// An XTS-AES cipher configured with an initial tweak that can be advanced through the block
603 /// numbers for that tweak's data unit.
604 ///
605 /// Encryption or decryption is per-block only; ciphertext stealing is not implemented at this
606 /// level.
607 struct XtsDecrypterTweaked<'a, A: Aes> {
608 tweak_state: TweakState,
609 dec_cipher: &'a A::DecryptCipher,
610 }
611
612 impl<'a, A: Aes> XtsDecrypterTweaked<'a, A> {
advance_to_next_block_num(&mut self)613 fn advance_to_next_block_num(&mut self) {
614 self.tweak_state.advance_to_next_block()
615 }
616
617 /// Get the current tweak state -- useful if needed to reset to an earlier block num.
current_tweak(&self) -> TweakState618 fn current_tweak(&self) -> TweakState {
619 self.tweak_state.clone()
620 }
621
622 /// Set the tweak to a state captured via [`current_tweak`](XtsDecrypterTweaked::current_tweak).
set_tweak(&mut self, tweak_state: TweakState)623 fn set_tweak(&mut self, tweak_state: TweakState) {
624 self.tweak_state = tweak_state;
625 }
decrypt_block(&self, block: &mut AesBlock)626 fn decrypt_block(&self, block: &mut AesBlock) {
627 // CC = C ^ T
628 array_xor(block, &self.tweak_state.tweak);
629 // PP = decrypt CC
630 self.dec_cipher.decrypt(block);
631 // P = PP ^ T
632 array_xor(block, &self.tweak_state.tweak);
633 }
634 }
635
636 /// Calculate `base = base ^ rhs` for each byte.
637 #[allow(clippy::expect_used)]
array_xor(base: &mut AesBlock, rhs: &AesBlock)638 fn array_xor(base: &mut AesBlock, rhs: &AesBlock) {
639 // hopefully this gets done smartly by the compiler (intel pxor, arm veorq, or equivalent).
640 // This seems to happen in practice at opt level 3: https://gcc.godbolt.org/z/qvjE8joMv
641 for i in 0..BLOCK_SIZE {
642 *base.get_mut(i).expect("i is always a valid index for an AesBlock") ^=
643 rhs.get(i).expect("i is always a valid index for an AesBlock");
644 }
645 }
646
try_split_concat_key<const N: usize>(slice: &[u8]) -> Option<([u8; N], [u8; N])>647 fn try_split_concat_key<const N: usize>(slice: &[u8]) -> Option<([u8; N], [u8; N])> {
648 slice.get(0..N).and_then(|slice| slice.try_into().ok()).and_then(|k1: [u8; N]| {
649 slice.get(N..).and_then(|slice| slice.try_into().ok()).map(|k2: [u8; N]| (k1, k2))
650 })
651 }
652