0

I have to compile existing C code using CNG (Cryptography API: Next Generation) functions for Windows Embedded Compact 2013. This code is using BCryptDeriveKeyPBKDF2, which is not available under Windows Embedded Compact 2013.

That means I need a replacement for the function below to implement the PBKDF2 key derivation algorithm as defined in RFC 2898 section 5.2, but without using BCryptDeriveKeyPBKDF2.

I found some C code which is using CryptoAPI functions here, but i don't want to use a 2nd, deprecated API if possible.

BOOL pbkdf2(
    PUCHAR pbPassword,  ULONG cbPassword,
    PUCHAR pbSalt, ULONG cbSalt,
    ULONGLONG cIterations,
    PUCHAR pbDerivedKey, ULONG cbDerivedKey)
{
    NTSTATUS status;
    BCRYPT_ALG_HANDLE hAlgorithm;

    status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_SHA1_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG);
    if (BCRYPT_SUCCESS(status))
    {
        status = BCryptDeriveKeyPBKDF2(hAlgorithm, pbPassword, cbPassword, pbSalt, cbSalt, cIterations, pbDerivedKey, cbDerivedKey, 0);
        BCryptCloseAlgorithmProvider(hAlgorithm, 0);
    }

    return BCRYPT_SUCCESS(status);
}
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
haide
  • 11
  • 1
  • 5

2 Answers2

0

You could use CNG primitive such as BCryptCreateHash to implement algorithm. The most important is to use flag BCRYPT_ALG_HANDLE_HMAC_FLAG in BCryptOpenAlgorithmProvider:

void pbkdf2()
{
    BCRYPT_ALG_HANDLE hAlg = NULL;
    BCRYPT_HASH_HANDLE hHash = NULL;
    std::vector<BYTE> pass = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
    std::vector<BYTE> salt = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };
    std::vector<BYTE> derived_key(32);
    std::vector<BYTE> dig(32);
    byte t[] = { 0x00, 0x00, 0x00, 0x01 };
    DWORD itcount = 10000;

    SECURITY_STATUS status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM,
        nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG);
    if (status != ERROR_SUCCESS) {
        goto Exit;
    }

    status = BCryptCreateHash(hAlg, &hHash, nullptr, 0, pass.data(), pass.size(), 0);
    if (status != ERROR_SUCCESS) {
        goto Exit;
    }
    status = BCryptHashData(hHash, salt.data(), salt.size(), 0);
    if (status != ERROR_SUCCESS) {
        goto Exit;
    }
    status = BCryptHashData(hHash, t, 4, 0);
    if (status != ERROR_SUCCESS) {
        goto Exit;
    }
    status = BCryptFinishHash(hHash, dig.data(), dig.size(), 0);
    if (status != ERROR_SUCCESS) {
        goto Exit;
    }
    derived_key = dig;
    BCryptDestroyHash(hHash);

    for (DWORD i = 1; i < itcount; ++i)
    {
        status = BCryptCreateHash(hAlg, &hHash, nullptr, 0, pass.data(), pass.size(), 0);
        if (status != ERROR_SUCCESS) {
            goto Exit;
        }
        status = BCryptHashData(hHash, dig.data(), dig.size(), 0);
        if (status != ERROR_SUCCESS) {
            goto Exit;
        }
        status = BCryptFinishHash(hHash, dig.data(), dig.size(), 0);
        if (status != ERROR_SUCCESS) {
            goto Exit;
        }
        BCryptDestroyHash(hHash);
        for (DWORD j = 0; j < dig.size(); ++j) {
            derived_key[j] ^= dig[j];
        }
    }


Exit:
    if (hHash) {
        BCryptDestroyHash(hHash);
    }
    if (hAlg) {
        BCryptCloseAlgorithmProvider(hAlg, 0);
    }
    return;
}

EDIT: to clarify meaning of t[].
According to RFC (5.2):

