透明資料加密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
。所以,只需要在校正和計算之後解密,即可保證記憶體中的資料已被解密。