Prehash 和 SignPrehash 原语将数字签名的计算分为两步:
- 预哈希只需要公钥。它将任意长度的消息转换为简短的固定大小的预哈希值。
- SignPrehash 需要私钥。它将预哈希值转换为签名。
生成的签名是针对原始消息的普通签名。您可以使用常规的数字签名 PublicKeyVerify 原语对其进行验证,验证者无需知道(也不必关心)签名是通过两个步骤生成的。
将预哈希值视为不透明字节。它的大小固定,并携带了为其计算的密钥的 ID,但其布局是 Tink 的有线格式的一部分,您不应自行解析或构建。如果您要移植 Tink 或需要字节级详细信息,请参阅 Tink 线格式。
在以下情况下,请使用这对原语:
- 签名密钥位于其他位置,例如在 HSM、KMS 中或 RPC 边界后面,并且您不想将完整消息跨越该边界进行传输。
- 消息过大,并且远程签名者强制执行请求大小限制。
如果上述两种情况都不适用,请改用简单的数字签名原语:它更简单,而且更难被滥用。
密钥集
这两个原语选择密钥的方式不同,就像签名和验证对数字签名原语所做的那样:
Prehash.Compute始终使用公开密钥集的主密钥,并在预哈希值中记录该密钥的 ID。它会选择用于生成签名的密钥。SignPrehash.Sign从预哈希值中读取密钥 ID,并使用私钥集中匹配的已启用的密钥进行签名。它指的是跟随他人已做出的选择的一方。如果密钥集中没有已启用的密钥具有该 ID,则调用失败。
SignPrehash 密钥集中的每个密钥都必须具有 ID 要求,否则创建原语会失败。
最低安全保障
- 生成的签名与使用相同密钥类型的 Digital Signature 原语生成的签名具有相同的属性。
- Tink 会在预哈希值前面添加 5 个字节,其中包含一个特殊的预留值以及计算该预哈希值所用的密钥的 ID。
SignPrehash仅使用该密钥对值进行签名。该值是否以加密方式绑定到相应密钥取决于算法;对于外部 Mu ML-DSA,该值是绑定的,请参阅 Tink wire format。 - 消息长度不限。
注意事项
- 签名者无法检查其签名的内容。任何可以调用
SignPrehash的人都可以获取任意已签名的消息,而签名者无法对消息内容应用政策。保护对SignPrehash的访问权限,就像保护对PublicKeySign的访问权限一样。 - 保护传输中的预哈希值。它不是密钥,但如果攻击者能够在传输过程中修改它,就能控制签名的内容。
选择密钥类型
如 RFC 9881 中所述,外部 Mu 模式下的 ML-DSA 是 Tink 支持的唯一用于 Prehash 和 SignPrehash 的算法。您应将标准 ML-DSA 密钥与这些原语搭配使用。
对于大多数使用场景,我们建议使用 ML_DSA_65。
密钥必须具有 ID 要求,因为每个预哈希值都以包含相应密钥 ID 的前缀开头。接受以下变体:
TINK- 生成的签名以 Tink 常规的 5 字节输出前缀开头。NO_PREFIX_WITH_PREHASH_ID- 生成的签名不带任何输出前缀,而密钥仍具有预哈希值所需的 ID。
不支持使用 NO_PREFIX(原始)变体的密钥,因为它们没有密钥 ID。
示例
以下示例分两步对消息进行签名,然后使用普通的数字签名原语验证生成的签名。
为便于阅读,它们显示为一个代码块。在实际部署中,这两个步骤在不同的位置运行;如需逐步演练,请参阅我想使用远程签名者对数据进行签名。
C++
#include "tink/keyset_handle.h" #include "tink/public_key_verify.h" #include "tink/signature/config_2026.h" #include "tink/signature/prehash.h" #include "tink/signature/sign_prehash.h" using ::crypto::tink::ConfigSignature2026; using ::crypto::tink::Prehash; using ::crypto::tink::PublicKeyVerify; using ::crypto::tink::SignPrehash; // 1. Wherever the message is: compute the prehash value. This needs only // the public keyset. absl::StatusOr<std::unique_ptr<Prehash>> prehasher = public_handle.GetPrimitive<Prehash>(ConfigSignature2026()); if (!prehasher.ok()) return prehasher.status(); absl::StatusOr<std::string> prehash = (*prehasher)->Compute(message); if (!prehash.ok()) return prehash.status(); // 2. Wherever the private key is: turn the prehash value into a signature. absl::StatusOr<std::unique_ptr<SignPrehash>> signer = private_handle.GetPrimitive<SignPrehash>(ConfigSignature2026()); if (!signer.ok()) return signer.status(); absl::StatusOr<std::string> signature = (*signer)->Sign(*prehash); if (!signature.ok()) return signature.status(); // 3. Anywhere: verify the signature over the original message. absl::StatusOr<std::unique_ptr<PublicKeyVerify>> verifier = public_handle.GetPrimitive<PublicKeyVerify>(ConfigSignature2026()); if (!verifier.ok()) return verifier.status(); absl::Status verified = (*verifier)->Verify(*signature, message);
Go
import ( "github.com/tink-crypto/tink-go/v2/signature" "github.com/tink-crypto/tink-go/v2/signprehash" ) // 1. Wherever the message is: compute the prehash value. This needs only // the public keyset. prehasher, err := signprehash.NewPrehash(publicHandle) if err != nil { return err } prehash, err := prehasher.ComputePrehash(message) if err != nil { return err } // 2. Wherever the private key is: turn the prehash value into a signature. signer, err := signprehash.NewPrehashSigner(privateHandle) if err != nil { return err } sig, err := signer.SignPrehash(prehash) if err != nil { return err } // 3. Anywhere: verify the signature over the original message. verifier, err := signature.NewVerifier(publicHandle) if err != nil { return err } if err := verifier.Verify(sig, message); err != nil { return err }
相关主题
- 我想使用远程签名者对数据进行签名 - 分步演示两步流程。
- 数字签名 - 当签名者可以查看邮件时,只需一步即可完成签名和验证。
- 不同语言支持的原始密钥类型
- Tink 线格式 - 预哈希值的字节级布局,用于将 Tink 移植到其他语言。