xts_mode/lib.rs
1#![no_std]
2
3/*!
4[XTS block mode] implementation in Rust.
5
6Currently this implementation supports only ciphers with 128-bit (16-byte)
7block size (distinct from key size). Note that AES-256 uses 128-bit blocks,
8so it is supported by this crate. If you require other block sizes, feel free
9to open an issue, but notice that only XTS-AES has been standardized and the
10AES standard supports only 128-bit blocks.
11
12[XTS block mode]: https://en.wikipedia.org/wiki/Disk_encryption_theory#XEX-based_tweaked-codebook_mode_with_ciphertext_stealing_(XTS)
13
14# Security warnings
15
16This crate has never been independently audited, use at your own risk.
17
18All of the usual caveats of XTS apply to this crate, and its only intended
19usage is disk (sector-based storage) encryption. Some of these caveats:
20
21- It has no authentication tags, so an adversary with write access may be able
22 to randomize blocks.
23- An adversary with read-write access may be able to reset blocks to a previous
24 value they have seen.
25- It's deterministic, so passive observers may be able to infer when each block
26 has changed.
27
28I recommend reading more about XTS weaknesses before using it.
29
30# Examples:
31
32Encrypting and decrypting multiple sectors at a time:
33```
34use aes::Aes128;
35use aes::cipher::{Array, KeyInit, consts::{U16, U32}};
36use xts_mode::{Xts128, get_tweak_default};
37
38// Load encryption key
39let key: Array<u8, U32> = Array([1; 32]);
40let plaintext = [5; 0x400];
41
42// Load data to be encrypted
43let mut buffer = plaintext.to_owned();
44
45let (key_1, key_2) = key.split::<U16>();
46let cipher_1 = Aes128::new(&key_1);
47let cipher_2 = Aes128::new(&key_2);
48
49let xts = Xts128::<Aes128>::new(cipher_1, cipher_2);
50
51let sector_size = 0x200;
52let first_sector_index = 0;
53
54// Encrypt data in the buffer
55xts.encrypt_area(&mut buffer, sector_size, first_sector_index, get_tweak_default);
56
57// Decrypt data in the buffer
58xts.decrypt_area(&mut buffer, sector_size, first_sector_index, get_tweak_default);
59
60assert_eq!(&buffer[..], &plaintext[..]);
61```
62
63AES-256 works too:
64```
65use aes::Aes256;
66use aes::cipher::{Array, KeyInit, consts::{U32, U64}};
67use xts_mode::{Xts128, get_tweak_default};
68
69// Load the encryption key
70let key: Array<u8, U64> = Array([1; 64]);
71let plaintext = [5; 0x400];
72
73// Load the data to be encrypted
74let mut buffer = plaintext.to_owned();
75
76let (key_1, key_2) = key.split::<U32>();
77let cipher_1 = Aes256::new(&key_1);
78let cipher_2 = Aes256::new(&key_2);
79
80let xts = Xts128::<Aes256>::new(cipher_1, cipher_2);
81
82let sector_size = 0x200;
83let first_sector_index = 0;
84
85xts.encrypt_area(&mut buffer, sector_size, first_sector_index, get_tweak_default);
86
87xts.decrypt_area(&mut buffer, sector_size, first_sector_index, get_tweak_default);
88
89assert_eq!(&buffer[..], &plaintext[..]);
90```
91
92Encrypting and decrypting a single sector:
93```
94use aes::Aes128;
95use aes::cipher::{Array, KeyInit, consts::{U16, U32}};
96use xts_mode::{Xts128, get_tweak_default};
97
98// Load encryption key
99let key: Array<u8, U32> = Array([1; 32]);
100
101let plaintext = [5; 0x200];
102
103// Load data to be encrypted
104let mut buffer = plaintext.to_owned();
105
106let (key_1, key_2) = key.split::<U16>();
107let cipher_1 = Aes128::new(&key_1);
108let cipher_2 = Aes128::new(&key_2);
109
110let xts = Xts128::<Aes128>::new(cipher_1, cipher_2);
111
112let tweak = get_tweak_default(0); // 0 is the sector index
113
114// Encrypt data in the buffer
115xts.encrypt_sector(&mut buffer, tweak);
116
117// Decrypt data in the buffer
118xts.decrypt_sector(&mut buffer, tweak);
119
120assert_eq!(&buffer[..], &plaintext[..]);
121```
122
123Decrypting a [NCA](https://switchbrew.org/wiki/NCA_Format) (nintendo content archive) header:
124```
125use aes::Aes128;
126use aes::cipher::{Array, KeyInit, consts::{U16, U32}};
127use xts_mode::Xts128;
128
129pub fn get_nintendo_tweak(sector_index: u128) -> Array<u8, U16> {
130 Array(sector_index.to_be_bytes())
131}
132
133// Load header key
134let header_key: Array<u8, U32> = Array([0; 0x20]);
135
136// Read header to be decrypted into buffer
137let mut buffer = vec![0; 0xC00];
138
139let (header_key_1, header_key_2) = header_key.split::<U16>();
140let cipher_1 = Aes128::new(&header_key_1);
141let cipher_2 = Aes128::new(&header_key_2);
142
143let xts = Xts128::new(cipher_1, cipher_2);
144
145// Decrypt the first 0x400 bytes of the header in 0x200 sections
146xts.decrypt_area(&mut buffer[0..0x400], 0x200, 0, get_nintendo_tweak);
147
148let magic = &buffer[0x200..0x204];
149# if false { // Removed from tests as we're not decrypting an actual NCA
150assert_eq!(magic, b"NCA3"); // In older NCA versions the section index used in header encryption was different
151# }
152
153// Decrypt the rest of the header
154xts.decrypt_area(&mut buffer[0x400..0xC00], 0x200, 2, get_nintendo_tweak);
155```
156*/
157
158use core::convert::TryFrom;
159
160pub use cipher::array::{self, Array};
161use cipher::consts::U16;
162use cipher::{BlockCipherDecrypt, BlockCipherEncrypt, BlockSizeUser};
163
164/// Xts128 block cipher. Does not implement implement BlockMode due to XTS differences detailed
165/// [here](https://github.com/RustCrypto/block-ciphers/issues/48#issuecomment-574440662).
166pub struct Xts128<C> {
167 /// This cipher is actually used to encrypt the blocks.
168 cipher_1: C,
169 /// This cipher is used only to compute the tweak at each sector start.
170 cipher_2: C,
171}
172
173impl<C> Xts128<C>
174where
175 C: BlockSizeUser<BlockSize = U16>,
176{
177 /// Creates a new Xts128 using two cipher instances: the first one's used to encrypt the blocks
178 /// and the second one to compute the tweak at the start of each sector.
179 ///
180 /// Usually both ciphers are the same algorithm, the key is stored as double sized (256 bits for Aes128).
181 /// When using, the key is split in half, the first half used for cipher_1 and the other for cipher_2.
182 ///
183 /// If you require support for different cipher types, or block sizes different than 16 bytes,
184 /// open an issue.
185 pub fn new(cipher_1: C, cipher_2: C) -> Xts128<C> {
186 Xts128 { cipher_1, cipher_2 }
187 }
188}
189
190impl<C> Xts128<C>
191where
192 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt,
193{
194 /// Encrypts a single sector in place using the given tweak.
195 ///
196 /// # Panics
197 /// - If there's less than a single block in the sector.
198 pub fn encrypt_sector(&self, sector: &mut [u8], mut tweak: Array<u8, U16>) {
199 assert!(
200 sector.len() >= 16,
201 "AES-XTS needs at least one complete block in each sector"
202 );
203
204 // Compute tweak
205 self.cipher_2.encrypt_block(&mut tweak);
206
207 let block_count = sector.len() / 16;
208
209 for i in (0..sector.len()).step_by(16).take(block_count - 1) {
210 let block = Array::slice_as_mut_array(&mut sector[i..i + 16]).unwrap();
211
212 xor(block, &tweak);
213 self.cipher_1.encrypt_block(block);
214 xor(block, &tweak);
215
216 tweak = galois_field_128_mul_le(tweak);
217 }
218
219 {
220 let block =
221 Array::slice_as_mut_array(&mut sector[16 * (block_count - 1)..16 * block_count])
222 .unwrap();
223
224 xor(block, &tweak);
225 self.cipher_1.encrypt_block(block);
226 xor(block, &tweak);
227 }
228
229 let remainder = sector.len() % 16;
230 if remainder != 0 {
231 let last_tweak = galois_field_128_mul_le(tweak);
232
233 let (full_block, partial_block) = sector[16 * (block_count - 1)..].split_at_mut(16);
234 let (last_ciphertext, cipher_plaintext) = full_block.split_at(remainder);
235
236 let mut last_block = Array([0u8; 16]);
237 last_block[..remainder].copy_from_slice(partial_block);
238 last_block[remainder..].copy_from_slice(cipher_plaintext);
239
240 xor(&mut last_block, &last_tweak);
241 self.cipher_1.encrypt_block(&mut last_block);
242 xor(&mut last_block, &last_tweak);
243
244 partial_block.copy_from_slice(last_ciphertext);
245 full_block.copy_from_slice(&last_block);
246 }
247 }
248
249 /// Encrypts a whole area in place, usually consisting of multiple sectors.
250 ///
251 /// The tweak is computed at the start of every sector using get_tweak_fn(sector_index).
252 /// `get_tweak_fn` is usually `get_tweak_default`.
253 ///
254 /// # Panics
255 /// - If there's less than a single block in the last sector.
256 pub fn encrypt_area(
257 &self,
258 area: &mut [u8],
259 sector_size: usize,
260 first_sector_index: u128,
261 get_tweak_fn: impl Fn(u128) -> Array<u8, U16>,
262 ) {
263 let area_len = area.len();
264 let mut chunks = area.chunks_exact_mut(sector_size);
265 for (i, chunk) in (&mut chunks).enumerate() {
266 let tweak = get_tweak_fn(
267 u128::try_from(i).expect("usize cannot be bigger than u128") + first_sector_index,
268 );
269 self.encrypt_sector(chunk, tweak);
270 }
271 let remainder = chunks.into_remainder();
272
273 if !remainder.is_empty() {
274 let i = area_len / sector_size;
275 let tweak = get_tweak_fn(
276 u128::try_from(i).expect("usize cannot be bigger than u128") + first_sector_index,
277 );
278 self.encrypt_sector(remainder, tweak);
279 }
280 }
281}
282
283impl<C> Xts128<C>
284where
285 C: BlockSizeUser<BlockSize = U16> + BlockCipherEncrypt + BlockCipherDecrypt,
286{
287 /// Decrypts a single sector in place using the given tweak.
288 ///
289 /// # Panics
290 /// - If there's less than a single block in the sector.
291 pub fn decrypt_sector(&self, sector: &mut [u8], mut tweak: Array<u8, U16>) {
292 assert!(
293 sector.len() >= 16,
294 "AES-XTS needs at least one complete block in each sector"
295 );
296
297 // Compute tweak
298 self.cipher_2.encrypt_block(&mut tweak);
299
300 let block_count = sector.len() / 16;
301
302 for i in (0..sector.len()).step_by(16).take(block_count - 1) {
303 let block = Array::slice_as_mut_array(&mut sector[i..i + 16]).unwrap();
304
305 xor(block, &tweak);
306 self.cipher_1.decrypt_block(block);
307 xor(block, &tweak);
308
309 tweak = galois_field_128_mul_le(tweak);
310 }
311
312 let remainder = sector.len() % 16;
313 if remainder != 0 {
314 let next_to_last_tweak = tweak;
315 let last_tweak = galois_field_128_mul_le(tweak);
316
317 let (full_block, partial_block) = sector[16 * (block_count - 1)..].split_at_mut(16);
318 let full_block = Array::slice_as_mut_array(full_block).unwrap();
319
320 xor(full_block, &last_tweak);
321 self.cipher_1.decrypt_block(full_block);
322 xor(full_block, &last_tweak);
323
324 let (last_plaintext, cipher_plaintext) = full_block.split_at(remainder);
325
326 let mut last_block = Array([0u8; 16]);
327 last_block[..remainder].copy_from_slice(partial_block);
328 last_block[remainder..].copy_from_slice(cipher_plaintext);
329
330 xor(&mut last_block, &next_to_last_tweak);
331 self.cipher_1.decrypt_block(&mut last_block);
332 xor(&mut last_block, &next_to_last_tweak);
333
334 partial_block.copy_from_slice(last_plaintext);
335 full_block.copy_from_slice(&last_block);
336 } else {
337 let block = Array::slice_as_mut_array(&mut sector[16 * (block_count - 1)..]).unwrap();
338
339 xor(block, &tweak);
340 self.cipher_1.decrypt_block(block);
341 xor(block, &tweak);
342 }
343 }
344
345 /// Decrypts a whole area in place, usually consisting of multiple sectors.
346 ///
347 /// The tweak is computed at the start of every sector using get_tweak_fn(sector_index).
348 /// `get_tweak_fn` is usually `get_tweak_default`.
349 ///
350 /// # Panics
351 /// - If there's less than a single block in the last sector.
352 pub fn decrypt_area(
353 &self,
354 area: &mut [u8],
355 sector_size: usize,
356 first_sector_index: u128,
357 get_tweak_fn: impl Fn(u128) -> Array<u8, U16>,
358 ) {
359 let area_len = area.len();
360 let mut chunks = area.chunks_exact_mut(sector_size);
361 for (i, chunk) in (&mut chunks).enumerate() {
362 let tweak = get_tweak_fn(
363 u128::try_from(i).expect("usize cannot be bigger than u128") + first_sector_index,
364 );
365 self.decrypt_sector(chunk, tweak);
366 }
367 let remainder = chunks.into_remainder();
368
369 if !remainder.is_empty() {
370 let i = area_len / sector_size;
371 let tweak = get_tweak_fn(
372 u128::try_from(i).expect("usize cannot be bigger than u128") + first_sector_index,
373 );
374 self.decrypt_sector(remainder, tweak);
375 }
376 }
377}
378
379/// This is the default way to get the tweak, which just consists of separating the sector_index
380/// in an array of 16 bytes in little endian byte order. May be called to get the tweak for every
381/// sector or passed directly to `(en/de)crypt_area`, which will basically do that.
382pub fn get_tweak_default(sector_index: u128) -> Array<u8, U16> {
383 Array(sector_index.to_le_bytes())
384}
385
386#[inline(always)]
387fn xor(buf: &mut [u8], key: &[u8]) {
388 debug_assert_eq!(buf.len(), key.len());
389 for (a, b) in buf.iter_mut().zip(key) {
390 *a ^= *b;
391 }
392}
393
394fn galois_field_128_mul_le(tweak_source: Array<u8, U16>) -> Array<u8, U16> {
395 let tweak_source = u128::from_le_bytes(tweak_source.0);
396 // Get the special value 0x87 if the high bit is set
397 let special = ((tweak_source as i128 >> 127) & 0x87) as u128;
398 let tweak = tweak_source << 1 ^ special;
399 Array(tweak.to_le_bytes())
400}