在Windows 25H2 26200.6584及更高版本上,针对ECDH_P256,将KDF设置为SHA256的 BCryptSetProperty会失败

后端开发 2026-07-11

以下代码用于在Windows的 CryptNG(又名Cryptography API: Next Generation、BestCrypt、bcrypt)中生成一个Diffie-Hellman密钥。

其中一个任务是通过 BCryptSetProperty 函数把密钥派生函数设为SHA256:

BCryptSetProperty(..., BCRYPT_HKDF_HASH_ALGORITHM, BCRYPT_SHA256_ALGORITHM, ...);

BCryptSetProperty 的调用在 算法标识符 为以下之一时成功:

  • BCRYPT_DH_ALGORITHM ("DH"): Diffie-Hellman密钥交换算法。标准:PKCS #3

但在算法为:

  • BCRYPT_ECDH_P256_ALGORITHM ("ECDH_P256"): 256位素数椭圆曲线Diffie-Hellman密钥交换算法。标准:SP800-56A

我们知道 BCryptSetProperty 之所以失败,是因为它返回了下列文档中所列的失败代码之一:

返回值

返回一个状态码,用以指示函数执行的成功与否。

可能的返回码包括但不限于下列:

Return code Description
STATUS_SUCCESS 函数执行成功。
STATUS_INVALID_HANDLE hObject参数中的句柄无效。
STATUS_INVALID_PARAMETER 一个或多个参数无效。
STATUS_NOT_SUPPORTED pszProperty参数指定的属性不受支持或为只读。

在本例中 BCryptSetProperty 的失败返回为 STATUS_INVALID_HANDLE

以下代码在Windows版本大约为22H2 22621.963时能够工作。但不久之后就停止工作。

  • Windows 22H2 22621.963: 可用
  • Windows 25H2 26200.6584: 失败(bcryptprimitives.dll 版本10.0.26100.5074)
  • Windows 10.0.28000.1516: 失败

CRME

NTSTATUS GenDHKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ PCWSTR pszAlgId)
{
    NTSTATUS status;
    BCRYPT_ALG_HANDLE hAlgorithm;

    if (0 <= (status = BCryptOpenAlgorithmProvider(&hAlgorithm, pszAlgId, 0, 0)))
    {
        BCRYPT_KEY_HANDLE hKey;
        status = BCryptGenerateKeyPair(hAlgorithm, &hKey, 0, 0);
        BCryptCloseAlgorithmProvider(hAlgorithm, 0);

        if (0 <= status)
        {
            if (0 > (status = BCryptFinalizeKeyPair(hKey, 0)))
            {
                BCryptDestroyKey(hKey);
            }
            else
            {
                *phKey = hKey;
            }
        }
    }

    return status;
}

NTSTATUS bug_demo(_In_ PCWSTR pszAlgId)
{
    BCRYPT_KEY_HANDLE hKey;
    NTSTATUS status;
    if (0 <= (status = GenDHKey(&hKey, pszAlgId)))
    {
        BCRYPT_SECRET_HANDLE hSecret;
        status = BCryptSecretAgreement(hKey, hKey, &hSecret, 0);
        BCryptDestroyKey(hKey);
        if (0 <= status)
        {
            status = BCryptSetProperty(hSecret, BCRYPT_HKDF_HASH_ALGORITHM, 
                (PBYTE)BCRYPT_SHA256_ALGORITHM, sizeof(BCRYPT_SHA256_ALGORITHM), 0);

            BCryptDestroySecret(hSecret);
        }
    }

    return status;
}

    bug_demo(BCRYPT_DH_ALGORITHM);
    bug_demo(BCRYPT_ECDH_P256_ALGORITHM);

基于 BCryptDeriveKey 的文档,以及 BCRYPT\_KDF\_HKDF (L"HKDF"),需要调用此函数

在带有 BCRYPT_HKDF_HASH_ALGORITHM 的秘密句柄上调用 [BCryptSetProperty],以在HKDF的 HMAC计算中设置要使用的哈希算法。

问题出在哪里?

解决方案

如果要查找 MSCryptEcDhSetProperty 的实现(它在ECDH密钥上的 BCryptSetProperty 调用,代码看起来像下述)

