Prehash y SignPrehash

Las primitivas Prehash y SignPrehash dividen el cálculo de una firma digital en dos pasos:

  1. Prehash solo necesita la clave pública. Convierte un mensaje de longitud arbitraria en un valor de prehash corto y de tamaño fijo.
  2. SignPrehash necesita la clave privada. Convierte un valor previo al hash en una firma.

La firma resultante es una firma ordinaria sobre el mensaje original. La verificas con la primitiva PublicKeyVerify Firma digital normal, y los verificadores no necesitan saber (ni les importa) que la firma se produjo en dos pasos.

Trata el valor previo al hash como bytes opacos. Tiene un tamaño fijo y contiene el ID de la clave para la que se calculó, pero su diseño es parte del formato de cable de Tink, por lo que no debes analizar ni construir uno por tu cuenta. Si vas a portar Tink o necesitas detalles a nivel de bytes, consulta Formato de cable de Tink.

Usa este par de elementos primitivos en los siguientes casos:

  • La clave de firma se encuentra en otro lugar, por ejemplo, en un HSM, en un KMS o detrás de un límite de RPC, y no quieres enviar el mensaje completo a través de ese límite.
  • El mensaje es grande y el firmante remoto aplica un límite de tamaño de la solicitud.

Si no se aplica ninguna de estas opciones, usa la primitiva Firma digital simple, ya que es más sencilla y es más difícil usarla de forma incorrecta.

Conjuntos de claves

Las dos primitivas seleccionan claves de manera diferente, de la misma forma en que la firma y la verificación lo hacen para la primitiva de Firma Digital:

  • Prehash.Compute siempre usa la clave principal del conjunto de claves públicas y registra el ID de esa clave en el valor previo al hash. Es el lado que elige con qué clave se realizará la firma.
  • SignPrehash.Sign lee el ID de la clave del valor previo al hash y firma con la clave habilitada coincidente del conjunto de claves privadas. Es el lado que sigue una elección que ya hizo otra persona. Si no hay ninguna clave habilitada en el conjunto de claves que tenga ese ID, la llamada falla.

Cada clave de un conjunto de claves de SignPrehash debe tener un requisito de ID. De lo contrario, no se podrá crear el elemento primitivo.

Garantías de seguridad mínimas

  • La firma resultante tiene las mismas propiedades que una firma producida por la primitiva Firma digital con el mismo tipo de clave.
  • Tink antepone al valor previo al hash 5 bytes que contienen un valor reservado especial y el ID de la clave para la que se calculó. SignPrehash firma el valor solo con esa clave. Si el valor está vinculado criptográficamente a esa clave depende del algoritmo; para ML-DSA externo de Mu, sí lo está. Consulta el formato de transferencia de Tink.
  • Los mensajes pueden tener una longitud arbitraria.

Aspectos que se deben tener en cuenta

  • El firmante no puede inspeccionar lo que firma. Cualquier persona que pueda llamar a SignPrehash puede obtener un mensaje arbitrario firmado, y el firmante no tiene forma de aplicar una política al contenido del mensaje. Protege el acceso a SignPrehash exactamente de la misma manera en que protegerías el acceso a PublicKeySign.
  • Protege el valor previo al hash durante la transmisión. No es secreto, pero un atacante que puede modificarlo en vuelo controla lo que se firma.

Elige un tipo de clave

El ML-DSA en modo External Mu, como se describe en el RFC 9881, es el único algoritmo que admite Tink para Prehash y SignPrehash. Debes usar una clave ML-DSA estándar con estas primitivas.

Recomendamos ML_DSA_44 para la mayoría de los casos de uso.

La clave debe tener un requisito de ID, ya que cada valor previo al hash comienza con un prefijo que contiene el ID de la clave para la que se calculó. Se aceptan las siguientes variantes:

  • TINK: La firma resultante comienza con el prefijo habitual de 5 bytes de salida de Tink.
  • NO_PREFIX_WITH_PREHASH_ID: La firma resultante no incluye ningún prefijo de salida, mientras que la clave aún tiene el ID que necesita el valor previo al hash.

No se admiten las claves que usan la variante NO_PREFIX (sin procesar) porque no tienen un ID de clave.

Ejemplos

En los siguientes ejemplos, se firma un mensaje en dos pasos y, luego, se verifica la firma resultante con la primitiva Firma digital común.

Se muestran como un solo bloque para facilitar la lectura. En una implementación real, los dos pasos se ejecutan en lugares diferentes. Consulta Quiero firmar datos con un firmante remoto para obtener una guía paso a paso.

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

Comienza a usarlo

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)