How to use BCryptDeriveKeyCapi with RC4 to replace CryptoAPI code

David Robinson 20 Reputation points
2024-06-18T12:50:27.4233333+00:00

I have some old code that uses CryptoAPI to encrypt data with RC4. The legacy process is (in summary):

CryptAcquireContext()
CryptCreateHash("MD5")
CryptHashData (password, length)
CryptDeriveKey ("RC4", )
CryptEncrypt ()

The new code:

BCryptOpenAlgorithmProvider("MD5")
BCryptGetProperty(BCRYPT_OBJECT_LENGTH)
HeapAlloc(objectLength)
BCryptGetProperty(BCRYPT_HASH_LENGTH)
BCryptCreateHash()
BCryptHashData(password, length)

At this point I can retrieve the MD5 hash from the hash object by calling BCryptFinishHash to check the hash value is consistent with the old code.

From the documentation it seems I am supposed to call BCryptDeriveKeyCapi () to create a key using the same key derivation mechanism as CryptDeriveKey but the output from this function looks to be a block of data of unspecified length with no indication of how to create a key from this data. The legacy CryptDeriveKey created a key object that could be used directly in CryptEncrypt.

I need either:

  • A way to create the RC4 key that can be used for BCryptEncrypt (keyHandle, plaintext, plaintextLength, [out] ciphertext)
  • Specification of how to convert the MD5 hash into a raw RC4 key that I can use in alternative RC4 implementations that operates the same way as the key created by CryptDeriveKey.

Thanks,

David Robinson

Sepura Ltd

Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,491 questions
{count} votes

Accepted answer
  1. Gary Nebbett 5,841 Reputation points
    2024-06-22T09:56:41.3433333+00:00

    Hello David,

    Your observations about BCryptDeriveKeyCapi are correct and are consistent with the documentation of CryptDeriveKey; the "Remarks" section of CryptDeriveKey describes the steps taken to derive a key and for stream ciphers this just amounts to completing the hash (BCryptDeriveKeyCapi really adds value when porting legacy symmetric block cipher code).

    I first tried comparing the ciphertext output of the legacy and CNG code. CryptDeriveKey only generates RC4 keys with lengths between 40 and 128 bits (lengths outside of this range fail with error NTE_BAD_FLAGS). The output matched for all lengths except 40 bits.

    There is an RFC (RFC 6229 - Test Vectors for the Stream Cipher RC4) that contains test vectors for 40 bit RC4 keys. The output of the CNG routines match the documented results but the legacy routines produce a different result (the legacy routines match the documented results for the key lengths 56, 64, 80 and 128).

    Unlikely as it may seem, I think that there might be a bug in the legacy (Microsoft) code when using 40 bit keys...

    Update: It is not a bug but rather "by design" behaviour - still trying to work out how to control it.

    Update 2: The Flags parameter to both CryptDeriveKey and CryptImportKey support the value CRYPT_NO_SALT. The description of this value is:

    A no-salt value gets allocated for a 40-bit symmetric key. For more information, see Salt Value Functionality.

    If your old code uses CryptDeriveKey without including the flag CRYPT_NO_SALT then 11 zero bytes are used as the salt. If the same salt bytes are appended to the CNG key then BCryptEncrypt produces the same ciphertext.

    Gary

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful