密碼學有一個核心原則,即輸出可以是確定性或非確定性。對於確定性輸出,對於相同的輸入,我們總是得到相同的輸出。例如,我們可以從明文中建立一個密碼,並且輸出將始終是相同的密文。對於非確定性,我們無法預測將使用什麼輸出,因為在過程中已使用某種形式的隨機化。對於簽名,ECDSA 是非確定性的,而 EdDSA 是確定性的。
當涉及到對稱金鑰方法時,確定性本質在搜尋資料時可能很有用,但從安全的角度來看,它會比較弱,因為攻擊者可以將輸入對應到輸出。在這種情況下,我們將確定性本質與具有關聯資料的身份驗證加密(Deterministic AEAD)相結合,並使用 AES-SIV 模式。
我們洩露了太多的秘密,並且常常未能保護我們最重要的秘密之一……我們的加密金鑰。為了克服這個問題,我們可以使用一種稱為金鑰包裝的方法,它可以保護金鑰。當我們透過不受信任的通道傳輸金鑰或將其儲存在沒有強存取控制的地方時,這尤其重要。對於金鑰包裝,Rogaway 等人提出了 SIV(合成初始化向量)方法 [here],它對金鑰進行身份驗證和加密,並對與金鑰相關的任何其他資料進行身份驗證:
該方法現在已透過 RFC 5297 [here] 進行了標準化。透過增強的加密方法,我們可以對密碼進行身份驗證並證明其完整性。這被稱為具有關聯資料的身份驗證加密 (AEAD)。為此,我們提供額外的資料來驗證加密過程,並且我們可以識別密文在哪裡被修改,以便無法對其進行解密。對於大多數傳統的 AEAD 方法,我們建立一個 nonce 值並新增額外的資料 (AD),這些資料經過身份驗證但未加密。使用無 nonce 的方法,我們可以使用金鑰包裝方法,該方法通常用於保護加密金鑰。額外的資料可以包括 [here]:
addresses, ports, sequence numbers, protocol version numbers, and other fields that indicate how the plaintext or ciphertext should be handled, forwarded, or processed
透過這種方式,我們可以將網路封包繫結到加密資料並提供完整性,以便入侵者無法從其他傳輸中複製和貼上有效的密文。例如,如果我們繫結到封包序列號和埠,則對於另一個序列號或另一個埠,身份驗證將失敗。
在下面的程式碼中,我們將使用“101112131415161718191a1b1c1d1e1f2021222324252627”的額外資訊,但在實踐中,它可以是任何長度。在 SIV 中,我們也可以有多個此額外資料來源(稱為字串向量)。此資訊可以分佈在多個來源中,並且入侵者可能難以捕獲和複製。這是 SIV 的特殊功能之一,它允許使用多個字串進行身份驗證,而不必將這些字串合併為單個字串以獲取額外資訊:
byte[] plaintext = pl.getBytes();
String additional="101112131415161718191a1b1c1d1e1f2021222324252627";
byte[] ciphertext = daead.encryptDeterministically(plaintext, Hex.decode(additional));
傳統的 AEAD 方法在 nonce 重複使用以及 nonce 誤用方面可能很弱。在許多系統中,我們使用非確定性方法來產生 nonce,因此無法知道以前產生的值是否以前未使用過。另一個弱點是 nonce 可以透過回滾虛擬機器並發現使用的 nonce 值來「回放」。因此,大多數 AEAD 方法在具有唯一 nonce 的情況下是安全的,但如果 nonce 不唯一,則無法保證安全性。由於 nonce 值的大小有限,因此始終有可能使用相同的金鑰重複使用它,因此,我們可能必須定期更改金鑰。SIV 為 nonce 重複使用/誤用提供了更多保護,並且攻擊者只能確定給定的明文值和給定的一組關聯資料是使用定義的金鑰和 nonce 值保護的。
在下面的程式碼中,我們建立一個 SIV 金鑰,然後建立密文值,這兩個值應該相同 [here]:
package main
import (
"fmt"
"os"
"github.com/tink-crypto/tink-go/v2/daead"
"github.com/tink-crypto/tink-go/v2/keyset"
)
func main() {
msg:="Hello"
associated:="Hello"
argCount := len(os.Args[1:])
if (argCount>0) {msg = os.Args[1]}
if (argCount>1) {associated =os.Args[2]}
handle, _ := keyset.NewHandle(daead.AESSIVKeyTemplate())
primitive, _ := daead.New(handle)
plaintext := []byte(msg)
associatedData := []byte(associated)
ciphertext1, _:= primitive.EncryptDeterministically(plaintext, associatedData)
ciphertext2, _:= primitive.EncryptDeterministically(plaintext, associatedData)
res, _ := primitive.DecryptDeterministically(ciphertext1, associatedData)
fmt.Printf("Plaintext is %s\n",msg)
fmt.Printf("Associated data is %s\n\n",associated)
fmt.Printf("Key is %s\n\n",handle)
fmt.Printf("Cipher1 is %x\n\n",ciphertext1)
fmt.Printf("Cipher2 is %x\n\n",ciphertext2)
fmt.Printf("Decrypted is %s",res)
}
一個範例執行顯示,當我們確定性地加密時,我們具有相同的密碼 [here]:
Plaintext is Testing 123
Associated data is qwerty123
Key is primary_key_id:1537855825 key_info:{type_url:"type.googleapis.com/google.crypto.tink.AesSivKey" status:ENABLED key_id:1537855825 output_prefix_type:TINK}
Cipher1 is 015ba9d1518f3a5d213ce9630294f87af76ce86011c8631842aa96571d0dee03
Cipher2 is 015ba9d1518f3a5d213ce9630294f87af76ce86011c8631842aa96571d0dee03
Decrypted is Testing 123