透明数据加密TDE(Transparent Data Encryption)通过在数据库层执行透明的数据加密,阻止可能的攻击者绕过数据库直接从存储层读取敏感信息。
前提条件
支持的PolarDB PostgreSQL版的版本如下:
PostgreSQL 14(内核小版本14.5.1.1及以上)
PostgreSQL 11(内核小版本1.1.1及以上)
您可通过如下语句查看PolarDB PostgreSQL版的内核小版本的版本号:
PostgreSQL 14
select version();
PostgreSQL 11
show polar_version;
背景信息
在中国,为了保证互联网信息安全,国家要求相关服务开发商需要满足一些数据安全标准,例如:
《国家密码法》(2020年1月1日施行)。
《网络安全等级保护基本要求》(GB/T 22239-2019)。
在国际上,一些相关行业也有监管数据安全标准,例如:
Payment Card Industry Data Security Standard (PCI DSS)
Health Insurance Portability and Accountability Act (HIPAA)
General Data Protection Regulation (GDPR)
California Consumer Protection Act (CCPA)
Sarbanes-Oxley Act (SOX)
为了满足保护用户数据安全的需求,PolarDB推出TDE功能。经过数据库身份验证的用户可以透明(不需要更改应用代码或配置)地访问数据,而尝试读取表空间文件中敏感数据的OS用户以及尝试读取磁盘或备份信息的不法之徒将不允许访问明文数据。
术语
名词 | 描述 |
KEK(Key Encryption Key) | 密钥加密密钥。用密钥对密钥进行经一步加密。 |
MDEK(Memory Data Encryption Key) | 存在于内存的数据加密密钥。通过 |
TDEK(Table Data Encryption Key) | 表数据加密密钥。由MDEK经过HKDF算法生成,存在内存中,作为实际加密数据的密码。 |
WDEK(Wal Data Encryption Key) | 日志数据加密密钥。由MDEK经过HKDF算法生成,存在内存中,作为实际加密数据的密码。 |
HMACK(Hash-based Message Authentication Code of Key) | 基于哈希消息认证码算法生成的密钥。passphrase经过SHA-512加密后生成KEK和HMACK。 |
KEK_HMAC(Hash-based Message Authentication Code of Key Encryption Key) | 基于哈希消息认证码算法生成的密钥加密密钥摘要。ENCMDEK和HMACK经过HMAC算法生成KEK_HMAC,用于还原密钥时的校验信息。 |
ENCMDEK(Encode Memory Data Encryption Key) | 存在于内存的数据加密密钥。由KEK加密MDEK生成ENCMDEK。 |
原理介绍
密钥管理模块
密钥结构
采用2层密钥结构,即密钥加密密钥和表数据加密密钥。表数据加密密钥是实际对数据库数据进行加密的密钥。密钥加密密钥则是对表数据加密密钥进行进一步加密的密钥。两层密钥的详细介绍如下:
密钥加密密钥(KEK),以及KEK的校验值HMACK:通过运行
polar_cluster_passphrase_command
参数中命令并计算SHA-512后得到64字节的数据,其中前32字节为顶层加密密钥KEK,后32字节为HMACK。表数据加密密钥(TDEK)和WAL日志加密密钥(WDEK):通过密码学中的安全随机数生成器生成的密钥,是数据和WAL日志加密的真正密钥。两个密钥加密后的密文使用HMACK作为密钥,经过HMAC算法得到rdek_hmac和wdek_hmac,用于密钥KEK的校验,保存在共享存储上。
KEK和HMACK每次都是通过外部获取。例如,KMS,测试的时候可以直接通过
echo passphrase
得到。ENCMDEK和KEK_HMAC需要保存在共享存储上,用来保证下次启动时RW和RO都可以读取该文件,获取真正的加密密钥。其数据结构如下:typedef struct KmgrFileData { /* version for kmgr file */ uint32 kmgr_version_no; /* Are data pages encrypted? Zero if encryption is disabled */ uint32 data_encryption_cipher; /* * Wrapped Key information for data encryption. */ WrappedEncKeyWithHmac tde_rdek; WrappedEncKeyWithHmac tde_wdek; /* CRC of all above ... MUST BE LAST! */ pg_crc32c crc; } KmgrFileData;
该文件当前是在initdb时产生,这样就可以保证Standby通过
pg_basebackup
获取到。在集群运行状态下,TDE相关的控制信息保存在进程的内存中,结构如下:
static keydata_t keyEncKey[TDE_KEK_SIZE]; static keydata_t relEncKey[TDE_MAX_DEK_SIZE]; static keydata_t walEncKey[TDE_MAX_DEK_SIZE]; char *polar_cluster_passphrase_command = NULL; extern int data_encryption_cipher;
密钥加密
数据库初始化时需要生成密钥,过程示意图如下所示:
运行
polar_cluster_passphrase_command
得到64字节的KEK + HMACK,其中KEK长度为32字节,HMACK长度为32字节。调用OpenSSLopen 中的随机数生成算法生成MDEK。
使用MDEK调用OpenSSL的HKDF算法生成TDEK。
使用MDEK调用OpenSSL的HKDF算法生成WDEK。
使用KEK加密MDEK生成ENCMDEK。
ENCMDEK和HMACK经过HMAC算法生成KEK_HMAC用于还原密钥时的校验信息。
将ENCMDEK和KEK_HMAC补充的其他
KmgrFileData
结构信息写入global/kmgr文件。
密钥解密
当数据库崩溃或重新启动等情况下,需要通过有限的密文信息解密出对应的密钥,其过程如下:
读取global/kmgr文件获取ENCMDEK和KEK_HMAC。
运行
polar_cluster_passphrase_command
得到64字节的KEK + HMACK。ENCMDEK和HMACK经过HMAC算法生成KEK_HMAC'。比较KEK_HMAC和KEK_HMAC'两者是否相同。如果相同,继续下一步;如果不同则报错返回。
使用KEK解密ENCMDEK生成MDEK。
使用MDEK调用OpenSSL的HKDF算法生成TDEK,因为是特定的info,所以可以生成相同TDEK。
使用MDEK调用OpenSSL的HKDF算法生成WDEK,因为是特定的info,所以可以生成相同WDEK。
密钥更换
密钥更换的过程可以理解为先用旧的KEK还原密钥,然后再用新的KEK生成新的kmgr文件。其过程如下图所示:
读取global/kmgr文件获取ENCMDEK和KEK_HMAC。
运行
polar_cluster_passphrase_command
得到64字节的KEK + HMACK。ENCMDEK和HMACK经过HMAC算法生成KEK_HMAC'。比较KEK_HMAC和KEK_HMAC'两者是否相同。如果相同,继续下一步;如果不同则报错返回。
使用KEK解密ENCMDEK生成MDEK。
运行
polar_cluster_passphrase_command
得到64字节新的new_KEK + new_HMACK。使用new_KEK加密MDEK生成new_ENCMDEK。
new_ENCMDEK和new_HMACK经过HMAC算法生成new_KEK_HMAC用于在还原密钥时校验信息。
将new_ENCMDEK和new_KEK_HMAC补充的其他
KmgrFileData
结构信息写入global/kmgr文件。
加密模块
我们期望对所有的用户数据按照Page的粒度进行加密,加密方法采用AES-128/256加密算法(产品化默认使用AES-256)。
(page LSN,page number)
作为每个数据页加密的IV,IV是可以保证相同内容加密出不同结果的初始向量。每个Page的头部数据结构如下:
typedef struct PageHeaderData { /* XXX LSN is member of *any* block, not only page-organized ones */ PageXLogRecPtr pd_lsn; /* LSN: next byte after last byte of xlog * record for last change to this page */ uint16 pd_checksum; /* checksum */ uint16 pd_flags; /* flag bits, see below */ LocationIndex pd_lower; /* offset to start of free space */ LocationIndex pd_upper; /* offset to end of free space */ LocationIndex pd_special; /* offset to start of special space */ uint16 pd_pagesize_version; TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */ ItemIdData pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */ } PageHeaderData;
说明其中:
pd_lsn
不能加密,因为解密时需要使用IV来解密。pd_flags
增加是否加密的标志位0x8000
,并且不加密,这样可以兼容明文Page的读取,为增量集群打开TDE提供条件。pd_checksum
不加密,这样可以在密文条件下判断Page的校验和。
加密文件
当前加密含有用户数据的文件。例如,加密数据目录中以下子目录中的文件:
base/
global/
pg_tblspc/
pg_replslot/
pg_stat/
pg_stat_tmp/
何时加密
当前对于按照数据Page来进行组织的数据,将按照Page来进行加密。Page落盘之前必定需要计算校验和,即使校验和相关参数关闭,也会调用校验和相关的函数
PageSetChecksumCopy
或PageSetChecksumInplace
。所以,只需要计算校验和之前加密Page,即可保证用户数据在存储上是被加密的。
解密模块
存储上的Page读入内存之前必定经过checksum校验,即使相关参数关闭,也会调用校验函数
PageIsVerified
。所以,只需要在校验和计算之后解密,即可保证内存中的数据已被解密。