Мы рекомендуем использовать примитивы Prehash и SignPrehash с ключом ML_DSA_65, если закрытый ключ хранится в месте, откуда сообщение не может быть отправлено.
Иногда сторона, владеющая ключом подписи, не может — или не должна — получать сообщение сама: ключ хранится в HSM или KMS, или размер сообщения превышает лимит размера запроса, установленный для подписывающей стороны.
Примитивы Prehash и SignPrehash решают эту проблему, разделяя процесс подписания на два этапа. Вычисляется короткое значение прехеша в месте расположения сообщения, используя только открытый ключ, и отправляется подписывающему лицу. Подписывающее лицо преобразует его в подпись, используя закрытый ключ, не видя самого сообщения. В режиме External Mu (алгоритм, поддерживаемый Tink) значение прехеша составляет 69 байт, независимо от размера сообщения.
В результате вы получаете обычную подпись, продублированную над исходным сообщением: проверяющие используют стандартный примитив цифровой подписи и им не нужно знать, что были задействованы два этапа.
Прежде чем начать
Создайте набор ключей ML-DSA, ключи которого требуют указания идентификатора — либо варианта TINK , либо NO_PREFIX_WITH_PREHASH_ID если вы не хотите, чтобы в результирующей подписи присутствовал префикс. Предоставьте подписывающей стороне закрытый набор ключей, а стороне, выполняющей предварительное хеширование, — соответствующий открытый набор ключей. У подписывающей стороны должен быть включенный ключ для каждого идентификатора ключа, который может сгенерировать сторона, выполняющая предварительное хеширование; см. раздел «Наборы ключей» .
Шаг 1: Вычислите значение прехеша.
Запустите этот код там, где находится сообщение. Для его работы требуется только открытый набор ключей.
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();
Идти
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 }
Шаг 2: Отправьте значение прехеша подписывающему лицу.
Отправьте значение прехеша тому, кто хранит закрытый ключ. Оно не является секретом, но необходимо защитить его целостность при передаче: злоумышленник, способный изменить его в процессе передачи, контролирует то, что будет подписано.
Шаг 3: Подпишите значение прехеша.
Запустите этот код там, где находится закрытый ключ.
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();
Идти
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 }
Шаг 4: Проверка подписи
Проверка — это обычный процесс цифровой подписи исходного сообщения , а не его прехеш-значения.
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);
Идти
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 и 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 (в исходном виде), не поддерживаются, поскольку у них нет идентификатора ключа.