Chcę podpisać dane za pomocą zdalnego podpisu

Zalecamy używanie elementów Prehash i SignPrehash z kluczem ML_DSA_44, gdy klucz prywatny znajduje się w miejscu, z którego nie można wysłać wiadomości.

Czasami podmiot, który ma klucz podpisywania, nie może lub nie powinien otrzymać samej wiadomości: klucz znajduje się w module HSM lub KMS albo wiadomość jest większa niż limit rozmiaru żądania osoby podpisującej.

Funkcje Prehash i SignPrehash rozwiązują ten problem, dzieląc podpisywanie na 2 etapy. Obliczasz krótką wartość przed haszowaniem w miejscu, w którym znajduje się wiadomość, używając tylko klucza publicznego, i wysyłasz ją do osoby podpisującej. Podpisujący przekształca go w podpis za pomocą klucza prywatnego, nie widząc wiadomości. W przypadku ML-DSA w trybie External Mu (algorytm obsługiwany przez Tink) wartość prehash ma 69 bajtów niezależnie od rozmiaru wiadomości.

Otrzymany podpis jest zwykłym podpisem oryginalnej wiadomości: weryfikatorzy używają zwykłego prymitywu podpisu cyfrowego i nie muszą wiedzieć, że proces składał się z 2 etapów.

Zanim zaczniesz

Utwórz zestaw kluczy ML-DSA, którego klucze mają wymaganie dotyczące identyfikatora – wariant TINK lub NO_PREFIX_WITH_PREHASH_ID, jeśli nie chcesz, aby wynikowy podpis miał prefiks wyjściowy. Przekaż osobie podpisującej zbiór kluczy prywatnych, a osobie przeprowadzającej wstępne haszowanie – odpowiadający mu zbiór kluczy publicznych. Osoba podpisująca powinna mieć włączony klucz dla każdego identyfikatora klucza, który może wygenerować strona wstępnego haszowania. Więcej informacji znajdziesz w sekcji Zbiory kluczy.

Krok 1. Oblicz wartość przed haszowaniem

Uruchom tę funkcję w miejscu, w którym znajduje się wiadomość. Wymaga ona tylko zbioru kluczy publicznych.

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();

Go

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
}

Python

from tink import signature

# 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 (such as PublicKeyVerify in
# Step 4).
prehasher = public_handle.primitive(signature.Prehash)
prehash = prehasher.compute(message)

Krok 2. Prześlij wartość wstępnego haszowania do osoby podpisującej

Wyślij wartość wstępnego haszowania do podmiotu, który ma klucz prywatny. Nie jest to tajemnica, ale musisz chronić jego integralność podczas przesyłania: atakujący, który może go zmodyfikować w trakcie przesyłania, kontroluje to, co zostanie podpisane.

Krok 3. Podpisz wartość prehash

Uruchom to polecenie w miejscu, w którym znajduje się klucz prywatny.

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();

Go

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
}

Python

from tink import signature

# 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 (such as PublicKeyVerify in
# Step 4).
signer = private_handle.primitive(signature.SignPrehash)
sig = signer.sign(prehash)

Krok 4. Sprawdź podpis

Weryfikacja to zwykły proces podpisu cyfrowego w przypadku oryginalnej wiadomości, a nie wartości prehash.

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);

Go

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
}

Python

from tink import signature

signature.register()

verifier = public_handle.primitive(signature.PublicKeyVerify)
verifier.verify(sig, message)

Prehash i SignPrehash

Funkcje Prehash i SignPrehash dzielą obliczanie podpisu cyfrowego na 2 etapy:

  1. Prehash wymaga tylko publicznego klucza. Przekształca wiadomość o dowolnej długości w krótką wartość wstępnego haszowania o stałym rozmiarze.
  2. Funkcja SignPrehash wymaga klucza prywatnego. Przekształca ona wstępnie zaszyfrowaną wartość w sygnaturę.

Wynikowy podpis jest zwykłym podpisem oryginalnej wiadomości. Weryfikujesz go za pomocą zwykłego prymitywu Digital Signature PublicKeyVerify, a weryfikatorzy nie muszą wiedzieć ani się przejmować tym, że podpis został utworzony w 2 krokach.

