大多數金鑰環中都會包含某種熵,並包含「CBC 模式加密 + HMAC 驗證」或「GCM 加密 + 驗證」的演算法資訊。 在這種情況下,我們將嵌入的熵稱為該金鑰的主金鑰材料(或稱 KM),並執行金鑰推導函數來推導出用於實際密碼操作的金鑰。
備註
金鑰是抽象的,自訂實作可能不會像以下那樣運作。 如果金鑰提供自己的IAuthenticatedEncryptor 實作,而非使用我們內建的工廠,則本節所述機制將不再適用。
額外的認證資料與子金鑰推導
該 IAuthenticatedEncryptor 介面是所有認證加密操作的核心介面。 其 Encrypt 方法接受兩個緩衝區:明文與附加認證資料(AAD)。 明文內容在呼叫至IDataProtector.Protect時保持不變,但AAD由系統產生,包含三個組件:
32位元魔法標頭09 F0 C9 F0,用以識別此版本的資料保護系統。
128 位元密鑰ID。
這個操作使用的
IDataProtector是由目的鏈創建的可變長度字串。
由於 AAD 對這三個元件的元組是唯一的,我們可以用它從 KM 衍生出新的金鑰,而不必在所有的加密運算中直接使用 KM。 對於每一次呼叫,IAuthenticatedEncryptor.Encrypt都會進行以下金鑰推導過程:
( K_E, K_H ) = SP800_108_CTR_HMACSHA512(K_M, AAD, contextHeader || keyModifier)
這裡,我們使用計數器模式呼叫 NIST SP800-108 KDF(見 NIST SP800-108,第 5.1 節),參數如下:
密鑰推導鍵(KDK)=
K_MPRF = HMACSHA512
label = additionalAuthenticatedData
context = contextHeader ||keyModifier
上下文標頭長度可變,基本上用作代表演算法的指紋,我們從中推導K_E與K_H。 金鑰修飾符是一串 128 位元的字串,每次呼叫 Encrypt 時隨機生成,用以極高機率確保 KE 與 KH 在特定認證加密操作中是唯一,即使其他輸入 KDF 都是常數。
對於 CBC 模式加密 + HMAC 驗證操作, | K_E | 是對稱區塊密碼金鑰的長度, | K_H | 是 HMAC 例程的摘要大小。 對於 GCM 加密及驗證操作, | K_H | = 0
CBC 模式加密 + HMAC 驗證
透過 K_E 上述機制生成後,我們產生隨機初始化向量,並執行對稱區塊密碼演算法來加密明文。 初始化向量與密文接著會透過初始化的 HMAC 例程執行,並以金鑰 K_H 初始化以產生 MAC。 此過程及回報值在下方以圖形形式呈現。
output:= keyModifier || iv || E_cbc (K_E,iv,data) || HMAC(K_H, iv || E_cbc (K_E,iv,data))
備註
實作會在 IDataProtector.Protect 返回給呼叫者之前,先附加 magic 標頭和金鑰 id 到輸出。 由於魔術標頭與鍵 ID 被隱含在 AAD 中,且鍵修飾符作為輸入提供給 KDF,這表示最終回傳的每一個有效載荷位元組都被 MAC 認證。
伽羅瓦/計數模式加密+驗證
透過 K_E 上述機制生成後,我們會產生隨機的 96 位元 "nonce",並執行對稱區塊加密演算法來加密明文,進而產生 128 位元的認證標籤。
output := keyModifier || nonce || E_gcm (K_E,nonce,data) || authTag
備註
即使 GCM 原生支援 AAD 概念,我們仍然只將 AAD 餵給原始 KDF,選擇將空字串傳入 GCM 以取得 AAD 參數。 原因有兩個。 首先, 為了支援敏捷性 ,我們絕不想直接使用 K_M 加密金鑰。 此外,GCM 對其輸入有非常嚴格的唯一性要求。 GCM 加密程序在兩組或以上相同(金鑰、隨機數)對的輸入資料被呼叫的機率不得超過 2^-32。 如果我們修正 K_E,那麼在我們突破 2^-32 的限制之前,最多只能進行 2^32 次加密操作。 這看似操作數量龐大,但一台高流量的網頁伺服器可在短短幾天內處理 40 億次請求,遠在這些金鑰的正常壽命內。 為了符合 2^-32 的機率限制,我們繼續使用 128 位元的金鑰修飾器和 96 位元的 nonce,這大幅擴展了任一給定 K_M的可用運算數。 為了設計簡便,我們在 CBC 與 GCM 操作間共享 KDF 程式碼路徑,且 AAD 已在 KDF 中被考慮,無需轉發至 GCM 例程。