7 releases (4 breaking)

0.5.1 Oct 31, 2022
0.5.0 Feb 25, 2022
0.4.1 Sep 23, 2021
0.4.0 May 1, 2021
0.1.0 Apr 30, 2020

#1044 in Cryptography

Download history 13206/week @ 2024-08-15 13584/week @ 2024-08-22 13910/week @ 2024-08-29 6346/week @ 2024-09-05 8894/week @ 2024-09-12 5816/week @ 2024-09-19 7508/week @ 2024-09-26 5659/week @ 2024-10-03 7188/week @ 2024-10-10 6577/week @ 2024-10-17 4744/week @ 2024-10-24 3185/week @ 2024-10-31 6906/week @ 2024-11-07 6471/week @ 2024-11-14 8637/week @ 2024-11-21 5511/week @ 2024-11-28

27,918 downloads per month
Used in 3 crates (2 directly)

MIT license

20KB
183 lines

xts-mode

XTS block mode implementation in Rust.

Currently this implementation supports only ciphers with 128-bit (16-byte) block size (distinct from key size). Note that AES-256 uses 128-bit blocks, so it works with this crate. If you require other cipher block sizes, please open an issue.

Examples:

Encrypting and decrypting multiple sectors at a time:

use aes::{Aes128, cipher::KeyInit, cipher::generic_array::GenericArray};
use xts_mode::{Xts128, get_tweak_default};

// Load the encryption key
let key = [1; 32];
let plaintext = [5; 0x400];

// Load the data to be encrypted
let mut buffer = plaintext.to_owned();

let cipher_1 = Aes128::new(GenericArray::from_slice(&key[..16]));
let cipher_2 = Aes128::new(GenericArray::from_slice(&key[16..]));

let xts = Xts128::<Aes128>::new(cipher_1, cipher_2);

let sector_size = 0x200;
let first_sector_index = 0;

// Encrypt data in the buffer
xts.encrypt_area(&mut buffer, sector_size, first_sector_index, get_tweak_default);

// Decrypt data in the buffer
xts.decrypt_area(&mut buffer, sector_size, first_sector_index, get_tweak_default);

assert_eq!(&buffer[..], &plaintext[..]);

AES-256 works too:

use aes::{Aes256, cipher::KeyInit, cipher::generic_array::GenericArray};
use xts_mode::{Xts128, get_tweak_default};

// Load the encryption key
let key = [1; 64];
let plaintext = [5; 0x400];

// Load the data to be encrypted
let mut buffer = plaintext.to_owned();

let cipher_1 = Aes256::new(GenericArray::from_slice(&key[..32]));
let cipher_2 = Aes256::new(GenericArray::from_slice(&key[32..]));

let xts = Xts128::<Aes256>::new(cipher_1, cipher_2);

let sector_size = 0x200;
let first_sector_index = 0;

xts.encrypt_area(&mut buffer, sector_size, first_sector_index, get_tweak_default);

xts.decrypt_area(&mut buffer, sector_size, first_sector_index, get_tweak_default);

assert_eq!(&buffer[..], &plaintext[..]);

Encrypting and decrypting a single sector:

use aes::{Aes128, cipher::KeyInit, cipher::generic_array::GenericArray};
use xts_mode::{Xts128, get_tweak_default};

// Load the encryption key
let key = [1; 32];
let plaintext = [5; 0x200];

// Load the data to be encrypted
let mut buffer = plaintext.to_owned();

let cipher_1 = Aes128::new(GenericArray::from_slice(&key[..16]));
let cipher_2 = Aes128::new(GenericArray::from_slice(&key[16..]));

let xts = Xts128::<Aes128>::new(cipher_1, cipher_2);

let tweak = get_tweak_default(0); // 0 is the sector index

// Encrypt data in the buffer
xts.encrypt_sector(&mut buffer, tweak);

// Decrypt data in the buffer
xts.decrypt_sector(&mut buffer, tweak);

assert_eq!(&buffer[..], &plaintext[..]);

Decrypting a NCA (nintendo content archive) header:

use aes::{Aes128, cipher::KeyInit, cipher::generic_array::GenericArray};
use xts_mode::{Xts128, get_tweak_default};

pub fn get_nintendo_tweak(sector_index: u128) -> [u8; 0x10] {
    sector_index.to_be_bytes()
}

// Load the header key
let header_key = &[0; 0x20];

// Read into buffer header to be decrypted
let mut buffer = vec![0; 0xC00];

let cipher_1 = Aes128::new(GenericArray::from_slice(&header_key[..0x10]));
let cipher_2 = Aes128::new(GenericArray::from_slice(&header_key[0x10..]));

let mut xts = Xts128::new(cipher_1, cipher_2);

// Decrypt the first 0x400 bytes of the header in 0x200 sections
xts.decrypt_area(&mut buffer[0..0x400], 0x200, 0, get_nintendo_tweak);

let magic = &buffer[0x200..0x204];
assert_eq!(magic, b"NCA3"); // In older NCA versions the section index used in header encryption was different

// Decrypt the rest of the header
xts.decrypt_area(&mut buffer[0x400..0xC00], 0x200, 2, get_nintendo_tweak);

Dependencies

~0.4–1MB
~21K SLoC