You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 06:55:36