一文读懂XTS模式
分组模式
这篇文章的灵感来源于我偶然翻到的一个某U盘有关磁盘加密的一个介绍(这一篇不是广告蛤), 然后发现这个模式我之前还真没遇到过,因此呢,就学习了一下,就出来了这一篇文章。
某某磁盘加密
具体是哪一个,去看参考资料吧,这里不具体写出来了, 这里有关AES算法的描述,这里就不过多说了,有兴趣的大佬们可以自行查找先关的资料或者看一下我之前写过的有关AES的介绍的相关文章。
算法背景
对于磁盘加密来说,这里和普通的消息加密,这里有一些不同,我们知道,磁盘对于数据的存储是有一定的格式的,比如会有不同扇区的概念,因此呢,我们在加密的过程当中,是不希望要有额外的地方存储类似于IV之类的东西的,并且,我们希望可以堆任意的分块进行快速的解密。
举一个例子,如果我们采用CBC模式,那么密文需要包含一个128bit的初始向量(IV),那么我们就必须要腾出来一块额外的空间来存储这个值。这里会造成额外的磁盘开销,并且明文和密文也不是对应的,注意一下,这里我们不考虑ECB模式,这个模式是不安全的,在前面的文章当中也说过了,或者看一下某U盘也讲了这个模式的缺点。
如果我们想要更改密钥,这里我们就需要重新执行密钥扩展算法,这里实际上对磁盘来说也是一个不小的开销。针对于以上所说的这些情况,在2002年,Moses Liskov,Ronald L.Rivest, David Wagner,首次提出了可调整的分组密码这个概念,跟传统的分组密码相比,除了密匙和明文这两个输入外,还引入另外一个输入tweak,即可调整值。引入可调整值之后,我们就可以改变tweak值,来改变加密之后的密文,同样的,这个算法那也不需要初始化向量(IV), 避免了明文密文在存储区域上不对应的关系。
算法描述
XTS-Mode
对于XTS模式来说,和之前我们讲过的ECB, CBC, CFB, OFB, CTR等这些有个比较大的区别,就是他有两个密钥,其中一个用于执行AES分组加密,另一个用于加密调整值(tweak), 这种加密调整借助有限域和异或运算,使得每次即时是相同的分组也不会得到相同的密文,确保了安全性。
如上图所示,对于每个分块来说,首先tweak先用密钥2通过AES加密,然后再通过有限域的运算,最终在和加密的明文进行异或。
因为这个没有链接的操作,因此呢,我们可以提前计算好一些值,并且这个是可以并行计算的,每个分块对应的密文也恰好是这个分块。
要注意一点,上面都是针对的正常分组的处理,如果是最后一个分组,并且长度不足分块大小,是要进行一个窃取操作的,具体操作如下图:
stealing
代码实现
依然采用rust来实现一下。
use aes::{BlockEncrypt, BlockDecrypt, BlockCipher, Aes128, NewBlockCipher}; use cipher::generic_array::GenericArray; use std::convert::TryInto; use byteorder::{ByteOrder, LittleEndian}; #[macro_use] extern crate hex_literal; fn xor(x: &mut [u8], y: &[u8]) { for (a, b) in x.iter_mut().zip(y) { *a ^= *b; } } /* * GF(128) 乘法 α = x^128 + x^7 + x^2 + x + 1 */ fn mul(tweak_source: [u8; 16]) -> [u8; 16] { let low_bytes = u64::from_le_bytes(tweak_source[0..8].try_into().unwrap()); let high_bytes = u64::from_le_bytes(tweak_source[8..16].try_into().unwrap()); let new_low_bytes = (low_bytes << 1) ^ if (high_bytes >> 63) != 0 { 0x87 } else { 0x00 }; let new_high_bytes = (low_bytes >> 63) | (high_bytes << 1); let mut tweak = [0; 16]; LittleEndian::write_u64(&mut tweak[0..8], new_low_bytes); LittleEndian::write_u64(&mut tweak[8..16], new_high_bytes); tweak } pub struct XTS<C: BlockEncrypt + BlockDecrypt + BlockCipher> { cipher_1: C, cipher_2: C, } impl<C: BlockEncrypt + BlockDecrypt + BlockCipher> XTS<C> { pub fn new(cipher_1: C, cipher_2: C) -> XTS<C> { XTS { cipher_1, cipher_2, } } pub fn encrypt(&self, message: &mut [u8], mut tweak: [u8; 16]) { let block_count = message.len() / 16; let need_stealing = message.len() % 16 != 0; self.cipher_2.encrypt_block(GenericArray::from_mut_slice(&mut tweak)); let full_block_count = if need_stealing { block_count - 1 } else { block_count }; for chunk in message[..(full_block_count * 16)].chunks_mut(16) { xor(chunk, &tweak); self.cipher_1.encrypt_block(GenericArray::from_mut_slice(chunk)); xor(chunk, &tweak); tweak = mul(tweak); } if need_stealing { let next_to_last_tweak = tweak; let last_tweak = mul(tweak); let mut block: [u8; 16] = message[16 * (block_count - 1)..16 * block_count] .try_into() .unwrap(); let remaining = message.len() % 16; xor(&mut block, &next_to_last_tweak); self.cipher_1.encrypt_block(GenericArray::from_mut_slice(&mut block)); xor(&mut block, &next_to_last_tweak); let mut last_block = [0u8; 16]; last_block[..remaining].copy_from_slice(&message[16 * block_count..]); last_block[remaining..].copy_from_slice(&block[remaining..]); xor(&mut last_block, &last_tweak); self.cipher_1 .encrypt_block(GenericArray::from_mut_slice(&mut last_block)); xor(&mut last_block, &last_tweak); message[16 * (block_count - 1)..16 * block_count].copy_from_slice(&last_block); message[16 * block_count..].copy_from_slice(&block[..remaining]); } } pub fn decrypt(&self, message: &mut [u8], mut tweak: [u8; 16]) { let block_count = message.len() / 16; let need_stealing = message.len() % 16 != 0; self.cipher_2 .encrypt_block(GenericArray::from_mut_slice(&mut tweak)); let full_block_count = if need_stealing { block_count - 1 } else { block_count }; for chunk in message[0..(full_block_count * 16)].chunks_mut(16) { xor(chunk, &tweak); self.cipher_1.decrypt_block(GenericArray::from_mut_slice(chunk)); xor(chunk, &tweak); tweak = mul(tweak); } if need_stealing { let next_to_last_tweak = tweak; let last_tweak = mul(tweak); let remaining = message.len() % 16; let mut block: [u8; 16] = message[16 * (block_count - 1)..16 * block_count] .try_into() .unwrap(); xor(&mut block, &last_tweak); self.cipher_1 .decrypt_block(GenericArray::from_mut_slice(&mut block)); xor(&mut block, &last_tweak); let mut last_block = [0u8; 16]; last_block[..remaining].copy_from_slice(&message[16 * block_count..]); last_block[remaining..].copy_from_slice(&block[remaining..]); xor(&mut last_block, &next_to_last_tweak); self.cipher_1 .decrypt_block(GenericArray::from_mut_slice(&mut last_block)); xor(&mut last_block, &next_to_last_tweak); message[16 * (block_count - 1)..16 * block_count].copy_from_slice(&last_block); message[16 * block_count..].copy_from_slice(&block[..remaining]); } } } fn make_xts_aes_128(key: &[u8]) -> XTS<Aes128> { let cipher_1 = Aes128::new(GenericArray::from_slice(&key[..16])); let cipher_2 = Aes128::new(GenericArray::from_slice(&key[16..])); XTS::<Aes128>::new(cipher_1, cipher_2) } pub fn get_tweak_default(sector_index: u128) -> [u8; 16] { sector_index.to_le_bytes() } #[cfg(test)] mod tests { use crate::{make_xts_aes_128, get_tweak_default, mul}; #[test] fn it_works() { let plaintext = b"ABCDEF12345678901"; let mut buffer = plaintext.to_owned(); let xts = make_xts_aes_128(&hex!("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f")); let tweak = get_tweak_default(0); xts.encrypt(&mut buffer, tweak); println!("buffer = {:?}", buffer); let _encrypted = buffer.clone(); xts.decrypt(&mut buffer, tweak); println!("buffer = {:?}", buffer); let mut tweak = [0; 16]; tweak[15] = 127; let tweak = mul(tweak); println!("tweak = {:?}", tweak); } }