For each block of the derived key apply the function F defined below to the password P, the salt S, the iteration count c, and the block index to compute the block:

               T_1 = F (P, S, c, 1) ,
               T_2 = F (P, S, c, 2) ,
               ...
               T_l = F (P, S, c, l) ,

     where the function F is defined as the exclusive-or sum of the
     first c iterates of the underlying pseudorandom function PRF
     applied to the password P and the concatenation of the salt S
     and the block index i:  F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c

     where

               U_1 = PRF (P, S || INT (i)) ,
               U_2 = PRF (P, U_1) ,
               ...
               U_c = PRF (P, U_{c-1}) .

     Here, INT (i) is a four-octet encoding of the integer i, most
     significant octet first.

So, in my code t[] - is a four-octet encoding of the integer 1 (for the first iteration), most significant octet first.

plstryagain
  • 686
  • 5
  • 9
  • plstryagain, thanks for your answer. what is the purpose of t[] in your code? i edited my post above to make things clearer – haide Sep 04 '18 at 12:15
  • thanks again for your help, i marked your answer as useful, but unfortunately it doesn't count because my reputation is to low. it was easier for me to convert existing code (see my answer below), because i'm a total newbie regarding this cryptography stuff. – haide Sep 04 '18 at 17:25
0

I took this code, converted the deprecated wincrypt calls to the new CNG API, refactored it and removed the stuff which i don't need.

Although i don't understand what the code is doing, it seems that it is producing the same result than the function in my question which is using BCryptDeriveKeyPBKDF2.

#define NOCRYPT

#include <windows.h>
#include <bcrypt.h>
#include <math.h>
#include <assert.h>

#define DIGEST_SIZE 20
#define BLOCK_SIZE 64

typedef struct
{
    BCRYPT_ALG_HANDLE hAlgorithm;
    BCRYPT_HASH_HANDLE hInnerHash;
    BCRYPT_HASH_HANDLE hOuterHash;
} PRF_CTX;

static void hmacFree(PRF_CTX* pContext)
{
    if (pContext->hOuterHash) BCryptDestroyHash(pContext->hOuterHash);
    if (pContext->hInnerHash) BCryptDestroyHash(pContext->hInnerHash);
    if (pContext->hAlgorithm) BCryptCloseAlgorithmProvider(pContext->hAlgorithm, 0);
}

static BOOL hmacPrecomputeDigest(BCRYPT_HASH_HANDLE hHash, PUCHAR pbPassword, DWORD cbPassword, BYTE mask)
{
    BYTE buffer[BLOCK_SIZE];
    DWORD i;
    assert(cbPassword <= BLOCK_SIZE);

    memset (buffer, mask, sizeof(buffer));

    for (i = 0; i < cbPassword; ++i)
    {
        buffer[i] = (char) (pbPassword[i] ^ mask);
    }

    return BCRYPT_SUCCESS(BCryptHashData(hHash, buffer, sizeof(buffer), 0));
}

static BOOL hmacInit(PRF_CTX* pContext, PUCHAR pbPassword, DWORD cbPassword)
{
    BCRYPT_HASH_HANDLE hHash = NULL;
    BOOL bStatus = FALSE;
    BYTE key[DIGEST_SIZE];

    if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&pContext->hAlgorithm, BCRYPT_SHA1_ALGORITHM, NULL, 0)) ||
        !BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, &pContext->hInnerHash, NULL, 0, NULL, 0, 0)) ||
        !BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, &pContext->hOuterHash, NULL, 0, NULL, 0, 0)))
    {
        goto hmacInit_end;
    }

    if (cbPassword > BLOCK_SIZE)
    {
        ULONG cbResult;
        if (!BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, &hHash, NULL, 0, NULL, 0, 0)) ||
            !BCRYPT_SUCCESS(BCryptHashData(hHash, pbPassword, cbPassword, 0)) ||
            !BCRYPT_SUCCESS(BCryptGetProperty(hHash, BCRYPT_HASH_LENGTH, (PUCHAR)&cbPassword, sizeof(cbPassword), &cbResult, 0)) ||
            !BCRYPT_SUCCESS(BCryptFinishHash(hHash, key, cbPassword, 0)))
        {
            goto hmacInit_end;
        }

        pbPassword = key;
    }

    bStatus =
        hmacPrecomputeDigest(pContext->hInnerHash, pbPassword, cbPassword, 0x36) &&
        hmacPrecomputeDigest(pContext->hOuterHash, pbPassword, cbPassword, 0x5C);

