haide
haide

Reputation: 11

BCryptDeriveKeyPBKDF2 replacement for Windows Embedded Compact 2013

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

Upvotes: 0

Views: 1051

Answers (2)

haide
haide

Reputation: 11

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

Upvotes: 0

plstryagain
plstryagain

Reputation: 726

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.

Upvotes: 0

Related Questions