Prehash i SignPrehash

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

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

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

Traktuj wartość wstępnego haszowania jako nieprzejrzyste 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 Tink.

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

  • Klucz podpisu znajduje się w innym miejscu, np. w module HSM, w KMS lub za granicą RPC, i 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 żadna z tych sytuacji nie ma miejsca, użyj prostego elementu Digital Signature. Jest on prostszy i trudniej go niewłaściwie użyć.

Zbiory kluczy

Te 2 typy kluczy wybierają klucze w inny sposób, podobnie jak w przypadku podpisywania i weryfikacji w typie 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 już 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 algorytmu 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ę

  • Osoba podpisująca nie może sprawdzić, co podpisuje. Każda osoba, która może dzwonić 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_65.

Klucz musi mieć wymaganie dotyczące identyfikatora, ponieważ każda wartość wstępnie zaszyfrowana 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.

Przykłady

W przykładach poniżej podpisujemy wiadomość w 2 krokach, a następnie weryfikujemy uzyskany podpis za pomocą zwykłego elementu Digital Signature.

Dla większej czytelności są one wyświetlane jako jeden blok. W prawdziwym wdrożeniu te 2 kroki są wykonywane w różnych miejscach. Szczegółowe instrukcje znajdziesz w artykule Chcę podpisać dane za pomocą zdalnego sygnatariusza.

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
}