hmacInit_end:

    if (hHash) BCryptDestroyHash(hHash);
    if (bStatus == FALSE) hmacFree(pContext);

    return bStatus;
}

static BOOL hmacCalculateInternal(BCRYPT_HASH_HANDLE hHashTemplate, PUCHAR pbData, DWORD cbData, PUCHAR pbOutput, DWORD cbOutput)
{
    BOOL success = FALSE;
    BCRYPT_HASH_HANDLE hHash = NULL;

    if (BCRYPT_SUCCESS(BCryptDuplicateHash(hHashTemplate, &hHash, NULL, 0, 0)))
    {
        success =
            BCRYPT_SUCCESS(BCryptHashData(hHash, pbData, cbData, 0)) &&
            BCRYPT_SUCCESS(BCryptFinishHash(hHash, pbOutput, cbOutput, 0));

        BCryptDestroyHash(hHash);
    }

    return success;
}

static BOOL hmacCalculate(PRF_CTX* pContext, PUCHAR pbData, DWORD cbData, PUCHAR pbDigest)
{
    return
        hmacCalculateInternal(pContext->hInnerHash, pbData, cbData, pbDigest, DIGEST_SIZE) &&
        hmacCalculateInternal(pContext->hOuterHash, pbDigest, DIGEST_SIZE, pbDigest, DIGEST_SIZE);
}

static void xor(LPBYTE ptr1, LPBYTE ptr2, DWORD dwLen)
{
    while (dwLen--)
        *ptr1++ ^= *ptr2++;
}

BOOL pbkdf2(
    PUCHAR pbPassword, ULONG cbPassword,
    PUCHAR pbSalt, ULONG cbSalt,
    DWORD cIterations,
    PUCHAR pbDerivedKey, ULONG cbDerivedKey)
{
    BOOL bStatus = FALSE;
    DWORD l, r, dwULen, i, j;
    BYTE Ti[DIGEST_SIZE];
    BYTE V[DIGEST_SIZE];
    LPBYTE U = malloc(max((cbSalt + 4), DIGEST_SIZE));
    PRF_CTX prfCtx = { 0 };

    assert(pbPassword != NULL && cbPassword != 0 && pbSalt != NULL && cbSalt != 0);
    assert(cIterations > 0 && pbDerivedKey > 0 && cbDerivedKey > 0);

    if (!hmacInit(&prfCtx, pbPassword, cbPassword))
    {
        goto PBKDF2_end;
    }

    l = (DWORD) ceil((double) cbDerivedKey / (double) DIGEST_SIZE);
    r = cbDerivedKey - (l - 1) * DIGEST_SIZE;

    for (i = 1; i <= l; i++)
    {
        ZeroMemory(Ti, DIGEST_SIZE);
        for (j = 0; j < cIterations; j++)
        {
            if (j == 0)
            {
                // construct first input for PRF
                memcpy(U, pbSalt, cbSalt);
                U[cbSalt] = (BYTE) ((i & 0xFF000000) >> 24);
                U[cbSalt + 1] = (BYTE) ((i & 0x00FF0000) >> 16);
                U[cbSalt + 2] = (BYTE) ((i & 0x0000FF00) >> 8);
                U[cbSalt + 3] = (BYTE) ((i & 0x000000FF));
                dwULen = cbSalt + 4;
            }
            else
            {
                memcpy(U, V, DIGEST_SIZE);
                dwULen = DIGEST_SIZE;
            }

            if (!hmacCalculate(&prfCtx, U, dwULen, V))
            {
                goto PBKDF2_end;
            }

            xor(Ti, V, DIGEST_SIZE);
        }

        if (i != l)
        {
            memcpy(&pbDerivedKey[(i-1) * DIGEST_SIZE], Ti, DIGEST_SIZE);
        }
        else
        {
            // Take only the first r bytes
            memcpy(&pbDerivedKey[(i-1) * DIGEST_SIZE], Ti, r);
        }
    }

    bStatus = TRUE;

PBKDF2_end:

    hmacFree(&prfCtx);
    free(U);
    return bStatus;
}
haide
  • 11
  • 1
  • 5