如何将有效值传入 cleartext_keyset_json 以创建 Tink 密钥

How to pass in valid values into cleartext_keyset_json to create a Tink key

在 Tink 中,可以将明文密钥集作为 json 加载和编写。下面是一个无效的例子:

{
  "primaryKeyId": 2800579,
  "key": [
    {
      "keyData": {
        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
        "value": "ODA9eJX9wcAGwZocL0Jym==",
        "keyMaterialType": "SYMMETRIC"
      },
      "status": "ENABLED",
      "keyId": 2800579,
      "outputPrefixType": "TINK"
    }
  ]
}

我的问题是 - 是否可以将您自己的值插入到各种 key/value 对中以获得另一个有效的键集?我已经对此进行了试验,但没有取得太大的成功——主要是因为抱怨的“价值”键INVALID_ARGUMENT: Could not parse key_data.value as key type 'type.googleapis.com/google.crypto.tink.AesGcmKey'知道有效的“价值”是什么吗?

首先,发布的代码片段中value字段的Base64字符串无效,可能是copy/paste错误。

以下 Python 代码使用 Tink version 1.5.0 并为 AES-256/GCM 创建和显示键集 JSON:

import io
from tink import aead
from tink import tink_config
from tink import JsonKeysetWriter
from tink import new_keyset_handle
from tink import cleartext_keyset_handle

tink_config.register()

key_template = aead.aead_key_templates.AES256_GCM
keyset_handle = new_keyset_handle(key_template)

string_out = io.StringIO()
writer = JsonKeysetWriter(string_out)
cleartext_keyset_handle.write(writer, keyset_handle)

serialized_keyset = string_out.getvalue();
print(serialized_keyset);

结果类似于您发布的 KeySet,例如:

{
  "primaryKeyId": 1794775293,
  "key": [
    {
      "keyData": {
        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
        "value": "GiD5ojApaIM2MRpPhGf5sVMhxeA6NE5KjdzUxsJ0ChH/JA==",
        "keyMaterialType": "SYMMETRIC"
      },
      "status": "ENABLED",
      "keyId": 1794775293,
      "outputPrefixType": "TINK"
    }
  ]
}   

我还没有找到描述一般结构或 value 字段的文档,但是比较不同算法生成的 KeySets 可以得出结论。如果value是十六进制编码,结果是:

1a20f9a23029688336311a4f8467f9b15321c5e03a344e4a8ddcd4c6c2740a11ff24

对于AES-256/GCM,它有 34 个字节,其中最后 32 个字节是实际密钥。开头是算法的特征,第二个字节表示密钥的大小,例如0x1a10 AES-128/GCM,0x1a20 AES-256/GCM 或 0x1220 ChaCha20Poly1305 (但可能会更复杂,具体取决于算法)。

为AES-256/GCM使用自定义键,例如

000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 

前置0x1a20,Base64编码结果:

GiAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHw==

并应用此值而不是上述 KeySet 中的旧值。

修改后的KeySet可以按如下方式加载并用于加密:

from tink import JsonKeysetReader
from tink import cleartext_keyset_handle

serialized_keyset = '''
{
  "primaryKeyId": 1794775293,
  "key": [
    {
      "keyData": {
        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
        "value": "GiAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHw==",
        "keyMaterialType": "SYMMETRIC"
      },
      "status": "ENABLED",
      "keyId": 1794775293,
      "outputPrefixType": "TINK"
    }
  ]
}   
'''
reader = JsonKeysetReader(serialized_keyset)
keyset_handle = cleartext_keyset_handle.read(reader)

plaintext = b'The quick brown fox jumps over the lazy dog'
aead_primitive = keyset_handle.primitive(aead.Aead)
tink_ciphertext = aead_primitive.encrypt(plaintext, b'')

KeySet和示例密钥0001...1e1f之间的关系可以通过使用示例密钥without[=55=解密生成的密文来验证] Tink,例如使用 PyCryptodome。

Tink密文的格式在Tink Wire Format, Crypto Formats中有描述。第一个字节指定版本,接下来的 4 个字节是密钥 ID,然后是实际数据。
对于 GCM,实际数据的格式为 nonce(12 字节)||密文||标记(16 字节)。然后可以使用(使用 PyCryptodome)进行解密:

from Crypto.Cipher import AES

key = bytes.fromhex('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f')

prefix = tink_ciphertext[:5]
nonce = tink_ciphertext[5:5 + 12]
ciphertext = tink_ciphertext[5 + 12:-16]
tag = tink_ciphertext[-16:]

cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
cipher.update(b'')
decryptedText = cipher.decrypt_and_verify(ciphertext, tag)

print(decryptedText.decode('utf-8')) # The quick brown fox jumps over the lazy dog

证明示例密钥 0001...1e1f 已正确集成到密钥集中。

虽然其他答案包含一些非常酷的工作,但实际答案略有不同(我们应该将其添加到 Wireformat 描述中)。此处存储的值是与已注册相应 typeUrlKeyTypeManager 关联的类型的序列化原型。在这种情况下,proto AesGcmKey. AES-GCM does not have any parameters other than the key size (which is implicitly defined when given an AES-GCM key), so the proto contains a single byte array. (This explains the superfluous looking first byte, it's the proto serialization of the version and a single bytes field. XChaCha20Poly1305 也没有其他参数,我假设您看到不同前缀的原因是等效但不相等的 proto 序列化,理论上 proto 的名称不应影响序列化,并且两者使用相同的标签(1 用于 version,3 用于原始密钥 material)和 version 0)

如果您查看 ECIES 等更复杂的密钥类型,则相应的 value 会涉及更多(并且包含更多序列化原型。它一直是原型)。

我们计划在不久的将来对我们的密钥管理层进行一些实质性的改革,这将使 export/import 不使用 Tink 密钥格式的密钥更容易,而不必依赖于原型定义(这应该是一个内部实现细节)。