存储加密模式的设计哲学
1 简介
存储加密模式的选择触及了存储加密的核心技术选型问题。XTS和GCM代表了两种不同的设计哲学。本文试图深入分析。

存储加密选XTS:因为它为随机访问、扇区独立性、零开销优化
数据传输选GCM:因为它提供完整性和认证,适合流式数据
2 XTS模式:为存储加密而设计
XTS(XEX-based tweaked-codebook mode with ciphertext stealing)原理
核心设计目标:磁盘/存储设备加密
XTS简化工作流程
void xts_encrypt_sector(sector_data, sector_number) {
// 关键:每个扇区独立加密,扇区内可随机访问
tweak = generate_tweak(sector_number); // 基于扇区号
for (int i = 0; i < blocks_in_sector; i++) {
// 每个块的加密都基于tweak
encrypted_block[i] = encrypt(
plain_block[i] ⊕ f(tweak, i), // 与位置相关
key
) ⊕ f(tweak, i);
}
}
- XTS的存储专用特性
1. 随机访问能力(决定性优势)
模拟磁盘读取场景
class DiskStorage:
def read_sector(self, sector_num, offset, length):
# 传统加密模式的问题
if using_cbc_or_gcm:
# 要解密offset=4096的数据,必须从sector=0开始解密
# 因为链式依赖!
data = b""
for i in range(0, sector_num + 1):
encrypted = self.raw_read_sector(i)
data += decrypt(encrypted) # 必须顺序解密
return data[offset:offset+length] # 性能灾难!
# XTS解决方案
elif using_xts:
# 直接解密目标扇区,无需前序扇区
encrypted = self.raw_read_sector(sector_num)
return xts_decrypt_sector(encrypted, sector_num)[offset:offset+length]
# O(1)时间复杂度!
实际性能对比:
场景:读取1TB SSD中位于中间位置的文件(500GB处)
-
GCM/CTR/CBC:必须从0解密到500GB才能访问
时间:~20分钟(假设解密速度500MB/s)
内存:需要缓存或重新解密 -
XTS:直接解密目标扇区
时间:~10毫秒
内存:只需一个扇区缓存(通常4KB)
2. 定位数据(Tweak机制)
XTS的tweak生成
uint8_t* xts_generate_tweak(uint64_t sector_number, int block_index) {
// tweak = 扇区号 + 块在扇区内的位置
uint8_t tweak[16] = {0};
// 扇区号(通常64位)
memcpy(tweak, §or_number, 8);
// 块索引(通常64位中的剩余部分)
uint64_t position = block_index;
memcpy(tweak + 8, &position, 8);
// 关键:相同的明文块,在不同的(sector, position)会加密成不同密文
return tweak;
}
示例:数据库记录的物理存储
– 假设用户表存储在扇区1000-1100
– 每个扇区512字节,存储多个用户记录
用户A记录:{id: 1001, name: “Alice”, balance: 5000}
用户B记录:{id: 1002, name: “Bob”, balance: 5000} – 相同余额!
– XTS加密后:
– 用户A的"5000"在扇区1002,块位置3
– 用户B的"5000"在扇区1005,块位置1
– 即使数值相同,tweak不同 → 密文完全不同!
– 解决了ECB的模式泄露问题
3. 无需完整性保护的设计哲学
存储加密的特殊需求:
磁盘/存储的故障模型:
- 位翻转(罕见)
- 扇区损坏(坏块)
- 数据老化(SSD读干扰)
但不像网络传输:
- 没有主动的中间人攻击者
- 篡改需要物理访问或内核漏洞
- 完整性由文件系统/RAID/ECC保障
因此XTS假设:完整性由上层保证
XTS在国密中的应用:SM4-XTS
国密SM4-XTS实现要点
int sm4_xts_encrypt_sector(
const uint8_t *plaintext, // 扇区明文
uint8_t *ciphertext, // 扇区密文
size_t sector_size, // 扇区大小(512B/4KB)
uint64_t sector_number, // 扇区号
const uint8_t key1[16], // 数据加密密钥
const uint8_t key2[16] // tweak加密密钥
) {
// 1. 生成tweak
uint8_t tweak[16];
sm4_xts_generate_tweak(sector_number, tweak);
// 2. 用key2加密tweak
uint8_t encrypted_tweak[16];
sm4_encrypt(key2, tweak, encrypted_tweak);
// 3. 分块加密扇区数据
for (int i = 0; i < sector_size / 16; i++) {
// 每个块独立加密,基于encrypted_tweak和块位置
uint8_t block_tweak[16];
sm4_xts_update_tweak(encrypted_tweak, i, block_tweak);
// XEX模式:P ⊕ T → 加密 → ⊕ T
xor_block(plaintext + i*16, block_tweak, temp);
sm4_encrypt(key1, temp, temp2);
xor_block(temp2, block_tweak, ciphertext + i*16);
}
return 0;
}
3 为什么存储不推荐GCM
GCM在存储场景的五大问题
- 问题1:链式依赖破坏随机访问
GCM使用递增计数器
要解密计数器N,需要知道计数器0…N-1?
实际上CTR模式可以独立计算,但是:
** GCM的CTR模式导致依赖关系
class GCMStorage:
def read_random_position(self, offset, length):
# 问题:认证标签是全局的!
tag = calculate_gmac(entire_ciphertext) # 需要全部数据
# 要验证部分数据的完整性:
# 1. 要么预先计算整个文件的tag(不现实)
# 2. 要么分块单独认证(丧失GCM优势)
# 对比XTS:每个扇区独立,无链式依赖
- 问题2:认证标签的存储开销
计算存储成本:
假设:1TB数据库,4KB页大小
XTS方案:
总数据:1TB
开销:0(无额外认证数据)
实际存储:1TB
GCM方案(每页单独认证):
数据:1TB
每页16字节tag:1TB / 4KB × 16B = 4GB
总存储:1TB + 4GB = 1.004TB
开销:0.4%
GCM方案(整个文件一个tag):
数据:1TB
tag:16字节
总存储:1TB + 16B ≈ 1TB
但:无法随机访问验证!
更严重的是数据库场景:
– 考虑B+树索引的更新
UPDATE users SET balance = 5000 WHERE id = 1001;
– XTS:只需重写对应扇区
– 开销:4KB写入
– GCM(每页单独tag):
– 1. 读取旧页(4KB + 16B tag)
– 2. 验证tag
– 3. 修改数据
– 4. 生成新tag
– 5. 写入新页(4KB + 16B)
– 开销:8KB+32B读写,计算两次GCM
– GCM(全局tag):
– 修改一个记录 → 需要重新计算整个1TB文件的tag!
– 完全不可行
- 问题3:Nonce管理灾难
GCM的Nonce必须唯一,但在存储中…
class GCMDiskEncryption:
def write_sector(self, sector_num, data):
# 每次写入都需要新Nonce
nonce = generate_nonce()
# 问题1:存储Nonce在哪?
# 选项A:与数据一起存储 → 每扇区额外12字节
# 选项B:从扇区号派生 → 但重写扇区需要新Nonce!
# 问题2:扇区重写(写放大)
for i in range(1000):
# SSD的同一逻辑扇区可能写到不同物理位置
self.write(sector_num, data) # 每次需要新Nonce
# 问题3:垃圾回收后,物理移动数据需要保持Nonce?
# XTS解决方案:tweak从扇区号派生,天然唯一
- 问题4:性能问题(特别是小写操作)
基准测试场景:
测试:4KB随机写操作
void benchmark_random_writes() {
// XTS性能
double xts_time = measure_time([]{
xts_encrypt_sector(data, sector_num); // 一次SM4调用/tweak
});
// GCM性能
double gcm_time = measure_time([]{
gcm_encrypt(data, nonce); // 需要GHASH + CTR
// GHASH:有限域乘法,比XTS的简单变换慢
});
// 典型结果(SM4软件实现):
// XTS: 2.1µs 每扇区
// GCM: 3.8µs 每扇区 (慢80%)
}
数据库OLTP场景影响:
TPC-C基准测试(订单处理系统):
- 90%操作是随机4-8KB读写
- 要求亚毫秒级延迟
使用XTS:平均延迟 0.8ms
使用GCM:平均延迟 1.4ms (增加75%)
TPS下降:~30%
问题5:与现有存储栈的兼容性
Linux磁盘加密栈(dm-crypt):
-
XTS是Linux标准
cryptsetup luksFormat --cipher aes-xts-plain64 /dev/sda1
尝试使用GCM:
cryptsetup luksFormat --cipher aes-gcm /dev/sda1
错误:不支持!
原因:
# 1. 需要存储/管理Nonce
# 2. 随机访问性能差
# 3. 与TRIM/discard命令不兼容
文件系统层的不匹配:
文件系统期望的行为
struct storage_driver {
int (*read)(sector_t sector, void *buffer);
int (*write)(sector_t sector, const void *buffer);
// 隐含假设:扇区操作是独立的、幂等的
};
GCM违反这些假设:
- write不是幂等(需要新Nonce)
- 独立read需要全局验证
问题6:数据恢复的复杂性
灾难恢复场景:
硬盘部分损坏,需要恢复数据:
XTS方案:
- 损坏扇区:仅丢失该扇区数据
- 健康扇区:可直接解密
- 恢复工具:可逐个扇区尝试恢复
GCM方案(全局tag):
- 任何损坏 → tag验证失败
- 整个卷无法解密
- 恢复几乎不可能
GCM方案(每扇区tag):
- 损坏扇区:tag损坏无法验证
- 即使数据完好,也无法解密
- 恢复需要绕过认证(破坏安全目标)
4 实际应用场景分析
- 场景1:全盘加密(FDE)
Linux LUKS配置对比
XTS方案(生产标准):
cryptsetup luksFormat \
--cipher sm4-xts-plain64 \
--key-size 512 \ # 两个256位密钥
/dev/nvme0n1p1
为什么不用GCM?原因:
- /boot分区需要随机访问(GRUB无法处理GCM)
- 休眠到磁盘:需要直接写入内存镜像
- SSD TRIM:XTS允许标记扇区为空
- 场景2:数据库透明加密(TDE)
– MySQL表空间加密
– XTS方案:
INSTALL PLUGIN keyring_file SONAME 'keyring_file.so';
CREATE TABLESPACE encrypted_ts
ADD DATAFILE 'encrypted.ibd'
ENCRYPTION='Y'
ENCRYPTION_KEYRING='keyring_file'
– 内部使用类似XTS的模式
– 每页(16KB)独立加密
– 尝试GCM的问题:
– 1. InnoDB的doublewrite buffer:需要独立页加密
– 2. 缓冲池管理:页可任意换入换出
– 3. 物理备份:需要逐页验证或全局tag
- 场景3:云存储服务端加密
对象存储加密设计
class ObjectStorageEncryption:
def __init__(self):
# AWS S3的实际选择:
# - 小对象(<1MB):AES-GCM(一次处理)
# - 大对象(>1MB):AES-CTR + HMAC(流式)
# - 但底层EBS卷:AES-XTS!
# 为什么分层?
self.small_obj_cipher = AES_GCM() # 对象级,完整
self.large_obj_cipher = AES_CTR_HMAC() # 流式,可分片
self.block_storage = AES_XTS() # 卷级,随机访问
- 场景4:固态硬盘(SSD)主控加密
SSD硬件加密引擎
struct ssd_encryption_engine {
// 必须使用XTS的原因:
// 1. 逻辑到物理地址映射(FTL)
uint32_t lba_to_ppa(uint32_t lba); // 动态重映射
// 2. 写放大优化
void garbage_collection() {
// 移动数据时,XTS:tweak基于LBA,不变
// GCM:需要新Nonce,但数据未改!
}
// 3. TRIM/UNMAP支持
void process_trim(uint32_t lba) {
// XTS:直接标记扇区可加密擦除
// GCM:需要保持Nonce一致性?复杂!
}
};
国密标准中的定位
GB/T 17964-2021 中的角色
存储加密标准体系:
- 基础算法:SM4
- 存储模式:SM4-XTS(GB/T XXXXX,待发布)
- 完整性补充:可搭配SM3-HMAC(上层)
- 密钥管理:配合SM9/密码机
定位:XTS是存储加密的"传输层",不是完整解决方案
完整存储加密方案
企业级存储加密架构
class EnterpriseStorageEncryption:
def __init__(self):
# 分层安全设计
self.layers = {
# 层1:物理加密(XTS)
"physical": SM4_XTS_Disk_Encryption(),
# 层2:文件系统加密(可选GCM)
"filesystem": {
"metadata": SM4_GCM(), # 元数据需要完整保护
"small_files": SM4_GCM(), # 小文件
"large_files": SM4_CTR_HMAC(), # 大文件流
},
# 层3:应用层加密(业务逻辑)
"application": {
"database": Column_Level_Encryption(),
"backup": SM4_GCM_With_Key_Rotation(),
}
}
def encrypt_data(self, data, context):
# 根据上下文选择合适层
if context == "disk_block":
return self.layers["physical"].encrypt(data)
elif context == "database_record":
# 应用层加密 + 物理层加密
app_encrypted = self.layers["application"]["database"].encrypt(data)
return self.layers["physical"].encrypt(app_encrypted)
什么情况下存储可以用GCM?
适合GCM的存储场景:
备份归档:
备份文件(一次性写入,顺序读取)
def encrypt_backup(backup_file):
# 整个备份文件一个GCM tag
nonce = generate_nonce()
ciphertext, tag = sm4_gcm_encrypt(
backup_file.read(),
key,
nonce
)
存储:nonce(12B) + tag(16B) + ciphertext
优点:强完整性验证
缺点:无法随机恢复部分文件
对象存储的小对象:
对象大小 < 1MB,且一次性读写
例如:用户头像、配置文件、API密钥
加密的通信协议中的存储:
如:TLS会话票据缓存
特点:体积小,需要强认证,与网络协议一致.
5 小结
为什么存储专用XTS,而不推荐GCM
维度 XTS GCM
设计目标 存储设备随机访问 网络数据流保护
核心假设 存储介质可信,完整性上层保障 传输信道不可信,需要端到端认证
访问模式 任意位置O(1)访问 顺序访问或全局验证
更新开销 扇区级,最小化写放大 Nonce管理复杂,可能全局重算
兼容性 与文件系统、SSD、RAID完美集成 破坏存储栈假设
XTS像"加密的RAM":每个地址独立,直接访问
GCM像"加密的TCP流":需要顺序处理,整体验证
在国密体系中,SM4-XTS将是存储加密的标准选择,而SM4-GCM适用于通信和需要强认证的场景。两者各司其职,共同构建完整的安全体系。
- 点赞
- 收藏
- 关注作者
评论(0)