Прехеш и Сигпрехеш

Примитивы Prehash и SignPrehash разделяют вычисление цифровой подписи на два этапа:

  1. Для работы функции prehash требуется только открытый ключ. Она преобразует сообщение произвольной длины в короткое значение prehash фиксированного размера.
  2. Для работы функции SignPrehash необходим закрытый ключ. Она преобразует значение прехеша в подпись.

Полученная подпись представляет собой обычную подпись над исходным сообщением. Вы проверяете её с помощью стандартного примитива Digital Signature PublicKeyVerify , и проверяющим не нужно знать — или интересоваться — тем, что подпись была создана в два этапа.

Рассматривайте значение прехеша как непрозрачные байты. Оно имеет фиксированный размер и содержит идентификатор ключа, для которого было вычислено, но его структура является частью формата передачи данных Tink, и вам не следует самостоятельно его анализировать или создавать. Если вы занимаетесь портированием Tink или вам необходимы подробности на уровне байтов, см. формат передачи данных Tink .

Используйте эту пару примитивов, когда:

  • Ключ подписи хранится в другом месте , например, в HSM, в KMS или за границей RPC, и вам не нужно отправлять полное сообщение через эту границу.
  • Сообщение большое , и удалённый подписант устанавливает ограничение на размер запроса.

Если ни один из этих вариантов не подходит, используйте вместо этого простой примитив цифровой подписи : он проще и его сложнее использовать не по назначению.

Наборы клавиш

Эти два примитива выбирают ключи по-разному, аналогично тому, как это происходит при подписании и проверке для примитива цифровой подписи :

  • Prehash.Compute всегда использует первичный ключ из открытого набора ключей и записывает идентификатор этого ключа в значение prehash. Именно она выбирает , с каким ключом будет создана подпись.
  • SignPrehash.Sign считывает идентификатор ключа из значения прехеша и подписывает с помощью соответствующего включенного ключа из закрытого набора ключей. Это та сторона, которая следует выбору, уже сделанному кем-то другим. Если ни один из включенных ключей в наборе ключей не имеет этого идентификатора, вызов завершается неудачей.

Каждый ключ в наборе ключей SignPrehash должен иметь идентификатор (ID), иначе создание примитива завершится неудачей.

Минимальные гарантии безопасности

  • Полученная подпись обладает теми же свойствами, что и подпись, созданная с помощью примитива «Цифровая подпись» с тем же типом ключа.
  • Tink добавляет к значению прехеша 5 байтов, содержащих специальное зарезервированное значение и идентификатор ключа, для которого оно было вычислено. SignPrehash подписывает значение только этим ключом. Криптографически ли привязано значение к этому ключу, зависит от алгоритма; для External Mu ML-DSA это так, см. формат передачи Tink .
  • Сообщения могут иметь произвольную длину.

На что следует обратить внимание

  • Подписывающая сторона не может проверить, что именно она подписывает. Любой, кто может вызвать SignPrehash может подписать произвольное сообщение, и у подписывающей стороны нет возможности применить политику к содержимому сообщения. Защищайте доступ к SignPrehash точно так же, как вы защищаете доступ к PublicKeySign .
  • Защитите значение прехеша во время передачи. Оно не является секретом, но злоумышленник, способный изменить его в процессе передачи, контролирует то, что будет подписано.

Выберите тип ключа

ML-DSA в режиме External Mu , как описано в RFC 9881 , является единственным алгоритмом, который Tink поддерживает для Prehash и SignPrehash. Для работы с этими примитивами следует использовать стандартный ключ ML-DSA.

Для большинства случаев использования мы рекомендуем ML_DSA_65 .

Ключ должен содержать идентификатор, поскольку каждое значение прехеша начинается с префикса, содержащего идентификатор ключа, для которого оно было вычислено. Допускаются следующие варианты:

  • TINK — полученная сигнатура начинается с обычного 5-байтового префикса вывода Tink.
  • NO_PREFIX_WITH_PREHASH_ID — результирующая подпись не содержит выходного префикса, в то время как ключ по-прежнему имеет идентификатор, необходимый для значения прехеша.

Ключи, использующие вариант NO_PREFIX (в исходном виде), не поддерживаются, поскольку у них нет идентификатора ключа.

Примеры

В приведенных ниже примерах сообщение подписывается в два этапа, а затем полученная подпись проверяется с помощью обычного примитива цифровой подписи .

Для удобства чтения они представлены в виде одного блока. В реальном развертывании эти два шага выполняются в разных местах; см. пошаговое руководство «Я хочу подписать данные с помощью удаленного подписанта» .

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

Идти

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
}