Prehash und SignPrehash

Die Primitiven „Prehash“ und „SignPrehash“ teilen die Berechnung einer digitalen Signatur in zwei Schritte auf:

  1. Für Prehash ist nur der öffentliche Schlüssel erforderlich. Sie wandelt eine Nachricht beliebiger Länge in einen kurzen Prehash-Wert mit fester Größe um.
  2. Für SignPrehash ist der private-Schlüssel erforderlich. Sie wandelt einen Prehash-Wert in eine Signatur um.

Die resultierende Signatur ist eine gewöhnliche Signatur für die ursprüngliche Nachricht. Sie wird mit dem regulären Digital Signature-Primitiv PublicKeyVerify überprüft. Prüfer müssen nicht wissen oder sich darum kümmern, dass die Signatur in zwei Schritten erstellt wurde.

Behandeln Sie den Prehash-Wert als undurchsichtige Bytes. Es hat eine feste Größe und enthält die ID des Schlüssels, für den es berechnet wurde. Das Layout ist jedoch Teil des Wire-Formats von Tink. Sie sollten es nicht selbst parsen oder erstellen. Wenn Sie Tink portieren oder Details auf Byte-Ebene benötigen, lesen Sie den Abschnitt Tink-Wire-Format.

Verwenden Sie dieses Primitive-Paar, wenn:

  • Der Signierschlüssel befindet sich an einem anderen Ort, z. B. in einem HSM, einem KMS oder hinter einer RPC-Grenze, und Sie möchten die vollständige Nachricht nicht über diese Grenze hinweg übertragen.
  • Die Nachricht ist groß und der Remote-Signierer erzwingt eine Beschränkung der Anfragengröße.

Wenn keines dieser Szenarien zutrifft, verwenden Sie stattdessen das einfache Digital Signature-Primitive, da es einfacher und schwieriger zu missbrauchen ist.

Schlüsselsätze

Die beiden Primitiven wählen Schlüssel unterschiedlich aus, so wie das Signieren und die Überprüfung für das Digital Signature-Primitiv funktionieren:

  • Prehash.Compute verwendet immer den Primärschlüssel des öffentlichen Schlüsselsatzes und zeichnet die ID dieses Schlüssels im Prehash-Wert auf. Es ist die Seite, die auswählt, mit welchem Schlüssel die Signatur erstellt wird.
  • SignPrehash.Sign liest die Schlüssel-ID aus dem Prehash-Wert und signiert mit dem entsprechenden aktivierten Schlüssel des privaten Schlüsselsets. Es ist die Seite, die einer Entscheidung folgt, die jemand anderes bereits getroffen hat. Wenn kein aktivierter Schlüssel im Keyset diese ID hat, schlägt der Aufruf fehl.

Jeder Schlüssel in einem SignPrehash-Keyset muss eine ID-Anforderung haben. Andernfalls schlägt das Erstellen des Primitivs fehl.

Minimale Sicherheitsgarantien

  • Die resultierende Signatur hat dieselben Eigenschaften wie eine Signatur, die mit dem Primitiv Digitale Signatur mit demselben Schlüsseltyp erstellt wurde.
  • Tink stellt dem Prehash-Wert 5 Bytes voran, die einen speziellen reservierten Wert und die ID des Schlüssels enthalten, für den er berechnet wurde. SignPrehash signiert den Wert nur mit diesem Schlüssel. Ob der Wert kryptografisch an diesen Schlüssel gebunden ist, hängt vom Algorithmus ab. Für External Mu ML-DSA ist dies der Fall. Weitere Informationen finden Sie unter Tink-Wire-Format.
  • Nachrichten können beliebig lang sein.

Wichtige Hinweise

  • Der Unterzeichner kann nicht prüfen, was er unterschreibt. Jeder, der SignPrehash anrufen kann, kann eine beliebige Nachricht signieren lassen. Der Unterzeichner hat keine Möglichkeit, eine Richtlinie auf den Inhalt der Nachricht anzuwenden. Schützen Sie den Zugriff auf SignPrehash genauso wie den Zugriff auf PublicKeySign.
  • Prehash-Wert bei der Übertragung schützen Es ist nicht geheim, aber ein Angreifer, der es während der Übertragung ändern kann, steuert, was signiert wird.

Schlüsseltyp auswählen

ML-DSA im External Mu-Modus, wie in RFC 9881 beschrieben, ist der einzige Algorithmus, den Tink für Prehash und SignPrehash unterstützt. Sie sollten einen standardmäßigen ML-DSA-Schlüssel mit diesen Primitiven verwenden.

Wir empfehlen ML_DSA_44 für die meisten Anwendungsfälle.

Der Schlüssel muss eine ID-Anforderung haben, da jeder Prehash-Wert mit einem Präfix beginnt, das die ID des Schlüssels enthält, für den er berechnet wurde. Die folgenden Varianten sind zulässig:

  • TINK – Die resultierende Signatur beginnt mit dem üblichen 5‑Byte-Ausgabepräfix von Tink.
  • NO_PREFIX_WITH_PREHASH_ID: Die resultierende Signatur hat kein Ausgabepräfix, während der Schlüssel weiterhin die ID hat, die für den Prehash-Wert erforderlich ist.

Schlüssel, für die die NO_PREFIX-Variante (raw) verwendet wird, werden nicht unterstützt, da sie keine Schlüssel-ID haben.

Beispiele

In den folgenden Beispielen wird eine Nachricht in zwei Schritten signiert und die resultierende Signatur dann mit dem normalen Digital Signature-Primitive überprüft.

Zur besseren Lesbarkeit werden sie als ein Block dargestellt. In einer echten Bereitstellung werden die beiden Schritte an unterschiedlichen Stellen ausgeführt. Eine detaillierte Anleitung finden Sie unter I want to sign data with a remote signer.

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)