3

I'm implementing ECDHE using crypto next generation APIs (CNG). I generate public and private keys successfully. For pre-shared key, I use BCryptSecretAgreement API, which returns me the pre-shared key secret handle (BCRYPT_SECRET_HANDLE).

How can I export the pre-shared key as BYTE array from the BCRYPT_SECRET_HANDLE?

Bizley
  • 17,392
  • 5
  • 49
  • 59

4 Answers4

3

Starting with Windows 10, you can call BCryptDeriveKey() with BCRYPT_KDF_RAW_SECRET.

The resulting key data is the raw secret.

Note 1: bcrypt.h indicates that this format works for "WINBLUE", which would be Windows 8.1, if I understand correctly, but I received STATUS_NOT_SUPPORTED for the use of this KDF type on both Windows 8.1 and Windows Server 2012 R2. This works, however, on Windows 10.)

Note2: I found the data returned using this KDF type to be little-endian (where everything else in BCrypt is big-endian). So, to use the value in an otherwise big-endian world, you need to byte-flip the data.

  • can you share a link to some example code? I'm looking at this to resolve a problem with libssh2 https://github.com/libssh2/libssh2/issues/388 and it would be super helpful. I've got something that gets to this final stage and then I successfully retrieve a buffer full of zeroes :-( – Wez Furlong Jul 31 '19 at 09:16
1

I needed to do the following, and here is an excerpt from my code which does the critical items, you will need to import the private and public keys before this segment

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);
}

As a side note, if you use NCrypt, this will work also (NCryptDeriveKey), I verified it on my production code. As it was stated earlier, the array will be reversed, and you will need to reverse the array of bytes to get the secret.

Russell Gantman
  • 269
  • 4
  • 7
0

Once you got your BCRYPT_SECRET_HANDLE, you use BCryptDeriveKey to obtain the actual symmetric encryption key.

Codeguard
  • 7,787
  • 2
  • 38
  • 41
  • This doesn't actually answer the original question. What this doesn't do is actually extract the shared key. It derives something from the shared secret, but there appears to be no way to actually get the shared key itself. – Tom Quarendon May 02 '17 at 14:33
-2

After calling BCryptSecretAgreement, You need to use the BCryptDeriveKey function to retrieve the shared secret.

This can be done as follows:

// 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;
}

For the second parameter, the constant BCRYPT_KDF_HASH says to use a hash as the key derivation function. The hash to use can be specified in the third parameter. In this example, the third parameter is NULL, so it uses SHA1 by default.

Also, the fourth parameter, which is a pointer to the buffer to receive the key, can be NULL. If so, the key is not copied however the number of bytes that would be copied are written to the address given by the sixth parameter. This allows us to allocate the proper amount of space then call the function again, this time passing in the address of the allocated buffer.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • This doesn't actually answer the original question. What this doesn't do is actually extract the shared key. It derives something from the shared secret, but there appears to be no way to actually get the shared key itself. – Tom Quarendon May 02 '17 at 14:33
  • @TomQuarendon This code does in fact retrieve the shared secret. After the second call to `BCryptDeriveKey`, there are `*keylen` bytes at `*key` which represent the shared key. Both sides of exchange will have the same value given their own private key and the other's public key. – dbush May 02 '17 at 14:41
  • It derives *a* shared key, yes. What it doesn't do in the case of Diffie Hellman for example, is give you the answer to "g**xy mod p". At best, if you provide no prepend and append, it gives you the SHA1 hash of that value. So if you are trying to write something that is compatible with, say, a Java implementation of DH, and in accordance with a spec such as, say, SSH, you're out of luck. Unless you can specify a "null" key derivation function, but I've failed in that so far. – Tom Quarendon May 02 '17 at 14:46
  • It didn't promise to generate what you want, so it's free to not do that. – Codeguard May 02 '17 at 17:57
  • @Codeguard: For me the API is deficient as it doesn't give you access to *the* shared key. It allows you to run one of a small selected set of key derivation functions on it, but you can't run a custom key derivation function. So it does all the hard, cryptographic work of doing the DH or ECCDH key exchange, but doesn't allow you to do the simple part of deriving a key in a custom fashion, which raw access to the shared key would, such as is necessary to comply with the SSH specification. I appreciate SSH is not a widely used protocol, but some people might be interested in it. – Tom Quarendon May 03 '17 at 07:50