Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Замечание
Некоторые сведения относятся к предварительному продукту, который может быть существенно изменен до его коммерческого выпуска. Корпорация Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых в отношении информации, предоставленной здесь. Функция, описанная в этом разделе, доступна в предварительной версии Предварительной версии Windows Preview.
В этой статье приведено руководство по реализации комплексного рабочего процесса для создания и проверки цифровых подписей с помощью алгоритма ML-DSA с ПОМОЩЬЮ API CNG Майкрософт.
Пример кода ML-DSA с помощью BCrypt
Узнайте, как использовать алгоритм ML-DSA с API CNG Майкрософт для цифровых подписей. Пример BCrypt включает функции для подписывания сообщения и проверки подписи, а также экспорта открытого ключа. Код предназначен для простоты выполнения и понимания, что подходит разработчикам, желающим реализовать пост-квантовые цифровые подписи в своих приложениях.
Создание и проверка подписи с помощью ML-DSA
Этот пример кода демонстрирует комплексный рабочий процесс для создания и проверки цифровых подписей с помощью алгоритма ML-DSA с ПОМОЩЬЮ API CNG Майкрософт. Он подчеркивает важность сопоставления контекста во время подписывания и проверки, а также тщательное управление криптографическими дескрипторами и памятью. Алгоритмы после квантовой цифровой подписи эквивалентны на высоком уровне существующим алгоритмам цифровой подписи, которые мы используем сегодня (RSA-PSS, ECDSA и т. д.), где одна сторона создает пару открытых и закрытых ключей и подписывает сообщение (или его хэш-значение) с закрытым ключом для создания подписи. Подпись может быть проверена любым, у кого есть связанный открытый ключ. Одно из различий заключается в том, что алгоритмы цифровой подписи PQ поддерживают пре-хэш-варианты, которые сначала хэшируют, затем подписывают данные, аналогичные традиционным алгоритмам подписи, а также чистые версии, которые подписывают любые входные данные произвольной длины. В этом примере используется чистый ML-DSA и описывается использование предварительно хэш-ML-DSA. Следуя этим инструкциям, разработчики могут реализовать безопасные пост-квантовые цифровые подписи в своих приложениях.
Настройка дескрипторов алгоритма и пар ключей
Выполните следующие шаги, чтобы настроить обработчики алгоритма и пары ключей для ML-DSA.
Используйте BCryptOpenAlgorithmProvider для алгоритма ML-DSA, выбрав либо поставщика Microsoft Primitive по умолчанию, либо другого поставщика HSM. Этот шаг настраивает криптографический контекст для последующих операций.
status = BCryptOpenAlgorithmProvider( &hAlg, BCRYPT_MLDSA_ALGORITHM, MS_PRIMITIVE_PROVIDER, NULL); if (!NT_SUCCESS(status)) { goto cleanup; }
Используйте BCryptGenerateKeyPair , чтобы создать закрытые и открытые ключи для выбранного алгоритма.
status = BCryptGenerateKeyPair(hAlg, &hKeyPair, 0, NULL); if (!NT_SUCCESS(status)) { goto cleanup; }
Параметры приведены следующим образом:
- hAlg: дескриптор поставщика алгоритма, полученный из BCryptOpenAlgorithmProvider.
- hKeyPair: дескриптор для пары ключей.
-
0
: указывает размер ключа по умолчанию для ML-DSA. -
NULL
: для этой операции не заданы флаги.
Используйте BCryptSetProperty , чтобы указать набор параметров, используемый для ML-DSA, которые имеют компромиссы по надежности и производительности. В этом примере выбран набор параметров ML-DSA-44.
status = BCryptSetProperty(&hKeyPair, BCRYPT_PARAMETER_SET_NAME, (PUCHAR)BCRYPT_MLDSA_PARAMETER_SET_44, sizeof(BCRYPT_MLDSA_PARAMETER_SET_44), 0); if (!NT_SUCCESS(status)) { goto cleanup; }
Параметр BCRYPT_PARAMETER_SET_NAME указывает, какой параметр используется (например, ML-DSA-44).
Используйте BCryptFinalizeKeyPair , чтобы открытый и закрытый ключ были готовы к использованию.
status = BCryptFinalizeKeyPair(hKeyPair, NULL); if (!NT_SUCCESS(status)) { goto cleanup; } // Public/Private key pair is generated at this point
Подпишите сообщение
Чтобы подписать сообщение с созданной парой ключей, код использует BCryptSignHash.
Код сначала определяет размер буфера, необходимый для сигнатуры, вызвав BCryptSignHash с выходными
NULL
данными. Затем он выделяет память для подписи.// Get the signature size status = BCryptSignHash(hKeyPair, NULL, NULL, 0, NULL, 0, &cbSignature, NULL); if (!NT_SUCCESS(status)) { goto cleanup; } pbSignature = (PBYTE)LocalAlloc(LMEM_FIXED, cbSignature); if (pbSignature == NULL) { status = STATUS_NO_MEMORY; goto cleanup; }
Затем он настраивает структуру BCRYPT_PQDSA_PADDING_INFO :
// Sign with pure ML-DSA // // Pure variants sign arbitrary length messages whereas // the pre-hash variants sign a hash value of the input. // To sign with pure ML-DSA or pure SLH-DSA, pszPrehashAlgId must be set // to NULL if BCRYPT_PQDSA_PADDING_INFO is provided to the sign/verify // functions. // For pre-hash signing, the hash algorithm used in creating // the hash of the input must be specified using the pszPrehashAlgId // field of BCRYPT_PQDSA_PADDING_INFO. This hash algorithm information // is required in generating and verifying the signature as the OID of // the hash algorithm becomes a prefix of the input to be signed. // padinfo.pbCtx = (PUCHAR)ctx; padinfo.cbCtx = sizeof(ctx); padinfo.pszPrehashAlgId = NULL;
Структура BCRYPT_PQDSA_PADDING_INFO содержит следующие поля:
- pbCtx и cbCtx: указатель и размер для необязательной строки контекста (дополнительные данные, прошедшие проверку подлинности).
-
pszPrehashAlgId: задано значение
NULL
"pure" для подписи (сообщение подписано напрямую, а не хэш).
Наконец, фактическая подпись создается с помощью другого вызова BCryptSignHash. Маркер ключа, сведения о заполнении, сообщение и выделенный буфер подписи передаются в вызов. Флаг BCRYPT_PAD_PQDSA указывает заполнение PQDSA.
status = BCryptSignHash( hKeyPair, &padinfo, (PUCHAR)msg, sizeof(msg), pbSignature, cbSignature, &cbWritten, BCRYPT_PAD_PQDSA); if (!NT_SUCCESS(status)) { goto cleanup; }
Экспорт открытого ключа
В этом разделе открытый ключ экспортируется, чтобы другие пользователи могли проверить подписи.
BCryptExportKey сначала вызывается с
NULL
параметрами и форматом BCRYPT_PQDSA_PUBLIC_BLOB для получения требуемого размера буфера:// Get the public key blob size status = BCryptExportKey( hKeyPair, NULL, BCRYPT_PQDSA_PUBLIC_BLOB, NULL, 0, &cbPublicKeyBlob, NULL); if (!NT_SUCCESS(status)) { goto cleanup; }
Буфер выделен, и BCryptExportKey снова вызывается для экспорта открытого ключа:
pbPublicKeyBlob = (PBYTE)LocalAlloc(LMEM_FIXED, cbPublicKeyBlob); if (pbPublicKeyBlob == NULL) { status = STATUS_NO_MEMORY; goto cleanup; } // Export the public key status = BCryptExportKey( hKeyPair, NULL, BCRYPT_PQDSA_PUBLIC_BLOB, pbPublicKeyBlob, cbPublicKeyBlob, &cbWritten, NULL); if (!NT_SUCCESS(status)) { goto cleanup; }
Экспортируемый ключ находится в формате BCRYPT_PQDSA_PUBLIC_BLOB .
Очистите ресурсы
На этапе очистки выделенные ресурсы, такие как буферы и дескрипторы, освобождаются, чтобы предотвратить утечки памяти и висячие дескрипторы.
cleanup:
if (pbPublicKeyBlob)
{
LocalFree(pbPublicKeyBlob);
}
if (pbSignature)
{
LocalFree(pbSignature);
}
if (hKeyPair != NULL)
{
BCryptDestroyKey(hKeyPair);
}
if (hAlg != NULL)
{
BCryptCloseAlgorithmProvider(hAlg, NULL);
}
Проверка подписи
Для проверки подписи код использует BCryptVerifySignature.
- Функция MLDSAVerifySample в примере кода ниже получает экспортированный BLOB-объект открытого ключа, сообщение, контекст и подпись.
- BCryptImportKeyPair импортирует BLOB открытого ключа для создания дескриптора ключа, который будет использоваться API.
- Опять же BCRYPT_PQDSA_PADDING_INFO подготавливается с контекстом (он должен соответствовать тому, который использовался во время подписи). Задаются следующие поля:
- pbCtx/cbCtx (Context): используется для предоставления дополнительных данных, которые могут помочь привязать сигнатуры к конкретному приложению или варианту использования. Подписывание и проверка должны использовать один и тот же контекст.
-
pszPrehashAlgId: установлено значение
NULL
для "pure" ML-DSA. Для предварительных вариантов хэша задается идентификатор хэш-алгоритма. - BCRYPT_PAD_PQDSA: Задает использование дополнения PQDSA для пост-квантовой безопасности.
- BCryptVerifySignature вызывается с помощью дескриптора ключа, сведений о заполнении, исходного сообщения и подписи. Если функция возвращает успех, сигнатура действительна; в противном случае она недействительна.
Приведенные выше действия аннотированы в примере кода ниже:
//
// This function takes as input an ML-DSA-44 public key blob,
// and a signature generated by the same algorithm along with
// the message the signature belongs to. It verifies whether the
// signature is valid or not.
//
// STEP 1: Receive the public key blob, message, context, and signature
NTSTATUS MLDSAVerifySample(
PCBYTE pbPublicKeyBlob,
ULONG cbPublicKeyBlob,
PCBYTE pbMsg,
ULONG cbMsg,
PCBYTE pbContext,
ULONG cbContext,
PCBYTE pbSignature,
ULONG cbSignature)
{
NTSTATUS status = STATUS_SUCCESS;
BCRYPT_ALG_HANDLE hAlg = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;
BCRYPT_PQDSA_PADDING_INFO padinfo = { 0 };
status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_MLDSA_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// STEP 2: Import the public key
// Import the public key
status = BCryptImportKeyPair(
hAlg,
NULL,
BCRYPT_PQDSA_PUBLIC_BLOB,
&hKey,
(PUCHAR)pbPublicKeyBlob,
cbPublicKeyBlob,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// STEP 3: Prepare the padding info
// Verify the signature
// Assuming the signature is generated with pure ML-DSA.
// Otherwise pszPrehashAlgId must be set to the identifier
// of the hash algorithm used in pre-hashing the input.
padinfo.pbCtx = (PUCHAR)pbContext;
padinfo.cbCtx = cbContext;
padinfo.pszPrehashAlgId = NULL;
// STEP 4: Verify the signature
status = BCryptVerifySignature(
hKey,
&padinfo,
(PUCHAR)pbMsg, // pbHash
cbMsg, // cbHash
(PUCHAR)pbSignature,
cbSignature,
BCRYPT_PAD_PQDSA);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
cleanup:
if (hKey != NULL)
{
BCryptDestroyKey(hKey);
}
if (hAlg != NULL)
{
BCryptCloseAlgorithmProvider(hAlg, NULL);
}
return status;
}
В блоке cleanup:
код использует BCryptDestroyKey для удаления дескриптора ключа из памяти и BCryptCloseAlgorithmProvider для удаления дескриптора алгоритма из памяти.
Просмотрите полный пример кода
В следующем примере кода показан полный процесс создания и проверки цифровой подписи с помощью алгоритма ML-DSA с ПОМОЩЬЮ API CNG Майкрософт:
//
// Sample ML-DSA code
//
// This function creates an ML-DSA-44 private key, signs
// a message with it, and exports the public-key. The receiver
// can use the public key, message, and the signature to verify
// that they are generated by the same party who owns the private key.
//
#define SAMPLE_MESSAGE "message"
#define SAMPLE_CONTEXT "context"
NTSTATUS MLDSASignSample()
{
NTSTATUS status = STATUS_SUCCESS;
BCRYPT_ALG_HANDLE hAlg = NULL;
BCRYPT_KEY_HANDLE hKeyPair = NULL;
BCRYPT_PQDSA_PADDING_INFO padinfo = { 0 };
PBYTE pbSignature = NULL;
ULONG cbSignature, cbWritten;
const BYTE msg[] = SAMPLE_MESSAGE;
const BYTE ctx[] = SAMPLE_CONTEXT;
PBYTE pbPublicKeyBlob = NULL;
ULONG cbPublicKeyBlob;
status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_MLDSA_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = BCryptGenerateKeyPair(hAlg, &hKeyPair, 0, NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = BCryptSetProperty(&hKeyPair, BCRYPT_PARAMETER_SET_NAME, (PUCHAR)BCRYPT_MLDSA_PARAMETER_SET_44, sizeof(BCRYPT_MLDSA_PARAMETER_SET_44), 0);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
status = BCryptFinalizeKeyPair(hKeyPair, NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// Public/Private key pair is generated at this point
// Get the signature size
status = BCryptSignHash(hKeyPair, NULL, NULL, 0, NULL, 0, &cbSignature, NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
pbSignature = (PBYTE)LocalAlloc(LMEM_FIXED, cbSignature);
if (pbSignature == NULL) {
status = STATUS_NO_MEMORY;
goto cleanup;
}
//
// Sign with pure ML-DSA
//
// Pure variants sign arbitrary length messages whereas
// the pre-hash variants sign a hash value of the input.
// To sign with pure ML-DSA or pure SLH-DSA, pszPrehashAlgId must be set
// to NULL if BCRYPT_PQDSA_PADDING_INFO is provided to the sign/verify
// functions.
// For pre-hash signing, the hash algorithm used in creating
// the hash of the input must be specified using the pszPrehashAlgId
// field of BCRYPT_PQDSA_PADDING_INFO. This hash algorithm information
// is required in generating and verifying the signature as the OID of
// the hash algorithm becomes a prefix of the input to be signed.
//
padinfo.pbCtx = (PUCHAR)ctx;
padinfo.cbCtx = sizeof(ctx);
padinfo.pszPrehashAlgId = NULL;
status = BCryptSignHash(
hKeyPair,
&padinfo,
(PUCHAR)msg,
sizeof(msg),
pbSignature,
cbSignature,
&cbWritten,
BCRYPT_PAD_PQDSA);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
//
// Export the public key
//
// Get the public key blob size
status = BCryptExportKey(
hKeyPair,
NULL,
BCRYPT_PQDSA_PUBLIC_BLOB,
NULL,
0,
&cbPublicKeyBlob,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
pbPublicKeyBlob = (PBYTE)LocalAlloc(LMEM_FIXED, cbPublicKeyBlob);
if (pbPublicKeyBlob == NULL) {
status = STATUS_NO_MEMORY;
goto cleanup;
}
// Export the public key
status = BCryptExportKey(
hKeyPair,
NULL,
BCRYPT_PQDSA_PUBLIC_BLOB,
pbPublicKeyBlob,
cbPublicKeyBlob,
&cbWritten,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
cleanup:
if (pbPublicKeyBlob)
{
LocalFree(pbPublicKeyBlob);
}
if (pbSignature)
{
LocalFree(pbSignature);
}
if (hKeyPair != NULL)
{
BCryptDestroyKey(hKeyPair);
}
if (hAlg != NULL)
{
BCryptCloseAlgorithmProvider(hAlg, NULL);
}
return status;
}
//
// This function takes as input an ML-DSA-44 public key blob,
// and a signature generated by the same algorithm along with
// the message the signature belongs to. It verifies whether the
// signature is valid or not.
//
NTSTATUS MLDSAVerifySample(
PCBYTE pbPublicKeyBlob,
ULONG cbPublicKeyBlob,
PCBYTE pbMsg,
ULONG cbMsg,
PCBYTE pbContext,
ULONG cbContext,
PCBYTE pbSignature,
ULONG cbSignature)
{
NTSTATUS status = STATUS_SUCCESS;
BCRYPT_ALG_HANDLE hAlg = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;
BCRYPT_PQDSA_PADDING_INFO padinfo = { 0 };
status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_MLDSA_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// Import the public key
status = BCryptImportKeyPair(
hAlg,
NULL,
BCRYPT_PQDSA_PUBLIC_BLOB,
&hKey,
(PUCHAR)pbPublicKeyBlob,
cbPublicKeyBlob,
NULL);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
// Verify the signature
// Assuming the signature is generated with pure ML-DSA.
// Otherwise pszPrehashAlgId must be set to the identifier
// of the hash algorithm used in pre-hashing the input.
padinfo.pbCtx = (PUCHAR)pbContext;
padinfo.cbCtx = cbContext;
padinfo.pszPrehashAlgId = NULL;
status = BCryptVerifySignature(
hKey,
&padinfo,
(PUCHAR)pbMsg, // pbHash
cbMsg, // cbHash
(PUCHAR)pbSignature,
cbSignature,
BCRYPT_PAD_PQDSA);
if (!NT_SUCCESS(status)) {
goto cleanup;
}
cleanup:
if (hKey != NULL)
{
BCryptDestroyKey(hKey);
}
if (hAlg != NULL)
{
BCryptCloseAlgorithmProvider(hAlg, NULL);
}
return status;
}