Prehash 和 SignPrehash

Prehash 和 SignPrehash 基本體會將數位簽章的運算分成兩個步驟:

  1. 前置雜湊只需要公開金鑰。可將任意長度的訊息轉換為固定大小的前置雜湊值。
  2. 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 線路格式。
  • 訊息長度不限。

注意事項

  • 簽署者無法檢查簽署內容。任何可以撥打電話給 SignPrehash 的人都能取得任意訊息的簽章,而簽署者無法對訊息內容套用政策。保護 SignPrehash存取權的方式與保護 PublicKeySign 存取權的方式完全相同。
  • 保護傳輸中的前置雜湊值。這不是密鑰,但攻擊者可以在傳輸期間修改這個值,進而控制簽署內容。

選擇車鑰類型

如 RFC 9881 所述,外部 Mu 模式中的 ML-DSA 是 Tink 支援的唯一演算法,適用於 Prehash 和 SignPrehash。您應搭配這些基本型別使用標準 ML-DSA 金鑰。

我們建議在大多數用途中使用 ML_DSA_44。

索引鍵必須符合 ID 規定,因為每個前置雜湊值開頭都有前置字串,其中包含計算該值所用的索引鍵 ID。接受的變體如下:

  • TINK - 產生的簽章開頭為 Tink 的一般 5 位元組輸出前置字元。
  • NO_PREFIX_WITH_PREHASH_ID - 產生的簽章不含輸出前置字元,但金鑰仍有前置雜湊值所需的 ID。

系統不支援使用 NO_PREFIX (原始) 變體的金鑰,因為這類金鑰沒有金鑰 ID。

範例

下列範例會分兩步驟簽署訊息,然後使用一般 Digital Signature 基本類型驗證產生的簽章。

為方便閱讀,這些項目會顯示為一個區塊。在實際部署作業中,這兩個步驟會在不同位置執行;如需逐步導覽,請參閱「我想使用遠端簽署者簽署資料」。

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
}

Python

from tink import signature

# Needed for PublicKeyVerify in step 3. Due to the internal structure of
# Tink Python, Prehash and SignPrehash are an exception where calling
# signature.register() is not necessary; do not expect the same for other
# primitives.
signature.register()

# 1. Wherever the message is: compute the prehash value. This needs only
#    the public keyset.
prehasher = public_handle.primitive(signature.Prehash)
prehash = prehasher.compute(message)

# 2. Wherever the private key is: turn the prehash value into a signature.
signer = private_handle.primitive(signature.SignPrehash)
sig = signer.sign(prehash)

# 3. Anywhere: verify the signature over the original message.
verifier = public_handle.primitive(signature.PublicKeyVerify)
verifier.verify(sig, message)