C++ 从BCRYPT_secret_句柄将共享密钥导出为字节数组

C++ 从BCRYPT_secret_句柄将共享密钥导出为字节数组,c++,c,tls1.2,cng,ecdhe,C++,C,Tls1.2,Cng,Ecdhe,我正在使用加密下一代API(CNG)实现ECDHE。我成功地生成了公钥和私钥。对于预共享密钥,我使用API,它返回预共享密钥秘密句柄(BCRYPT\u secret\u句柄) 如何从BCRYPT_SECRET_句柄将预共享密钥导出为字节数组?一旦获得BCRYPT_SECRET_句柄,您就可以使用BCryptDeriveKey来获取实际的对称加密密钥。调用BCryptSecretAgreement后,您需要使用该函数来检索共享密钥 这可以通过以下方式完成: // generates an ECDH

我正在使用加密下一代API(CNG)实现ECDHE。我成功地生成了公钥和私钥。对于预共享密钥,我使用API,它返回预共享密钥秘密句柄(BCRYPT\u secret\u句柄)


如何从BCRYPT_SECRET_句柄将预共享密钥导出为字节数组?

一旦获得
BCRYPT_SECRET_句柄
,您就可以使用
BCryptDeriveKey
来获取实际的对称加密密钥。

调用
BCryptSecretAgreement
后,您需要使用该函数来检索共享密钥

这可以通过以下方式完成:

// 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
表示使用HASH作为密钥派生函数。可以在第三个参数中指定要使用的哈希。在本例中,第三个参数为NULL,因此默认情况下使用SHA1


另外,第四个参数(指向接收密钥的缓冲区的指针)可以为NULL。如果是这样,则不会复制密钥,但会将要复制的字节数写入第六个参数给定的地址。这允许我们分配适当的空间量,然后再次调用该函数,这次传递分配的缓冲区的地址。

从Windows 10开始,您可以使用
BCRYPT_KDF_RAW_SECRET
调用
BCryptDeriveKey()

生成的密钥数据是原始密钥

注1:bcrypt.h表示此格式适用于“WINBLUE”,如果我理解正确,它将是Windows 8.1,但我收到的状态为“不支持在Windows 8.1和Windows Server 2012 R2上使用此KDF类型”。不过,这在Windows 10上是有效的。)


注2:我发现使用此KDF类型返回的数据是little-endian(BCrypt中的其他所有内容都是big-endian)。因此,要在另一个大端世界中使用该值,您需要对数据进行字节翻转。

我需要执行以下操作,这里是我的代码中的一段摘录,用于处理关键项,您需要在该段之前导入私钥和公钥

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),我在生产代码中验证了它。
如前所述,数组将被反转,您需要反转字节数组以获取机密。

这实际上并没有回答最初的问题。实际上,这并不能提取共享密钥。它从共享密钥中派生出一些东西,但似乎无法实际获取共享密钥本身。@TomQuarendon此代码实际上检索到共享密钥。第二次调用
BCryptDeriveKey
后,
*key
处有
*keylen
字节表示共享密钥。交换双方在各自的私钥和另一方的公钥下具有相同的值。它派生出一个共享密钥,是的。例如,在Diffie Hellman的例子中,它没有做的是给出“g**xy mod p”的答案。充其量,如果您不提供prepend和append,它将为您提供该值的SHA1哈希。因此,如果您试图编写与DH的Java实现兼容的东西,并且符合SSH之类的规范,那么您的运气就不好了。除非您可以指定一个“null”键派生函数,但我到目前为止在这方面失败了。它不能保证生成您想要的,所以可以不这样做。@Codeguard:对我来说,API是有缺陷的,因为它不允许您访问共享键。它允许您在其上运行一组选定的小密钥派生函数,但不能运行自定义密钥派生函数。因此,它完成了DH或ECCDH密钥交换的所有艰苦的加密工作,但不允许您以自定义方式派生密钥的简单部分,这是对共享密钥的原始访问,如遵守SSH规范所必需的。我知道SSH不是一个广泛使用的协议,但有些人可能对它感兴趣。这实际上并没有回答最初的问题。实际上,这并不能提取共享密钥。它从共享密钥中派生出一些东西,但似乎无法实际获取共享密钥本身。您可以共享指向某个示例代码的链接吗?我正在研究这个来解决libssh2的一个问题,它将非常有用。我得到了一些东西,可以进入最后一个阶段,然后我成功地检索到一个满是零的缓冲区:-(