【密码学】一文读懂XTS模式

简介: 这篇文章的灵感来源于我偶然翻到的一个某U盘有关磁盘加密的一个介绍(这一篇不是广告蛤), 然后发现这个模式我之前还真没遇到过,因此呢,就学习了一下,就出来了这一篇文章。

一文读懂XTS模式


)IZH(RJY3QW{HR%5@6`8SMR.jpg分组模式

这篇文章的灵感来源于我偶然翻到的一个某U盘有关磁盘加密的一个介绍(这一篇不是广告蛤), 然后发现这个模式我之前还真没遇到过,因此呢,就学习了一下,就出来了这一篇文章。

0RY7FVV{`U(S3E@O4{}%7PR.png某某磁盘加密

具体是哪一个,去看参考资料吧,这里不具体写出来了, 这里有关AES算法的描述,这里就不过多说了,有兴趣的大佬们可以自行查找先关的资料或者看一下我之前写过的有关AES的介绍的相关文章。


算法背景

对于磁盘加密来说,这里和普通的消息加密,这里有一些不同,我们知道,磁盘对于数据的存储是有一定的格式的,比如会有不同扇区的概念,因此呢,我们在加密的过程当中,是不希望要有额外的地方存储类似于IV之类的东西的,并且,我们希望可以堆任意的分块进行快速的解密。


举一个例子,如果我们采用CBC模式,那么密文需要包含一个128bit的初始向量(IV),那么我们就必须要腾出来一块额外的空间来存储这个值。这里会造成额外的磁盘开销,并且明文和密文也不是对应的,注意一下,这里我们不考虑ECB模式,这个模式是不安全的,在前面的文章当中也说过了,或者看一下某U盘也讲了这个模式的缺点。


如果我们想要更改密钥,这里我们就需要重新执行密钥扩展算法,这里实际上对磁盘来说也是一个不小的开销。针对于以上所说的这些情况,在2002年,Moses Liskov,Ronald L.Rivest, David Wagner,首次提出了可调整的分组密码这个概念,跟传统的分组密码相比,除了密匙和明文这两个输入外,还引入另外一个输入tweak,即可调整值。引入可调整值之后,我们就可以改变tweak值,来改变加密之后的密文,同样的,这个算法那也不需要初始化向量(IV), 避免了明文密文在存储区域上不对应的关系。


算法描述

2UJYKKXM7F(VLVJU7[5HV`E.pngXTS-Mode

对于XTS模式来说,和之前我们讲过的ECB, CBC, CFB, OFB, CTR等这些有个比较大的区别,就是他有两个密钥,其中一个用于执行AES分组加密,另一个用于加密调整值(tweak), 这种加密调整借助有限域和异或运算,使得每次即时是相同的分组也不会得到相同的密文,确保了安全性。

如上图所示,对于每个分块来说,首先tweak先用密钥2通过AES加密,然后再通过有限域的运算,最终在和加密的明文进行异或。

因为这个没有链接的操作,因此呢,我们可以提前计算好一些值,并且这个是可以并行计算的,每个分块对应的密文也恰好是这个分块。

要注意一点,上面都是针对的正常分组的处理,如果是最后一个分组,并且长度不足分块大小,是要进行一个窃取操作的,具体操作如下图:

U$Q)K)YRX[_T88L[NE~59IV.pngstealing


代码实现

依然采用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);
    }
}


相关文章
|
算法 数据安全/隐私保护
【密码学】一文读懂Whirlpool
首先呢,祝大家今晚节日快乐,Whirlpool是由Vincent Rijmen(高级加密标准的联合创始人)和Paulo S.L.M.Barreto设计的,后者于2000年首次提出了它。
【密码学】一文读懂Whirlpool
|
Rust 算法 安全
【密码学】一文读懂MurMurHash2
上次我们聊过了一代的MurMurHash算法,是的,我又来水文章了,今天呢,接着来聊一下二代的MurMurHash算法,二代算法的整体结构实际上和一代算法差不太多,只是对于每一轮数据的处理过程当中的运算有一些差异,算法的来源依然是来自于Google官网给提供的源码,对着源码看的结构,对于这个算法呢,有两个版本,一个是32位的,一个是64位的,对于32位的算法和64位的算法,区别在于两个初始的魔数不同,整体运算过程还是十分相似的。
1680 0
【密码学】一文读懂MurMurHash2
|
Rust 算法 网络安全
【密码学】一文读懂CMAC
介于上一篇文章比较水,然后这个和上一篇也比较相似,CMAC是为了解决DAA当中安全性不足的问题而出现的,这个算法一共有三个密钥,K, K1, K2, 其中K1和K2可以由K导出,接下来就来一起看一下CMAC的具体过程吧,这一篇文章其实也不长。
2786 0
【密码学】一文读懂CMAC
|
Web App开发 Rust 算法
【密码学】一文读懂ChaCha20
好久没写新的加密算法的原理了, 这次所选取的加密算法结构比较简单, 一起来看一下吧。
5594 0
【密码学】一文读懂ChaCha20
|
Rust 算法 Go
【密码学】一文读懂MurMurHash3
本文应该是MurMurHash算法介绍的最后一篇,来一起看一下最新的MurMurHash算法的具体过程,对于最新的算法来说,整个流程和之前的其实也比较相似,这里从维基百科当中找到了伪代码,也就不贴出来Google官方给出的推荐代码了,先来看一下维基百科给出的伪代码,这里只有32位的伪代码。
1195 0
【密码学】一文读懂MurMurHash3
|
Rust 算法 数据安全/隐私保护
【密码学】一文读懂XTEA加密
本篇文章,我们来看一下上一次讲过的TEA加密算法的一个升级版XTEA, 相比于TEA, XTEA的安全性显然是更高的,其中的过程要比TEA稍微复杂一点点。
1020 0
【密码学】一文读懂XTEA加密
【密码学】一文读懂SHAMIR门限方案
【密码学】一文读懂SHAMIR门限方案
|
存储 算法 数据安全/隐私保护
【密码学】一文读懂白盒AES(Chow方案)(一)
本文主要参考了文献^[1], 代码参考了^[2], 这里感谢文献作者和代码作者,如果有能力的大佬,可以自行查看原文献,个人水平有限,有哪里写的不对的地方,也欢迎读者指正。
2729 0
【密码学】一文读懂白盒AES(Chow方案)(一)
|
算法 安全 Go
【密码学】一文读懂HKDF
我这又来水一篇文章,来聊一下HKDF(基于HMAC的密钥导出函数)。密钥派生函数是密钥管理的组成部分,他的目标是通过一些初始的数据派生出来密码学安全的随机密钥。
2758 0
【密码学】一文读懂HKDF
|
Rust 算法 数据安全/隐私保护
【密码学】一文读懂白盒AES(Chow方案)(二)
本文主要参考了文献^[1], 代码参考了^[2], 这里感谢文献作者和代码作者,如果有能力的大佬,可以自行查看原文献,个人水平有限,有哪里写的不对的地方,也欢迎读者指正。
1250 0
【密码学】一文读懂白盒AES(Chow方案)(二)