我正在使用加密下一代 API (CNG) 实现 ECDHE。我成功生成了公钥和私钥。对于预共享密钥,我使用BCryptSecretAgreement API,它返回预共享密钥秘密句柄(BCRYPT_SECRET_HANDLE)。
如何从 BCRYPT_SECRET_HANDLE 将预共享密钥导出为 BYTE 数组?
从 Windows 10 开始,您可以BCryptDeriveKey()
使用BCRYPT_KDF_RAW_SECRET
.
生成的密钥数据是原始机密。
注意 1:bcrypt.h 表示此格式适用于“WINBLUE”,如果我理解正确,它将是 Windows 8.1,但我收到了 STATUS_NOT_SUPPORTED 用于在 Windows 8.1 和 Windows Server 2012 R2 上使用此 KDF 类型。但是,这适用于 Windows 10。)
注意 2:我发现使用这种 KDF 类型返回的数据是小端(BCrypt 中的其他所有东西都是大端)。因此,要在大端世界中使用该值,您需要对数据进行字节翻转。
我需要执行以下操作,这是我的代码摘录,其中包含关键项目,您需要在此段之前导入私钥和公钥
DWORD bCryptStatus;
BCRYPT_SECRET_HANDLE secretHandle = NULL;
BCRYPT_KEY_HANDLE privateKeyHandle= NULL;
BCRYPT_KEY_HANDLE importedPublicKey = NULL;
BYTE *agreedSecret = NULL;
DWORD agreedSecretLength = 0;
//Import your keys here
//Generate the secret from the imported keys
bCryptStatus= BCryptSecretAgreement(privateKeyHandle, importedPublicKey, &secretHandle, 0);
//Now get the raw value of the secret agreement and copy it into an array
bCryptStatus= BCryptDeriveKey(
secretHandle, // Secret agreement handle
BCRYPT_KDF_RAW_SECRET, // Key derivation function (null terminated unicode string)
NULL, // KDF parameters
NULL, // Buffer that recieves the derived key
0, // Length of the buffer
&agreedSecretLength, // Number of bytes copied to the buffer
0); // Flags
agreedSecret = (PBYTE)MALLOC(agreedSecretLength);
if (NULL != agreedSecret)
{
_nCryptError = BCryptDeriveKey(
secretHandle, // Secret agreement handle
BCRYPT_KDF_RAW_SECRET, // Key derivation function (null terminated unicode string)
NULL, // KDF parameters
agreedSecret, // Buffer that recieves the derived key
agreedSecretLength, // Length of the buffer
&agreedSecretLength, // Number of bytes copied to the buffer
0); // Flags
}
//Free all the objects and the array when you are done, otherwise you will get memory leaks
if (NULL != importedPublicKey)
{
BCryptDestroyKey(importedPublicKey);
}
if (NULL != privateKeyHandle)
{
BCryptDestroyKey(privateKeyHandle);
}
if (NULL != secretHandle)
{
BCryptDestroySecret(secretHandle);
}
if (NULL != agreedSecret)
{
FREE(agreedSecret);
}
作为旁注,如果您使用 NCrypt,这也将起作用(NCryptDeriveKey),我在我的生产代码中验证了它。如前所述,数组将被反转,您将需要反转字节数组以获取秘密。
一旦你得到你的BCRYPT_SECRET_HANDLE
,你就可以BCryptDeriveKey
用来获取实际的对称加密密钥。
调用后BCryptSecretAgreement
,您需要使用该BCryptDeriveKey
函数来检索共享密钥。
这可以按如下方式完成:
// generates an ECDH shared secret from a public key and a private key
int get_ECDH_key(BCRYPT_KEY_HANDLE pubkey, BCRYPT_KEY_HANDLE privkey, unsigned char **key,
unsigned int *keylen)
{
SECURITY_STATUS sstatus;
BCRYPT_SECRET_HANDLE secret;
int _len;
// creates the shared secret, stored in a BCRYPT_SECRET_HANDLE
sstatus = BCryptSecretAgreement(privkey, pubkey, &secret, 0);
if (!BCRYPT_SUCCESS(sstatus)) {
printf("BCryptSecretAgreement failed with status %d", sstatus);
return 0;
}
// find out how much space is needed before retrieving the shared secret
sstatus = BCryptDeriveKey(secret, BCRYPT_KDF_HASH, NULL, NULL, 0, &_len, 0);
if (!BCRYPT_SUCCESS(sstatus)) {
printf("BCryptDeriveKey failed with status %d", sstatus);
return 0;
}
// allocate space for the shared secret
*key = malloc(_len);
if (*key == NULL) {
perror("malloc failed");
exit(1);
}
// retrieve the shared secret
sstatus = BCryptDeriveKey(secret, BCRYPT_KDF_HASH, NULL, *key, _len,
keylen, 0 );
if (!BCRYPT_SUCCESS(sstatus)) {
printf("BCryptDeriveKey failed with status %d", sstatus);
return 0;
}
return 1;
}
对于第二个参数,常量BCRYPT_KDF_HASH
表示使用散列作为密钥派生函数。要使用的哈希可以在第三个参数中指定。在本例中,第三个参数为 NULL,因此默认使用 SHA1。
此外,第四个参数是指向接收密钥的缓冲区的指针,可以为 NULL。如果是这样,则不复制密钥,但是将复制的字节数写入第六个参数给出的地址。这允许我们分配适当数量的空间,然后再次调用该函数,这次传入分配的缓冲区的地址。