Voglio firmare i dati con un firmatario remoto

Ti consigliamo di utilizzare le primitive Prehash e SignPrehash con una chiave ML_DSA_65 quando la chiave privata si trova in un luogo da cui non è possibile inviare il messaggio.

A volte la parte che detiene la chiave di firma non può o non deve ricevere il messaggio stesso: la chiave si trova in un HSM o un KMS oppure il messaggio è più grande del limite di dimensioni della richiesta del firmatario.

Le primitive Prehash e SignPrehash risolvono questo problema dividendo la firma in due passaggi. Calcoli un valore prehash breve in cui si trova il messaggio, utilizzando solo la chiave pubblica, e lo invii al firmatario. Il firmatario la trasforma in una firma utilizzando la chiave privata, senza mai vedere il messaggio. Con ML-DSA in modalità External Mu, l'algoritmo Tink supporta qui il valore pre-hash di 69 byte, indipendentemente dalle dimensioni del messaggio.

La firma che ricevi è una firma ordinaria sul messaggio originale: i verificatori utilizzano la primitiva Firma digitale standard e non devono sapere che sono stati coinvolti due passaggi.

Prima di iniziare

Crea un set di chiavi ML-DSA le cui chiavi hanno un requisito di ID: la variante TINK o NO_PREFIX_WITH_PREHASH_ID se non vuoi un prefisso di output nella firma risultante. Fornisci al firmatario il set di chiavi private e al lato di pre-hashing il set di chiavi pubbliche corrispondente. Il firmatario deve avere una chiave abilitata per ogni ID chiave che il lato di pre-hashing può produrre. Vedi Keyset.

Passaggio 1: calcola il valore pre-hash

Esegui questa operazione ovunque si trovi il messaggio. È necessario solo il set di chiavi pubbliche.

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
}

Passaggio 2: invia il valore pre-hash al firmatario

Invia il valore prehash a chiunque detenga la chiave privata. Non è segreto, ma devi proteggerne l'integrità durante il transito: un malintenzionato che può modificarlo durante il volo controlla ciò che viene firmato.

Passaggio 3: firma il valore pre-hash

Esegui questo comando ovunque si trovi la chiave privata.

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
}

Passaggio 4: verifica la firma

La verifica è il flusso ordinario della firma digitale sul messaggio originale, non sul valore 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
}

Prehash e SignPrehash

Le primitive Prehash e SignPrehash dividono il calcolo di una firma digitale in due passaggi:

  1. Prehash richiede solo la chiave pubblica. Trasforma un messaggio di lunghezza arbitraria in un valore pre-hash breve e di dimensioni fisse.
  2. SignPrehash richiede la chiave privata. Trasforma un valore pre-hash in una firma.

La firma risultante è una firma ordinaria sul messaggio originale. La verifica viene eseguita con la primitiva firma digitale PublicKeyVerify e i verificatori non devono sapere, né interessarsi, che la firma è stata prodotta in due passaggi.

Considera il valore pre-hash come byte opachi. Ha una dimensione fissa e contiene l'ID della chiave per cui è stato calcolato, ma il suo layout fa parte del formato di trasferimento di Tink e non devi analizzarlo o costruirne uno autonomamente. Se stai trasferendo Tink o hai bisogno dei dettagli a livello di byte, consulta Formato di Tink.

Utilizza questa coppia di primitive quando:

  • La chiave di firma si trova altrove, ad esempio in un modulo HSM, in un KMS o dietro un limite RPC, e non vuoi inviare l'intero messaggio oltre questo limite.
  • Il messaggio è grande e il firmatario remoto impone un limite alle dimensioni della richiesta.

Se nessuno di questi casi si applica, utilizza la primitiva Firma digitale: è più semplice e più difficile da usare in modo improprio.

Set di chiavi

Le due primitive selezionano le chiavi in modo diverso, proprio come la firma e la verifica per la primitiva Firma digitale:

  • Prehash.Compute utilizza sempre la chiave primaria del keyset pubblico e registra l'ID della chiave nel valore pre-hash. È la parte che sceglie con quale chiave verrà creata la firma.
  • SignPrehash.Sign legge l'ID chiave dal valore pre-hash e firma con la chiave abilitata corrispondente del set di chiavi private. È la parte che segue una scelta già fatta da qualcun altro. Se nessuna chiave abilitata nel keyset ha questo ID, la chiamata non va a buon fine.

Ogni chiave in un keyset SignPrehash deve avere un requisito ID, altrimenti la creazione della primitiva non va a buon fine.

Garanzie di sicurezza minime

  • La firma risultante ha le stesse proprietà di una firma prodotta dalla primitiva Firma digitale con lo stesso tipo di chiave.
  • Tink antepone al valore prehash 5 byte contenenti un valore speciale riservato e l'ID della chiave per cui è stato calcolato. SignPrehash firma il valore solo con questa chiave. Se il valore è associato crittograficamente a quella chiave dipende dall'algoritmo; per External Mu ML-DSA lo è, vedi Formato di Tink.
  • I messaggi possono avere una lunghezza arbitraria.

Aspetti a cui prestare attenzione

  • Il firmatario non può ispezionare ciò che firma. Chiunque possa chiamare SignPrehash può ottenere la firma di un messaggio arbitrario e il firmatario non ha modo di applicare un criterio ai contenuti del messaggio. Proteggi l'accesso a SignPrehash esattamente come proteggeresti l'accesso a PublicKeySign.
  • Proteggere il valore pre-hash in transito. Non è un segreto, ma un malintenzionato che può modificarlo durante il trasferimento controlla cosa viene firmato.

Scegli un tipo di chiave

ML-DSA in modalità External Mu, come descritto in RFC 9881, è l'unico algoritmo supportato da Tink per Prehash e SignPrehash. Devi utilizzare una chiave ML-DSA standard con queste primitive.

Consigliamo ML_DSA_65 per la maggior parte dei casi d'uso.

La chiave deve avere un requisito di ID, perché ogni valore pre-hash inizia con un prefisso che contiene l'ID della chiave per cui è stato calcolato. Sono accettate le seguenti varianti:

  • TINK: la firma risultante inizia con il prefisso di output di 5 byte abituale di Tink.
  • NO_PREFIX_WITH_PREHASH_ID: la firma risultante non ha un prefisso di output, mentre la chiave ha ancora l'ID necessario per il valore prehash.

Le chiavi che utilizzano la variante NO_PREFIX (non elaborata) non sono supportate perché non hanno un ID chiave.