Ich möchte Daten mit einem Remote-Signer signieren

Wir empfehlen die Primitiven Prehash und SignPrehash mit einem ML_DSA_65-Schlüssel, wenn sich der privaten Schlüssel an einem Ort befindet, an dem die Nachricht nicht gesendet werden kann.

Manchmal kann oder sollte die Partei, die den Signaturschlüssel besitzt, die Nachricht selbst nicht empfangen: Der Schlüssel befindet sich in einem HSM oder KMS oder die Nachricht ist größer als die Größenbeschränkung für Anfragen des Unterzeichners.

Die Primitiven „Prehash“ und „SignPrehash“ lösen dieses Problem, indem die Signierung in zwei Schritte unterteilt wird. Sie berechnen einen kurzen Prehash-Wert für die Nachricht, indem Sie nur den öffentlichen Schlüssel verwenden, und senden ihn an den Unterzeichner. Der Unterzeichner wandelt sie mit dem privaten Schlüssel in eine Signatur um, ohne die Nachricht jemals zu sehen. Bei ML-DSA im externen MU-Modus, dem Algorithmus, den Tink unterstützt, hat der Prehash-Wert unabhängig von der Größe der Nachricht eine Länge von 69 Byte.

Die zurückgegebene Signatur ist eine normale Signatur für die ursprüngliche Nachricht. Prüfer verwenden das reguläre Primitive Digitale Signatur und müssen nicht wissen, dass zwei Schritte erforderlich waren.

Hinweis

Erstellen Sie einen ML-DSA-Schlüsselsatz, dessen Schlüssel eine ID-Anforderung haben – entweder die TINK-Variante oder NO_PREFIX_WITH_PREHASH_ID, wenn Sie kein Ausgabepräfix für die resultierende Signatur wünschen. Geben Sie dem Unterzeichner den privaten Schlüsselsatz und der Prehashing-Seite den entsprechenden öffentlichen Schlüsselsatz. Der Unterzeichner sollte für jede Schlüssel-ID, die die Prehashing-Seite erstellen kann, einen aktivierten Schlüssel haben. Weitere Informationen finden Sie unter Keysets.

Schritt 1: Prehash-Wert berechnen

Führen Sie diesen Befehl überall dort aus, wo sich die Nachricht befindet. Es wird nur das öffentliche Keyset benötigt.

C++

#include "tink/keyset_handle.h"
#include "tink/signature/config_2026.h"
#include "tink/signature/prehash.h"

absl::StatusOr<std::unique_ptr<crypto::tink::Prehash>> prehasher =
    public_handle.GetPrimitive<crypto::tink::Prehash>(
        crypto::tink::ConfigSignature2026());
if (!prehasher.ok()) return prehasher.status();

absl::StatusOr<std::string> prehash = (*prehasher)->Compute(message);
if (!prehash.ok()) return prehash.status();

Ok

import "github.com/tink-crypto/tink-go/v2/signprehash"

prehasher, err := signprehash.NewPrehash(publicHandle)
if err != nil {
    return err
}
prehash, err := prehasher.ComputePrehash(message)
if err != nil {
    return err
}

Schritt 2: Prehash-Wert an den Unterzeichner senden

Senden Sie den Prehash-Wert an die Stelle, an der sich der private Schlüssel befindet. Es ist nicht geheim, aber Sie müssen seine Integrität während der Übertragung schützen: Ein Angreifer, der es während der Übertragung ändern kann, steuert, was signiert wird.

Schritt 3: Prehash-Wert signieren

Führen Sie diesen Befehl dort aus, wo sich der private Schlüssel befindet.

C++

#include "tink/keyset_handle.h"
#include "tink/signature/config_2026.h"
#include "tink/signature/sign_prehash.h"

absl::StatusOr<std::unique_ptr<crypto::tink::SignPrehash>> signer =
    private_handle.GetPrimitive<crypto::tink::SignPrehash>(
        crypto::tink::ConfigSignature2026());
if (!signer.ok()) return signer.status();

absl::StatusOr<std::string> signature = (*signer)->Sign(prehash);
if (!signature.ok()) return signature.status();

Ok

import "github.com/tink-crypto/tink-go/v2/signprehash"

signer, err := signprehash.NewPrehashSigner(privateHandle)
if err != nil {
    return err
}
sig, err := signer.SignPrehash(prehash)
if err != nil {
    return err
}

Schritt 4: Signatur überprüfen

Die Überprüfung erfolgt über den normalen Ablauf für digitale Signaturen für die Originalnachricht und nicht für den Prehash-Wert.

C++

absl::StatusOr<std::unique_ptr<crypto::tink::PublicKeyVerify>> verifier =
    public_handle.GetPrimitive<crypto::tink::PublicKeyVerify>(
        crypto::tink::ConfigSignature2026());
if (!verifier.ok()) return verifier.status();

absl::Status verified = (*verifier)->Verify(signature, message);

Ok

import "github.com/tink-crypto/tink-go/v2/signature"

verifier, err := signature.NewVerifier(publicHandle)
if err != nil {
    return err
}
if err := verifier.Verify(sig, message); err != nil {
    return err
}

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-PublicKeyVerify-Primitive überprüft. Prüfer müssen nicht wissen oder sich darum kümmern, dass die Signatur in zwei Schritten erstellt wurde.

Den Prehash-Wert als undurchsichtige Bytes behandeln. 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, in einem KMS oder hinter einer RPC-Grenze, und Sie möchten nicht die gesamte Nachricht über diese Grenze hinweg übertragen.
  • Die Nachricht ist groß und der Remote-Signierer erzwingt eine Beschränkung der Anfragengröße.

Wenn keines der beiden zutrifft, verwenden Sie stattdessen das einfache Primitive Digital Signature. Es ist einfacher und es ist schwieriger, es zu missbrauchen.

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ären Schlü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 passenden 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-Schlüsselsatz 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 Digital Signature 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. Bei External Mu ML-DSA ist das der Fall (siehe Tink-Wire-Format).
  • Nachrichten können eine beliebige Länge haben.

Worauf Sie achten sollten

  • Der Unterzeichner kann nicht prüfen, was er unterzeichnet. 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 Standard-ML-DSA-Schlüssel mit diesen Primitiven verwenden.

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

Für den Schlüssel muss eine ID-Anforderung gelten, 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 (roh) verwendet wird, werden nicht unterstützt, da sie keine Schlüssel-ID haben.