【密码学】一文读懂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);
    }
}


相关文章
|
存储 弹性计算 算法
倚天产品介绍|倚天ECS加速国密算法性能
倚天ECS是阿里云基于平头哥自研数据中心芯片倚天710推出arm架构实例,采用armv9架构,支持SM3/SM4指令,可以加速国密算法性能。本文基于OpenSSL 3.2和Tongsuo 实测对比了倚天ECS g8y实例和Intel g7 实例国密性能。为用户选择ECS提供参考。
|
Web App开发 Rust 算法
【密码学】一文读懂ChaCha20
好久没写新的加密算法的原理了, 这次所选取的加密算法结构比较简单, 一起来看一下吧。
9605 1
【密码学】一文读懂ChaCha20
|
并行计算 算法 搜索推荐
简单学习一下AES算法:GCM、ECB、CFB、OFB等
简单学习一下AES算法:GCM、ECB、CFB、OFB等
2051 0
|
Rust 算法 安全
【密码学】一文读懂HMAC
本文将来聊一聊基于哈希函数的消息认证码,在此之前,先来科普一下什么是 「消息认证码」 (MAC), 先来看一个简单的栗子
2275 0
【密码学】一文读懂HMAC
|
Rust 算法 网络安全
【密码学】一文读懂CMAC
介于上一篇文章比较水,然后这个和上一篇也比较相似,CMAC是为了解决DAA当中安全性不足的问题而出现的,这个算法一共有三个密钥,K, K1, K2, 其中K1和K2可以由K导出,接下来就来一起看一下CMAC的具体过程吧,这一篇文章其实也不长。
4922 0
【密码学】一文读懂CMAC
|
算法 数据安全/隐私保护
【密码学】一文读懂Whirlpool
首先呢,祝大家今晚节日快乐,Whirlpool是由Vincent Rijmen(高级加密标准的联合创始人)和Paulo S.L.M.Barreto设计的,后者于2000年首次提出了它。
1470 0
【密码学】一文读懂Whirlpool
|
安全 Linux 数据安全/隐私保护
Intel TME和MKTME技术解析
# 市场需求 人们对透明全内存加密这个功能的需求主要来自对机密和敏感数据的保护。普通RAM里面储存的数据,在掉电之后,一般都以为是彻底消失了。但其实在一些复杂的离线攻击下,这些数据仍然是能被恢复出来并导致泄密;而持久性存储器(即外存,包括磁盘、SSD、eMMC等)的数据更加容易泄露。这些设备可能有硬件锁机制的保护,但是用户其实希望的是更细粒度的保护,比如per进程/容器/VM级的。 Int
5822 0
Intel TME和MKTME技术解析
|
算法 安全 Go
【密码学】一文读懂HKDF
我这又来水一篇文章,来聊一下HKDF(基于HMAC的密钥导出函数)。密钥派生函数是密钥管理的组成部分,他的目标是通过一些初始的数据派生出来密码学安全的随机密钥。
3701 1
【密码学】一文读懂HKDF
|
Rust 搜索推荐 算法
【密码学】一文读懂GCM(Golais计数器模式)
GCM(Galois计数器模式)同样的也是NIST提出来的,这个模式基于并行化设计,下面来一起看一下这个模式具体是如何工作的吧。
3232 0
【密码学】一文读懂GCM(Golais计数器模式)