使用Tink与HashiCorp Vault流式加密时跨会话解密失败问题
Tink + HashiCorp Vault 跨会话解密报错:
No matching key found for the ciphertext in the stream. 使用Tink结合HashiCorp Vault实现加密解密时,同一会话内用同一个AEAD对象完成加解密流程完全正常,但将加密后的文件保存后,单独运行解密逻辑时,会抛出错误:No matching key found for the ciphertext in the stream.
参考代码:
keyUri := "hcvault://my-vault-url.com/transit/keys/my-key2" vaultClient, err := hcvault.NewClient(keyUri, tlsConfig(), vaultToken()) if err != nil { log.Fatal(err) } kekAEAD, err := vaultClient.GetAEAD(keyUri) if err != nil { log.Fatal(err) } // Generate a new keyset handle for the primitive we want to use. newHandle, err := keyset.NewHandle(streamingaead.AES256GCMHKDF1MBKeyTemplate()) if err != nil { log.Fatal(err) } // Choose some associated data. This is the context in which the keyset will be used. keysetAssociatedData := []byte("keyset encryption example") // Encrypt the keyset with the KEK AEAD and the associated data. buf := new(bytes.Buffer) writer := keyset.NewBinaryWriter(buf) err = newHandle.WriteWithAssociatedData(writer, kekAEAD, keysetAssociatedData) if err != nil { log.Fatal(err) } encryptedKeyset := buf.Bytes() reader := keyset.NewBinaryReader(bytes.NewReader(encryptedKeyset)) handle, err := keyset.ReadWithAssociatedData(reader, kekAEAD, keysetAssociatedData) if err != nil { log.Fatal(err) } streamingAEAD, err := streamingaead.New(handle) if err != nil { log.Fatal(err) } outputFilePath := "C:\\temp\\encryptionOutput6.txt" inputFilePath := "C:\\temp\\input.mkv" EncryptFile(streamingAEAD, inputFilePath, outputFilePath, keysetAssociatedData) DecryptFile(streamingAEAD, outputFilePath, "c:\\temp\\f_result.mkv", keysetAssociatedData)
问题原因
核心问题是跨会话解密时未复用加密所用的流式AEAD密钥集。当前代码每次运行都会生成全新的keyset handle(通过streamingaead.AES256GCMHKDF1MBKeyTemplate()),加密用的是本次会话生成的密钥,而单独解密时又生成了另一套全新密钥集,自然无法匹配之前密文对应的密钥,导致报错。
解决方案
需要将加密时使用的、经Vault KEK加密后的密钥集保存下来,解密时重新加载该密钥集,而非生成新密钥。
1. 加密流程(保存密钥集)
修改加密代码,将encryptedKeyset写入文件留存:
package main import ( "bytes" "log" "os" "github.com/google/tink/go/keyset" "github.com/google/tink/go/streamingaead" "github.com/google/tink/go/integration/hcvault" ) func main() { keyUri := "hcvault://my-vault-url.com/transit/keys/my-key2" vaultClient, err := hcvault.NewClient(keyUri, tlsConfig(), vaultToken()) if err != nil { log.Fatal(err) } kekAEAD, err := vaultClient.GetAEAD(keyUri) if err != nil { log.Fatal(err) } // 生成流式AEAD密钥集 newHandle, err := keyset.NewHandle(streamingaead.AES256GCMHKDF1MBKeyTemplate()) if err != nil { log.Fatal(err) } keysetAssociatedData := []byte("keyset encryption example") // 加密密钥集并保存到文件 buf := new(bytes.Buffer) writer := keyset.NewBinaryWriter(buf) err = newHandle.WriteWithAssociatedData(writer, kekAEAD, keysetAssociatedData) if err != nil { log.Fatal(err) } encryptedKeyset := buf.Bytes() err = os.WriteFile("encrypted_keyset.bin", encryptedKeyset, 0600) if err != nil { log.Fatal(err) } // 初始化流式AEAD并加密目标文件 streamingAEAD, err := streamingaead.New(newHandle) if err != nil { log.Fatal(err) } inputFilePath := "C:\\temp\\input.mkv" outputFilePath := "C:\\temp\\encryptionOutput6.txt" EncryptFile(streamingAEAD, inputFilePath, outputFilePath, keysetAssociatedData) }
2. 解密流程(加载保存的密钥集)
单独解密时,先读取留存的密钥集文件,再解密得到密钥集handle:
package main import ( "bytes" "log" "os" "github.com/google/tink/go/keyset" "github.com/google/tink/go/streamingaead" "github.com/google/tink/go/integration/hcvault" ) func main() { keyUri := "hcvault://my-vault-url.com/transit/keys/my-key2" vaultClient, err := hcvault.NewClient(keyUri, tlsConfig(), vaultToken()) if err != nil { log.Fatal(err) } kekAEAD, err := vaultClient.GetAEAD(keyUri) if err != nil { log.Fatal(err) } // 读取保存的加密密钥集 encryptedKeyset, err := os.ReadFile("encrypted_keyset.bin") if err != nil { log.Fatal(err) } keysetAssociatedData := []byte("keyset encryption example") // 解密密钥集得到handle reader := keyset.NewBinaryReader(bytes.NewReader(encryptedKeyset)) handle, err := keyset.ReadWithAssociatedData(reader, kekAEAD, keysetAssociatedData) if err != nil { log.Fatal(err) } // 初始化流式AEAD并解密目标文件 streamingAEAD, err := streamingaead.New(handle) if err != nil { log.Fatal(err) } inputFilePath := "C:\\temp\\encryptionOutput6.txt" outputFilePath := "C:\\temp\\f_result.mkv" DecryptFile(streamingAEAD, inputFilePath, outputFilePath, keysetAssociatedData) }
注意事项
keysetAssociatedData在加密和解密时必须完全一致(字节内容、长度均要匹配),Tink会用它验证密钥集的合法性。encrypted_keyset.bin是经Vault Transit密钥加密的,即使泄露也无法直接获取原始密钥,可安全存储。- Go代码中Windows路径的反斜杠需转义为
\\,避免解析错误。
内容的提问来源于stack exchange,提问作者barambuk
相关产品推荐
相关产品推荐

