Примитивы Prehash и SignPrehash разделяют вычисление цифровой подписи на два этапа:
- Для работы функции prehash требуется только открытый ключ. Она преобразует сообщение произвольной длины в короткое значение prehash фиксированного размера.
- Для работы функции 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 }
Связанные темы
- Я хочу подписать данные с помощью удалённого подписанта — пошаговое описание двухэтапного процесса.
- Цифровая подпись — подписание и проверка за один шаг, когда подписывающее лицо может видеть сообщение.
- Язык поддерживает примитивные типы ключей.
- Формат передачи данных Tink — побайтовая структура значения прехеша, используемая для переноса Tink на другие языки программирования.