TDE 核心原理:密钥层次结构与加密机制
很多人觉得 TDE 很神秘,好像做了啥黑魔法。其实把它的核心原理拆开看,就是一个**分层保护**的小体系。今天我重点讲三个东西:密钥怎么分层、数据怎么被加密、日志又怎么处理。
嗯,先看最关键的密钥层次结构。
密钥层次结构
TDE 不会只靠一个密码就搞定全部数据。它用的是「层层包裹」的思路——一层加密保护下一层。我习惯把它叫作「俄罗斯套娃」模型。下面是它的层级拆解:
| 层级 | 名称 | 生成来源 | 存储位置 | 轮换频率 |
|---|---|---|---|---|
| L0 | 主密钥(Master Key / KEK) | 外部 KMS / HSM 生成 | 硬件安全模块或密钥管理服务 | 按安全策略,通常 1~3 年 |
| L1 | 表空间密钥(Tablespace Key / DEK) | 数据库内部随机生成并受 L0 加密保护 | 数据库数据字典(加密存储) | 按需轮换,不影响已有数据 |
| L2 | 文件密钥(File Key) | 由 L1 派生而来 | 不在磁盘持久化,运行时内存临时存在 | 每次 DB 实例重启自动刷新 |
| L3 | 页密钥(Page Key) | 由 L2 对每个数据页计算得到 | 不落地,每个页加密时派生 | 每次读写新页时重新计算 |
这个结构有什么好处?你想想看:假设攻击者拿到了数据库文件,他只能看到被 L2 或 L3 加密过的乱码。他如果想解密,得先拿到 L1,而 L1 又被 L0 加密,L0 存在远处的 KMS 里。只要 KMS 那边不松口,他根本动不了你的数据。
具体到落盘过程,数据写入时走的路径是这样的:
写入路径:
数据库页 → L3 派生页密钥 → AES-256-XTS 加密 → 加密后的密文页 → 落盘
读取路径:
从磁盘读取密文页 → 页级页密钥派生于 L2 → 原地解密 → 明文数据页到内存缓存
页级加密会让每页的加密初始向量(IV)都不一样,避免相同明文页加密后得到相同密文。这个技术在 Oracle 和 SQL Server 的 TDE 实现里都已经很成熟了。
数据加密过程
讲完密钥怎么放,再讲数据到底怎么加密。TDE 做的是页级透明加密——它对应用来说是完全无感的,但内部做了大量工作。
我刚开始接触 TDE 时有个困惑:为啥不直接对整库一把梭哈加密?后来在一次性能压测里才明白——**页级加密可以实现按需加解密**。数据库读数据是以页为单位的,如果只读一页,就只解密那一页,效率高很多。
这里我总结了一个简化实现流程:
- 明文页进入 Buffer Pool,等待写入磁盘。
- 检查该表空间是否启用了 TDE。如果是,走第 3 步;否则直接写磁盘。
- 从缓存中取 L2 派生出来的文件密钥。
- 对当前页号 + LSN 做 hash/计数器得到页密钥 IV,与 L2 合成的页密钥(L3)。
- 调用 AES-256-XTS,以页密钥加密整个 8KB(或 16KB)数据页。
- 加密后的密文页写入磁盘。
一个细节: 是原地加密吗?是,也不是。密文页占据和明文页相同大小的空间,所以数据文件大小不会凭空翻倍。但页头会有一个标记位标识「此页已加密」,读取时根据标记决定是否解密——这个叫加密标识位,很关键。
日志加密
很多人容易忽略日志这块。你想想看,数据文件全部加密了,但重做日志(Redo Log)和归档日志里可能还有明文,那不等于开了一扇后门?
我见过一个案例:客户把数据文件加密做得很到位,结果审计发现归档日志里明晃晃地插着客户信用卡号的明文片段。为啥?因为对日志的加密和对数据文件的加密是**两套逻辑**。
TDE 对日志的做法一般分为两种:
| 日志类型 | 加密方式 | 密钥来源 | 备注 |
|---|---|---|---|
| 在线 Redo Log | 日志块级加密 | 与表空间密钥同源的日志密钥 | 写日志时实时加密 |
| 归档日志 | 使用日志文件密钥加密 | 独立派生,仍受主密钥保护 | 归档时或归档后对称加密 |
| Undo / Rollback 段 | 同数据页加密 | 沿用表空间密钥 | 因为 Undo 本身是表空间的一份子 |
这里我重点提一下在线 Redo 的「同步加密」。日志必须是顺序写的,加密过程如果产生了额外延时,会造成事务响应变慢。所以很多数据库实现在 Redo 加密时用的是「懒加载密钥」——密钥早就在内存里准备好了,等日志写过来直接 XOR 一下就行,不需要实时查询密钥层次。