本页介绍了 Tink 的密钥和原语输出的序列化格式。此文档面向希望向 Tink 添加其他语言的密码学家,以及希望使用线兼容模式的其他高级加密库的维护人员。不适合一般观众。
密钥集序列化
Tink 使用 Google protobuf 来序列化其密钥集。
- 二进制序列化密钥集是 tink.proto 中定义的序列化密钥集 proto。密钥的 KeyData 值属性是相应密钥类型的序列化 proto。
- JSON 序列化密钥集是以 JSON 格式序列化的密钥集 proto。 请注意,KeyData 值仍然是二进制序列化 proto。
- 加密的密钥集是 tink.proto 中定义的序列化 EncryptedKeyset proto。它包含一个加密的二进制序列化密钥集,还可以包含一些未加密的 KeysetInfo 元数据。
Tink 输出前缀
大多数 Tink 原语都支持 5 字节的输出前缀,该前缀包含:
- 1 字节版本:
0x01 - 4 字节密钥提示:这是所用密钥的密钥 ID。
某些旧版密钥可能也支持版本字节 0x00。
请注意,此前缀未经身份验证,不能用于安全目的。Tink 会将其用作提示,以加快解密或验证速度。
AEAD
一般来说,Tink 会将 AEAD 密文格式化为:
prefix || IV || ciphertext || tag
除非相应 RFC 中另有规定。prefix 为空或 5 字节的 Tink 输出前缀。
AES-CTR-HMAC
对于 AES-CTR-HMAC,Tink 会按如下方式计算包含关联数据 (AD) 的 MAC:
AD || IV || ciphertext || bitlen(AD)
其中,bitlen(AD) 是以 64 位大端序无符号整数表示的 AD 的长度(以位为单位)。此 HMAC 方案遵循 Mcgrew 的 AES-CBC-HMAC 草稿。
确定性 AEAD
Tink 为 AES-SIV 实现了 RFC 5297,将合成初始化向量 (SIV) 放在密文的开头。原语可能会添加 5 字节的 Tink 输出前缀。
虽然 RFC 5297 支持关联数据的列表,但 Tink 仅支持一个关联数据,这相当于 RFC 5297 中的一个元素列表。空关联数据是指包含一个空元素的列表,而不是空列表。
流式 AEAD
请参阅 AES-CTR HMAC 和 AES-GCM-HKDF。
信封加密
信封加密使用 Tink 的 AEAD 原语,通过数据加密密钥 DEK 对数据进行加密。加密的工作原理如下:
- 系统会使用给定的密钥模板(或密钥参数)生成新的
DEK。 DEK会序列化为字节字符串。密钥类型 proto 的协议缓冲区序列化的序列化格式。例如,以下是 aes_gcm.proto 中定义的序列化AesGcmKey协议缓冲区消息,适用于密钥类型为 AES GCM 的 DEK。 如需了解如何序列化协议缓冲区,请参阅协议缓冲区序列化。- 序列化的
DEK由外部提供方(例如 GCP)加密为encrypted DEK。 DEK用于将明文与关联数据一起加密为ciphertext。因此,ciphertext的格式与DEK对应的 AEAD 原语完全相同。
信封加密的输出格式如下:
encrypted DEK length || encrypted DEK || ciphertext
encrypted DEK length 为 4 字节,以 32 位大端序整数形式存储 encrypted DEK 的长度。
MAC
Tink 遵循相应的 RFC。原语可能会向标记添加 5 字节的 Tink 输出前缀。
PRF 集
Tink 遵循相应的 RFC。请注意,对于 PRF,密钥类型与相同算法的 MAC 密钥类型不同,因为前者不包含输出长度。PRF 集密钥绝不会添加 Tink 输出前缀。这样可确保输出实际上是 PRF。
混合加密
Tink 混合加密的一般有线格式如下:
prefix || encapsulated_key || encrypted_data
prefix 为空或 5 字节的 Tink 输出前缀。每种密钥类型都包含有关要解析的字节数以及如何从 encapsulated_key 解析这些字节的信息。
HPKE(混合公钥加密)
Tink 遵循 RFC 9180 中定义的 HPKE 标准。 HPKE 密码套件包含以下三种原语。
- 密钥封装机制 (KEM)
- 密钥派生函数 (KDF)
- 关联数据加密的身份验证 (AEAD)
HPKE 标准未在 RFC 9180 第 10 节中定义通用线格式。Tink 的 HPKE 实现使用以下 encapsulated_key 和 encrypted_data 值。
encapsulated_key- 发件人的序列化公钥
- 在 RFC 9180 第 4.1 节中定义为
enc - 格式由所用的特定 HPKE KEM 确定
encrypted_data- 密文和标记(即
ciphertext || tag(不含 IV) - 在 RFC 9180 第 4 节中定义为
ct - 由所用的特定 HPKE AEAD 确定
- 密文和标记(即
基于 X25519 Diffie-Hellman 的 KEM
对于 X25519 DHKEM,值 enc 是发送方的 32 字节 Diffie-Hellman 公钥。
ECIES-AEAD-HKDF
对于 Tink 的 ECIES-AEAD-HKDF 实现,encapsulated_key 是密钥封装机制 (KEM) 的输出,encrypted_data 是数据封装机制 (DEM) 的输出。
KEM
根据密钥类型,Tink 会使用压缩和未压缩的椭圆曲线点,遵循 RFC 8422/ANSI.X9-62.2005 编码标准。对于未压缩的点,字节 0x04 后面是 x 和 y 坐标,以固定大小的整数表示。对于压缩坐标,使用字节 0x02 或 0x03 以及作为固定大小整数的 x 坐标。对于 X25519,使用 RFC 7748 定义(x 坐标为固定大小的整数)。
DEM
对于 encrypted_data,Tink 使用与 AEAD 相同的格式。这包括指定 IV。
密钥派生
首先,计算共享点的 x 坐标 x_ss。然后,将 AEAD 的密钥设置为:
HKDF(ikm = encapsulated_key || x_ss, salt = salt_of_key, info = context_info, length = dem_key_size)
其中,encapsulated_key 是以字节为单位的完整 KEM 输出。
数字签名
Tink 遵循相应的 RFC。原语可能会向生成的标记添加 5 字节的 Tink 输出前缀。
ECDSA
根据密钥中的 EcdsaSignatureEncoding 字段,ECDSA 签名的格式为 IEEE P1363 或 ASN.1 DER。
IEEE P1363 签名的格式为 r || s,其中 r 和 s 经过零填充,并且字节大小与曲线的阶数相同。例如,对于 NIST P-256 曲线,r 和 s 会填充零,直至达到 32 字节。
DER 签名使用 ASN.1 进行编码:
ECDSA-Sig-Value :: = SEQUENCE { r INTEGER, s INTEGER }
具体而言,编码为:
0x30 || totalLength || 0x02 || r's length || r || 0x02 || s's length || s
Tink 遵循签名验证的最佳实践,仅接受 DER 编码的 ECDSA 签名(其他 BER 编码的签名无效)。
这有助于防范签名可延展性攻击,此类攻击通常会影响加密货币系统。
外部 Mu ML-DSA 预哈希值
预哈希原语将消息转换为预哈希值,然后由 SignPrehash 原语对该值进行签名。对于 External Mu 模式下的 ML-DSA(如 RFC 9881 中所述),预哈希值为 69 字节,布局如下:
0xff || key_id (4 bytes, big-endian) || mu (64 bytes)
mu是 ML-DSA 消息代表,计算方式为SHAKE256(tr || 0x00 || 0x00 || message, 64),其中tr是公钥的 64 字节哈希,第一个0x00是纯 ML-DSA 的 FIPS 204 域分隔符,第二个0x00是(空)上下文字符串的长度。由于tr是从公钥派生出来的,因此mu在密码学上与该密钥绑定,不过检查绑定需要原始消息。- 5 字节前缀是 Tink 框架,不属于
mu。它将预哈希值与一个特定密钥相关联,以便 SignPrehash 知道要使用哪个密钥进行签名,并可以拒绝它没有相应密钥的预哈希值。前缀是纯元数据,不以加密方式绑定任何内容:任何人都可以重写它。
签名会拒绝任何不完全是 69 字节、不以 0xff 开头或密钥 ID 与密钥集中已启用的密钥不匹配的输入。