Поделиться через


Использование ML-DSA с CNG

Замечание

Некоторые сведения относятся к предварительному продукту, который может быть существенно изменен до его коммерческого выпуска. Корпорация Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых в отношении информации, предоставленной здесь. Функция, описанная в этом разделе, доступна в предварительной версии Предварительной версии 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.

  1. Используйте BCryptOpenAlgorithmProvider для алгоритма ML-DSA, выбрав либо поставщика Microsoft Primitive по умолчанию, либо другого поставщика HSM. Этот шаг настраивает криптографический контекст для последующих операций.

    status = BCryptOpenAlgorithmProvider(
        &hAlg,
        BCRYPT_MLDSA_ALGORITHM,
        MS_PRIMITIVE_PROVIDER,
        NULL);
    
    if (!NT_SUCCESS(status)) {
        goto cleanup;
    }
    
  2. Используйте BCryptGenerateKeyPair , чтобы создать закрытые и открытые ключи для выбранного алгоритма.

    status = BCryptGenerateKeyPair(hAlg, &hKeyPair, 0, NULL);
    
    if (!NT_SUCCESS(status)) {
        goto cleanup;
    }
    

    Параметры приведены следующим образом:

    • hAlg: дескриптор поставщика алгоритма, полученный из BCryptOpenAlgorithmProvider.
    • hKeyPair: дескриптор для пары ключей.
    • 0: указывает размер ключа по умолчанию для ML-DSA.
    • NULL: для этой операции не заданы флаги.
  3. Используйте 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).

  4. Используйте BCryptFinalizeKeyPair , чтобы открытый и закрытый ключ были готовы к использованию.

    status = BCryptFinalizeKeyPair(hKeyPair, NULL); 
    if (!NT_SUCCESS(status)) { 
        goto cleanup; 
    }
    
    // Public/Private key pair is generated at this point 
    

Подпишите сообщение

Чтобы подписать сообщение с созданной парой ключей, код использует BCryptSignHash.

  1. Код сначала определяет размер буфера, необходимый для сигнатуры, вызвав 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;
    }
    
  2. Затем он настраивает структуру 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" для подписи (сообщение подписано напрямую, а не хэш).
  3. Наконец, фактическая подпись создается с помощью другого вызова 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;
    } 
    

Экспорт открытого ключа

В этом разделе открытый ключ экспортируется, чтобы другие пользователи могли проверить подписи.

  1. 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;
    }
    
  2. Буфер выделен, и 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.

  1. Функция MLDSAVerifySample в примере кода ниже получает экспортированный BLOB-объект открытого ключа, сообщение, контекст и подпись.
  2. BCryptImportKeyPair импортирует BLOB открытого ключа для создания дескриптора ключа, который будет использоваться API.
  3. Опять же BCRYPT_PQDSA_PADDING_INFO подготавливается с контекстом (он должен соответствовать тому, который использовался во время подписи). Задаются следующие поля:
    • pbCtx/cbCtx (Context): используется для предоставления дополнительных данных, которые могут помочь привязать сигнатуры к конкретному приложению или варианту использования. Подписывание и проверка должны использовать один и тот же контекст.
    • pszPrehashAlgId: установлено значение NULL для "pure" ML-DSA. Для предварительных вариантов хэша задается идентификатор хэш-алгоритма.
    • BCRYPT_PAD_PQDSA: Задает использование дополнения PQDSA для пост-квантовой безопасности.
  4. 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;
}