金鑰組

Tink 會使用 Keyset 啟用金鑰輪替功能。正式來說,金鑰組是空白以外的金鑰清單1,其中一個金鑰會指定為主要金鑰 (例如用於簽署及加密新的明文)。此外,鍵組中的鍵會取得專屬 ID2 和鍵狀態,可讓您停用鍵,而無須從鍵組中移除鍵。

鍵組是使用者存取鍵的主要方式 (透過 KeysetHandle 類別)。這可確保每位使用者都有一組程式碼,可同時處理多個鍵。對於大多數加密技術使用者而言,處理多個金鑰是必要的:他們需要能夠變更金鑰 (例如舊金鑰可能會外洩),而且幾乎不會有「切換至下一個金鑰」的獨立作業,可在全球範圍內,立即套用於程式碼執行的機器和所有密文。因此,使用者需要編寫程式碼,讓系統在從一個按鍵切換到下一個按鍵時運作。

範例:AEAD

請考慮 AEAD 鍵組,其中包含 AEAD 基本元素的多個鍵。如先前所述,每個鍵都會指定兩個函式: \(\mathrm{Enc}\) 和 \(\mathrm{Dec}\)。鍵組現在也指定了兩個新函式: \(\mathrm{Enc}\) 和 \(\mathrm{Dec}\) 。 \(\mathrm{Enc}\) 等同於鍵組主索引鍵的函式 \(\mathrm{Enc}\) ,而函式 \(\mathrm{Dec}\) 會嘗試使用所有金鑰解密,並依某種順序逐一執行 (請參閱下方的說明,瞭解 Tink 如何改善這項功能的效能)。

值得一提的是,Keyset 是完整的鍵:它是所用函式 \(\mathrm{Enc}\) 和\(\mathrm{Dec}\) 的完整說明。這表示使用者可以編寫類別,並將 KeysetHandle 做為輸入內容,表示類別需要完整的物件說明 \(\mathrm{Enc}\) 和 \(\mathrm{Dec}\) 才能正常運作。這可讓使用者編寫 API,說明如要使用這個類別,就必須提供加密原語的說明。

金鑰輪替

以 Tink 使用者為例,他們編寫的程式會先從 KMS 取得金鑰組,然後使用該金鑰組建立 AEAD 物件,最後使用這個物件加密及解密密文。

系統會自動為這類使用者準備金鑰輪替,並在使用者目前選擇的演算法不再符合標準時切換演算法。

不過,實作這類金鑰輪替時必須小心謹慎:首先,KMS 應在金鑰組中新增金鑰 (但尚未將其設為主要金鑰)。接著,您必須將新的金鑰組推送至所有二進位檔,以便每個使用此金鑰組的二進位檔,都能在金鑰組中取得最新的金鑰。只有在這個時候,新金鑰才會設為主要金鑰,而產生的金鑰組會再次分發至使用該金鑰組的所有二進位檔。

密文中的金鑰 ID

請再想想 AEAD 鍵組的範例。如果採用這種方式,解密密文時,Tink 會嘗試使用金鑰組中的所有金鑰進行解密,因為無法得知用來加密金鑰組的金鑰。這可能會導致大量的效能額外負擔。

因此,Tink 允許在密文前方加上由 ID 衍生的 5 個位元組字串。根據上述「完整金鑰」的概念,這個前置字串是金鑰的一部分,且所有使用此金鑰產生的密文都應包含這個前置字串。使用者建立金鑰時,可以選擇金鑰是否要使用前置字串,或是使用不含前置字串的密文格式。

當金鑰位於金鑰組中時,Tink 會根據金鑰在金鑰組中的 ID 計算這個標記。在鍵組中,ID 是唯一的2,這表示標記也是唯一的。因此,如果只使用標記的金鑰,相較於使用單一金鑰解密,不會有任何效能損失:Tink 在解密時只需嘗試其中一個金鑰。

不過,由於標記是鍵的一部分,這也表示鍵必須具有特定 ID,才能位於鍵組中。這會影響描述不同語言中關鍵物件的實作方式。


  1. Tink 的部分部分仍會將 Keyset 視為集合。不過,這應該要變更。原因是順序通常很重要:舉例來說,請考慮使用 Aead 進行金鑰輪替的一般生命週期。首先,系統會將新鍵新增至鍵組。這組金鑰尚未設為主要金鑰,但已啟用。這組新金鑰已推送至所有二進位檔。一旦所有二進位檔都知道新金鑰,系統就會將該金鑰設為主要金鑰 (只有在這個時候使用這個金鑰才安全)。在這個第二步驟中,金鑰輪替功能需要知道上次新增的金鑰。 

  2. 為了與 Google 內部程式庫相容,Tink 允許在重複 ID 的鍵組中使用 ID。這項支援功能日後將會移除。