Traktuj wartość przed haszowaniem jako nieprzezroczyste bajty. Ma stały rozmiar i zawiera identyfikator klucza, dla którego został obliczony, ale jego układ jest częścią formatu przesyłania Tink, więc nie należy go samodzielnie analizować ani tworzyć. Jeśli przenosisz Tink lub potrzebujesz szczegółów na poziomie bajtów, zapoznaj się z formatem przesyłania Tink.

Używaj tej pary elementów podstawowych, gdy:

  • Klucz podpisu znajduje się w innym miejscu, np. w module HSM, w usłudze KMS lub za granicą RPC, a Ty nie chcesz przesyłać pełnej wiadomości przez tę granicę.
  • Wiadomość jest duża, a zdalny podpisujący wymusza limit rozmiaru żądania.

Jeśli żaden z tych przypadków nie ma zastosowania, użyj prostego elementu Digital Signature. Jest on prostszy i trudniej go niewłaściwie użyć.

Zbiory kluczy

Te 2 typy podstawowe wybierają klucze w inny sposób, podobnie jak w przypadku podpisywania i weryfikacji w typie podstawowym Podpis cyfrowy:

  • Prehash.Compute zawsze używa klucza podstawowego w zbiorze kluczy publicznych i zapisuje identyfikator tego klucza w wartości przed haszowaniem. To strona, która wybiera klucz, za pomocą którego zostanie utworzony podpis.
  • SignPrehash.Sign odczytuje identyfikator klucza z wartości wstępnego haszowania i podpisuje go za pomocą pasującego włączonego klucza z zestawu kluczy prywatnych. Jest to strona, która podąża za wyborem dokonanym przez inną osobę. Jeśli w zestawie kluczy nie ma włączonego klucza z tym identyfikatorem, wywołanie się nie powiedzie.

Każdy klucz w zestawie kluczy SignPrehash musi mieć wymaganie dotyczące identyfikatora, w przeciwnym razie utworzenie elementu podstawowego się nie powiedzie.

Minimalne gwarancje bezpieczeństwa

  • Wynikowy podpis ma takie same właściwości jak podpis wygenerowany przez funkcję pierwotną Digital Signature z tym samym typem klucza.
  • Tink dodaje do wartości przed haszowaniem 5 bajtów zawierających specjalną wartość zarezerwowaną i identyfikator klucza, dla którego została obliczona. SignPrehash podpisuje wartość tylko tym kluczem. To, czy wartość jest kryptograficznie powiązana z tym kluczem, zależy od algorytmu. W przypadku zewnętrznego Mu ML-DSA tak jest. Więcej informacji znajdziesz w formacie przesyłania Tink.
  • Wiadomości mogą mieć dowolną długość.

Na co zwracać uwagę

  • Sygnatariusz nie może sprawdzić, co podpisuje. Każdy, kto może zadzwonić na numer SignPrehash, może uzyskać podpis dowolnej wiadomości, a osoba podpisująca nie ma możliwości zastosowania zasad do treści wiadomości. Chroń dostęp do SignPrehash dokładnie tak samo, jak chronisz dostęp do PublicKeySign.
  • Chroń wartość przed zaszyfrowaniem podczas przesyłania. Nie jest to tajne, ale atakujący, który może je zmodyfikować w trakcie przesyłania, kontroluje, co zostanie podpisane.

Wybierz typ klucza

ML-DSA w trybie External Mu, zgodnie z opisem w RFC 9881, to jedyny algorytm obsługiwany przez Tink w przypadku funkcji Prehash i SignPrehash. W przypadku tych elementów pierwotnych należy używać standardowego klucza ML-DSA.

W większości przypadków zalecamy używanie ML_DSA_44.

Klucz musi mieć wymaganie dotyczące identyfikatora, ponieważ każda wartość wstępnego haszowania zaczyna się od prefiksu zawierającego identyfikator klucza, dla którego została obliczona. Akceptujemy te warianty:

  • TINK – wynikowy podpis zaczyna się od zwykłego 5-bajtowego prefiksu wyjściowego Tink.
  • NO_PREFIX_WITH_PREHASH_ID – wynikowy podpis nie zawiera prefiksu wyjściowego, a klucz nadal ma identyfikator, którego potrzebuje wartość przed haszowaniem.

Klucze, które używają wariantu NO_PREFIX (surowego), nie są obsługiwane, ponieważ nie mają identyfikatora klucza.