Las primitivas Prehash y SignPrehash dividen el cálculo de una firma digital en dos pasos:
- Prehash solo necesita la clave pública. Convierte un mensaje de longitud arbitraria en un valor previo al hash corto y de tamaño fijo.
- 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 Firma digital PublicKeyVerify 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 transferencia 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 básicos 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 deseas 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 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.Computesiempre usa la clave primaria 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.Signlee el ID de 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 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 agrega un prefijo al valor previo al hash con 5 bytes que contienen un valor reservado especial y el ID de la clave para la que se calculó.
SignPrehashfirma el valor solo con esa clave. Si el valor está vinculado criptográficamente a esa clave depende del algoritmo; para External Mu ML-DSA, 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
SignPrehashpuede obtener un mensaje arbitrario firmado, y el firmante no tiene forma de aplicar una política al contenido del mensaje. Protege el acceso aSignPrehashexactamente de la misma manera en que protegerías el acceso aPublicKeySign. - Protege el valor previo al hash en tránsito. No es secreto, pero un atacante que puede modificarlo en vuelo controla lo que se firma.
Elige un tipo de llave
El ML-DSA en modo External Mu, como se describe en RFC 9881, es el único algoritmo que admite Tink para Prehash y SignPrehash. Debes usar una clave ML-DSA estándar con estos elementos primitivos.
Recomendamos ML_DSA_65 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);
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 }
Temas relacionados
- I want to sign data with a remote signer: Una guía paso a paso del flujo de dos pasos.
- Firma digital: Firma y verifica en un solo paso, cuando el firmante puede ver el mensaje.
- Tipos de claves primitivas admitidos por idioma
- Formato de cable de Tink: Es el diseño a nivel de bytes de un valor previo al hash, para portar Tink a otro lenguaje.