#define BCRYPT_KEY_MAGIC        'MSKY'
#define BCRYPT_ALG_MAGIC        'MSAL'
#define BCRYPT_SECRET_MAGIC     'MSEC'

typedef struct BCRYPT_OBJECT_HEADER {
    ULONG cbSize;
    ULONG Magic;
} *PBCRYPT_OBJECT_HEADER;

void WPP_SF_sDsd(TRACEHANDLE LoggerHandle, PVOID, PVOID, NTSTATUS status, PCSTR msg, PCSTR file, ULONG line);

void DebugTraceError(NTSTATUS status, PCSTR msg, PCSTR file, ULONG line)
{
    if (WPP_GLOBAL_Control.Next != &WPP_GLOBAL_Control && (WPP_GLOBAL_Control.Flags & 1)) 
    { 
        WPP_SF_sDsd(WPP_GLOBAL_Control.LoggerHandle, 0, 0, status, msg, file, line);
    }
}

#define LOG_ERROR(status) DebugTraceError(status, "Status", __FILE__, __LINE__)

inline BOOLEAN ValidateEccHandle(_In_ PBCRYPT_OBJECT_HEADER hObject)
{
    if (!hObject || hObject->cbSize < sizeof(BCRYPT_OBJECT_HEADER) + 4)
    {
        LOG_ERROR(STATUS_INVALID_HANDLE);// onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c(438):
        return FALSE;
    }

    switch (hObject->Magic)
    {
    case BCRYPT_KEY_MAGIC:
    case BCRYPT_ALG_MAGIC:
        return TRUE;
    }

    LOG_ERROR(STATUS_INVALID_HANDLE);// onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c(446):
    return FALSE;
}

NTSTATUS WINAPI MSCryptEcDhSetProperty(
    _In_ PBCRYPT_OBJECT_HEADER hObject,
    _In_ LPCWSTR pszProperty, 
    _In_reads_bytes_(cbInput) PUCHAR pbInput,
    _In_ ULONG cbInput, 
    _In_ ULONG dwFlags 
)
{
    NTSTATUS status;

    if (dwFlags) 
    {
        LOG_ERROR(STATUS_INVALID_PARAMETER);// onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c(4017):
        return STATUS_INVALID_PARAMETER;
    }

    if (!ValidateEccHandle(hObject))
    {
        LOG_ERROR(STATUS_INVALID_HANDLE);// onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c(4024):
        return STATUS_INVALID_HANDLE;
    }

    switch (hObject->Magic)
    {
    case BCRYPT_KEY_MAGIC:
        if (0 > (status = MSCryptEccSetKeyProperty(hObject, pszProperty, pbInput, cbInput, 0))) 
        {
            LOG_ERROR(status);// onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c(4041):
        }
        return status;

    case BCRYPT_ALG_MAGIC:
        if (0 > (status = MSCryptEccSetAlgProperty(hObject, pszProperty, pbInput, cbInput, 0))) 
        {
            LOG_ERROR(status);// onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c(4054):
        }
        return status;

    case BCRYPT_SECRET_MAGIC:
        if (0 > (status = MSCryptSetPropertySecret(hObject, pszProperty, pbInput, cbInput, 0))) 
        {
            LOG_ERROR(status);// onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c(4067):
        }
        return status;
    }

    LOG_ERROR(STATUS_NOT_SUPPORTED);// onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c(4074):
    return STATUS_NOT_SUPPORTED;
}

MSCryptEcDhSetProperty 的代码是正确的。这个函数处理3 种对象类型 - ALG( 'MSAL' )、KEY( 'MSKY' )和SECRET( 'MSEC' )。这个函数原本位于onecore\ds\security\cryptoapi\ncrypt\msprim\ecc\ecc.c文件,大约在第4000-4100行。但在某个时刻,在该函数的开头插入了一个验证函数 ValidateEccHandle —— 它也在同一文件(ecc.c)但大约在430-450行。并且这个函数包含一个bug —— 它忘记了 BCRYPT_SECRET_MAGIC 情况。结果,当我们尝试在 BCRYPT_SECRET_HANDLE 上设置任何属性时,得到错误 STATUS_INVALID_HANDLE

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章