Prehash 和 SignPrehash

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

  1. 前置雜湊只需要公開金鑰。可將任意長度的訊息轉換為固定大小的簡短前置雜湊值
  2. SignPrehash 需要私密金鑰。將前置雜湊值轉換為簽章。

產生的簽章是原始訊息的普通簽章。您可以使用一般的數位簽章 PublicKeyVerify 基本類型進行驗證,驗證者不需要知道 (或在意) 簽章是分兩步驟產生。

將前置雜湊值視為不透明位元組。這項值的大小固定,且會攜帶計算所用金鑰的 ID,但其版面配置屬於 Tink 的線路格式,您不應自行剖析或建構。如要移植 Tink 或需要位元層級的詳細資料,請參閱「Tink 傳輸格式」。

在下列情況下,請使用這對基元:

  • 簽署金鑰位於其他位置,例如 HSM、KMS 或 RPC 邊界後方,且您不想跨越該邊界傳送完整訊息。
  • 訊息過大,且遠端簽署者強制執行要求大小限制。

如果上述情況均不適用,請改用簡單的數位簽章基本類型,因為這種基本類型較簡單,也較不容易誤用。

金鑰組

這兩個基本型別會以不同方式選取金鑰,就像簽署和驗證會針對 Digital Signature 基本型別執行一樣:

  • Prehash.Compute 一律會使用公開金鑰組的主要金鑰,並在預先雜湊值中記錄該金鑰的 ID。這個角色會選擇用來建立簽章的金鑰。
  • SignPrehash.Sign 會從前置雜湊值讀取金鑰 ID,並使用私密金鑰集中相符的已啟用金鑰簽署。也就是「跟隨」他人選擇的一方。如果金鑰集中沒有已啟用且具有該 ID 的金鑰,呼叫就會失敗。

SignPrehash 鍵集中的每個金鑰都必須有 ID 需求,否則建立基本體的作業會失敗。

安全性保障不足

  • 產生的簽章與數位簽章基本體使用相同金鑰類型產生的簽章具有相同屬性。
  • Tink 會在前置雜湊值加上 5 個位元組,其中包含特殊保留值和計算該值的金鑰 ID。SignPrehash 只會使用該金鑰簽署值。值是否以密碼編譯方式繫結至該金鑰,取決於演算法;如果是 External Mu ML-DSA,則會繫結至該金鑰,請參閱 Tink 線路格式
  • 訊息長度不限。

注意事項

  • 簽署者無法檢查簽署內容。任何可以撥打電話給SignPrehash的人都能取得任意訊息的簽章,而簽署者無法對訊息內容套用政策。保護「SignPrehash」的存取權,就像保護「PublicKeySign」的存取權一樣。
  • 保護傳輸中的前雜湊值。這不是密鑰,但攻擊者可以在傳輸過程中修改這項資訊,進而控制簽署內容。

選擇車鑰類型

RFC 9881 所述,Tink 僅支援 External Mu 模式的 ML-DSA,用於 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
}