在Windows 25H2 26200.6584及更高版本上,针对ECDH_P256,将KDF设置为SHA256的 BCryptSetProperty会失败
以下代码用于在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。