List Archive

Thread

Thread Index

Message

From: Erwin Haid <erwin.haid%gmx.de@localhost>
To: libzip-discuss%nih.at@localhost
Subject: Re: WinZIP AES changes
Date: Wed, 12 Sep 2018 01:37:59 +0200

Am 16.02.2018 um 08:50 schrieb Dieter Baron:
hi,

we’ve removed the custom AES implementation and instead rely on standard 
cryptographic libraries.

(The version we used was several years old, and the new version isn’t backwards 
compatible. Instead of adapting to a new proprietary API, we preferred to switch to 
standard APIs and use maintained and audited implementations.)

So far we support OpenSSL, GnuTLS and Apple’s native library (CommonCrypto).

We would like to also support the Windows native libraries. Volunteers for 
writing the compatibility layer would be welcome. It should be doable in about 
an hour. zip_random() has also been moved to the crypto backends, since most 
libraries implement it. The Windows versions were removed from the build, but 
the files are still there for reference.

If no supported library can be found, libzip will still build, it just won’t be 
able to read/write WinzipAES encrypted files.

yours,
dillo

Hello,

i have added support for AES encryption using the windows cryptography API.

I haven't worked with CMake and Git before, so i just have attached the source file with the implementation, could someone be so kind to integrate it properly?

I have added a comment at the top of the c file with some explanations, because my target platform is Windows Embedded Compact 2013 which has some restrictions compared to standard desktop/server Windows OSes.

I have compiled the code with VS2017 for Windows 10 and with VS2013 for Windows Embedded Compact 2013 running on an ARM iMX6 CPU. The AES encrypted files created with this code can be extracted with 7-Zip on Windows 10 without problem, but i haven't tested too extensively yet.

best regards,
Erwin
#include <stdlib.h>

#include "zipint.h"
#include "zip_crypto.h"

#define WIN32_LEAN_AND_MEAN
#define NOCRYPT

#include <Windows.h>
#include <bcrypt.h>

#pragma comment(lib, "bcrypt.lib")

/*

This code is using the Cryptography API: Next Generation (CNG)
https://docs.microsoft.com/en-us/windows/desktop/seccng/cng-portal

This API is supported on
 - Windows Vista or later (client OS)
 - Windows Server 2008 (server OS)
 - Windows Embedded Compact 2013 (don't know about Windows Embedded Compact 7)

The code was developed for Windows Embedded Compact 2013 (WEC2013),
but should be working for all of the above mentioned OSes.

There are 2 restrictions for WEC2013, Windows Vista and Windows Server 2008:

1.) The function "BCryptDeriveKeyPBKDF2" is not available

I found some code which is implementing this function using the deprecated 
Crypto API here:
https://www.idrix.fr/Root/content/view/37/54/

I took this code and converted it to the newer CNG API. The original code was 
more
flexible, but this is not needed here so i refactored it a bit and just kept 
what is needed.

The define "HAS_BCRYPTDERIVEKEYPBKDF2" controls whether "BCryptDeriveKeyPBKDF2"
of the CNG API is used or not. This define must not be set if you are compiling 
for WEC2013 or Windows Vista.


2.) "BCryptCreateHash" can't manage the memory needed for the hash object 
internally

On Windows 7 or later it is possible to pass NULL for the hash object buffer.
This is not supported on WEC2013, so we have to handle the memory 
allocation/deallocation ourselves.
There is no #ifdef to control that, because this is working for all supported 
OSes.

*/

#ifndef WINCE
#define HAS_BCRYPTDERIVEKEYPBKDF2
#endif

#ifdef HAS_BCRYPTDERIVEKEYPBKDF2

bool _zip_crypto_pbkdf2(const zip_uint8_t *key, zip_uint64_t key_length, const 
zip_uint8_t *salt, zip_uint16_t salt_length, zip_uint16_t iterations, 
zip_uint8_t* output, zip_uint16_t output_length)
{
        BCRYPT_ALG_HANDLE hAlgorithm = NULL;

        if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlgorithm, 
BCRYPT_SHA1_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG)))
        {
                return false;
        }

        bool result = BCRYPT_SUCCESS(BCryptDeriveKeyPBKDF2(hAlgorithm, 
(PUCHAR)key, (ULONG)key_length, (PUCHAR)salt, salt_length, iterations, output, 
output_length, 0));

        BCryptCloseAlgorithmProvider(hAlgorithm, 0);

        return result;
}

#else

#include <math.h>

#define DIGEST_SIZE 20
#define BLOCK_SIZE 64

typedef struct
{
        BCRYPT_ALG_HANDLE hAlgorithm;
        BCRYPT_HASH_HANDLE hInnerHash;
        BCRYPT_HASH_HANDLE hOuterHash;
        ULONG cbHashObject;
        PUCHAR pbInnerHash;
        PUCHAR pbOuterHash;
} PRF_CTX;

static void hmacFree(PRF_CTX* pContext)
{
        if (pContext->hOuterHash) BCryptDestroyHash(pContext->hOuterHash);
        if (pContext->hInnerHash) BCryptDestroyHash(pContext->hInnerHash);
        free(pContext->pbOuterHash);
        free(pContext->pbInnerHash);
        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;

        if (cbPassword > BLOCK_SIZE)
        {
                return FALSE;
        }

        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)
{
        BOOL bStatus = FALSE;
        ULONG cbResult;
        BYTE key[DIGEST_SIZE];

        if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&pContext->hAlgorithm, 
BCRYPT_SHA1_ALGORITHM, NULL, 0)) ||
                !BCRYPT_SUCCESS(BCryptGetProperty(pContext->hAlgorithm, 
BCRYPT_OBJECT_LENGTH, (PUCHAR)&pContext->cbHashObject, 
sizeof(pContext->cbHashObject), &cbResult, 0)) ||
                ((pContext->pbInnerHash = malloc(pContext->cbHashObject)) == 
NULL) ||
                ((pContext->pbOuterHash = malloc(pContext->cbHashObject)) == 
NULL) ||
                !BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, 
&pContext->hInnerHash, pContext->pbInnerHash, pContext->cbHashObject, NULL, 0, 
0)) ||
                !BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, 
&pContext->hOuterHash, pContext->pbOuterHash, pContext->cbHashObject, NULL, 0, 
0)))
        {
                goto hmacInit_end;
        }

        if (cbPassword > BLOCK_SIZE)
        {
                BCRYPT_HASH_HANDLE hHash = NULL;
                PUCHAR pbHashObject = malloc(pContext->cbHashObject);

                bStatus =
                        BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, 
&hHash, pbHashObject, pContext->cbHashObject, 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));

                if (hHash) BCryptDestroyHash(hHash);
                free(pbHashObject);

                if (!bStatus)
                {
                        goto hmacInit_end;
                }

                pbPassword = key;
        }

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

hmacInit_end:

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

        return bStatus;
}

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

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

                BCryptDestroyHash(hHash);
        }

        free(pbHashObject);

        return success;
}

static BOOL hmacCalculate(PRF_CTX* pContext, PUCHAR pbData, DWORD cbData, 
PUCHAR pbDigest)
{
        DWORD cbResult;
        DWORD cbHashObject;

        return
                BCRYPT_SUCCESS(BCryptGetProperty(pContext->hAlgorithm, 
BCRYPT_OBJECT_LENGTH, (PUCHAR)&cbHashObject, sizeof(cbHashObject), &cbResult, 
0)) &&
                hmacCalculateInternal(pContext->hInnerHash, pbData, cbData, 
pbDigest, DIGEST_SIZE, cbHashObject) &&
                hmacCalculateInternal(pContext->hOuterHash, pbDigest, 
DIGEST_SIZE, pbDigest, DIGEST_SIZE, cbHashObject);
}

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

        if (pbPassword == NULL || cbPassword == 0 || pbSalt == NULL || cbSalt 
== 0 || cIterations == 0 || pbDerivedKey == NULL || cbDerivedKey == 0)
        {
                return FALSE;
        }

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

bool _zip_crypto_pbkdf2(const zip_uint8_t *key, zip_uint64_t key_length, const 
zip_uint8_t *salt, zip_uint16_t salt_length, zip_uint16_t iterations, 
zip_uint8_t* output, zip_uint16_t output_length)
{
        return
                (key_length <= ZIP_UINT32_MAX) &&
                pbkdf2((PUCHAR)key, (ULONG)key_length, (PUCHAR)salt, 
salt_length, iterations, output, output_length);
}

#endif


struct _zip_crypto_aes_s
{
        BCRYPT_ALG_HANDLE hAlgorithm;
        BCRYPT_KEY_HANDLE hKey;
        ULONG cbKeyObject;
        PUCHAR pbKeyObject;
};

_zip_crypto_aes_t *_zip_crypto_aes_new(const zip_uint8_t *key, zip_uint16_t 
key_size, zip_error_t *error)
{
        _zip_crypto_aes_t *aes = (_zip_crypto_aes_t *)calloc(1, sizeof(*aes));

        ULONG cbResult;
        ULONG key_length = key_size / 8;

        if (aes == NULL)
        {
                zip_error_set(error, ZIP_ER_MEMORY, 0);
                return NULL;
        }

        if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&aes->hAlgorithm, 
BCRYPT_AES_ALGORITHM, NULL, 0)))
        {
                _zip_crypto_aes_free(aes);
                return NULL;
        }

        if (!BCRYPT_SUCCESS(BCryptSetProperty(aes->hAlgorithm, 
BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_ECB, 
sizeof(BCRYPT_CHAIN_MODE_ECB), 0)))
        {
                _zip_crypto_aes_free(aes);
                return NULL;
        }

        if (!BCRYPT_SUCCESS(BCryptGetProperty(aes->hAlgorithm, 
BCRYPT_OBJECT_LENGTH, (PUCHAR)&aes->cbKeyObject, sizeof(aes->cbKeyObject), 
&cbResult, 0)))
        {
                _zip_crypto_aes_free(aes);
                return NULL;
        }

        aes->pbKeyObject = malloc(aes->cbKeyObject);

        if (!BCRYPT_SUCCESS(BCryptGenerateSymmetricKey(aes->hAlgorithm, 
&aes->hKey, aes->pbKeyObject, aes->cbKeyObject, (PUCHAR)key, key_length, 0)))
        {
                _zip_crypto_aes_free(aes);
                return NULL;
        }

        return aes;
}

void _zip_crypto_aes_free(_zip_crypto_aes_t *aes)
{
        if (aes == NULL)
        {
                return;
        }

        if (aes->hKey != NULL)
        {
                BCryptDestroyKey(aes->hKey);
        }

        if (aes->pbKeyObject != NULL)
        {
                free(aes->pbKeyObject);
        }

        if (aes->hAlgorithm != NULL)
        {
                BCryptCloseAlgorithmProvider(aes->hAlgorithm, 0);
        }

        free(aes);
}

bool _zip_crypto_aes_encrypt_block(_zip_crypto_aes_t *aes, const zip_uint8_t 
*in, zip_uint8_t *out)
{
        ULONG cbResult;
        NTSTATUS status = BCryptEncrypt(aes->hKey, (PUCHAR)in, 
ZIP_CRYPTO_AES_BLOCK_LENGTH, NULL, NULL, 0, (PUCHAR)out, 
ZIP_CRYPTO_AES_BLOCK_LENGTH, &cbResult, 0);
        return BCRYPT_SUCCESS(status);
}

struct _zip_crypto_hmac_s
{
        BCRYPT_ALG_HANDLE hAlgorithm;
        BCRYPT_HASH_HANDLE hHash;
        DWORD cbHashObject;
        PUCHAR pbHashObject;
        DWORD cbHash;
        PUCHAR pbHash;
};

// 
https://code.msdn.microsoft.com/windowsdesktop/Hmac-Computation-Sample-11fe8ec1/sourcecode?fileId=42820&pathId=283874677

_zip_crypto_hmac_t *_zip_crypto_hmac_new(const zip_uint8_t *secret, 
zip_uint64_t secret_length, zip_error_t *error)
{
        NTSTATUS status;
        ULONG cbResult;
        _zip_crypto_hmac_t *hmac;

        if (secret_length > INT_MAX)
        {
                zip_error_set(error, ZIP_ER_INVAL, 0);
                return NULL;
        }

        hmac = (_zip_crypto_hmac_t *)calloc(1, sizeof(*hmac));

        if (hmac == NULL)
        {
                zip_error_set(error, ZIP_ER_MEMORY, 0);
                return NULL;
        }

        status = BCryptOpenAlgorithmProvider(&hmac->hAlgorithm, 
BCRYPT_SHA1_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG);
        if (!BCRYPT_SUCCESS(status))
        {
                _zip_crypto_hmac_free(hmac);
                return NULL;
        }

        status = BCryptGetProperty(hmac->hAlgorithm, BCRYPT_OBJECT_LENGTH, 
(PUCHAR)&hmac->cbHashObject, sizeof(hmac->cbHashObject), &cbResult, 0);
        if (!BCRYPT_SUCCESS(status))
        {
                _zip_crypto_hmac_free(hmac);
                return NULL;
        }

        hmac->pbHashObject = malloc(hmac->cbHashObject);

        status = BCryptGetProperty(hmac->hAlgorithm, BCRYPT_HASH_LENGTH, 
(PUCHAR)&hmac->cbHash, sizeof(hmac->cbHash), &cbResult, 0);
        if (!BCRYPT_SUCCESS(status))
        {
                _zip_crypto_hmac_free(hmac);
                return NULL;
        }

        hmac->pbHash = malloc(hmac->cbHash);

        status = BCryptCreateHash(hmac->hAlgorithm, &hmac->hHash, 
hmac->pbHashObject, hmac->cbHashObject, (PUCHAR)secret, (ULONG)secret_length, 
0);
        if (!BCRYPT_SUCCESS(status))
        {
                _zip_crypto_hmac_free(hmac);
                return NULL;
        }

        return hmac;
}

void _zip_crypto_hmac_free(_zip_crypto_hmac_t *hmac)
{
        if (hmac == NULL)
        {
                return;
        }

        if (hmac->hHash != NULL)
        {
                BCryptDestroyHash(hmac->hHash);
        }

        if (hmac->pbHash != NULL)
        {
                free(hmac->pbHash);
        }

        if (hmac->pbHashObject != NULL)
        {
                free(hmac->pbHashObject);
        }

        if (hmac->hAlgorithm)
        {
                BCryptCloseAlgorithmProvider(hmac->hAlgorithm, 0);
        }

        free(hmac);
}

bool _zip_crypto_hmac(_zip_crypto_hmac_t *hmac, zip_uint8_t *data, zip_uint64_t 
length)
{
        if (hmac == NULL || length > ULONG_MAX)
        {
                return false;
        }

        return BCRYPT_SUCCESS(BCryptHashData(hmac->hHash, data, (ULONG)length, 
0));
}

bool _zip_crypto_hmac_output(_zip_crypto_hmac_t *hmac, zip_uint8_t *data)
{
        if (hmac == NULL)
        {
                return false;
        }

        return BCRYPT_SUCCESS(BCryptFinishHash(hmac->hHash, data, hmac->cbHash, 
0));
}

ZIP_EXTERN bool
zip_random(zip_uint8_t *buffer, zip_uint16_t length)
{
        return BCRYPT_SUCCESS(BCryptGenRandom(NULL, buffer, length, 
BCRYPT_USE_SYSTEM_PREFERRED_RNG));
}
#ifndef HAD_ZIP_CRYPTO_WIN_H
#define HAD_ZIP_CRYPTO_WIN_H

typedef struct _zip_crypto_aes_s _zip_crypto_aes_t;
typedef struct _zip_crypto_hmac_s _zip_crypto_hmac_t;

void _zip_crypto_aes_free(_zip_crypto_aes_t *aes);
_zip_crypto_aes_t *_zip_crypto_aes_new(const zip_uint8_t *key, zip_uint16_t 
key_size, zip_error_t *error);
bool _zip_crypto_aes_encrypt_block(_zip_crypto_aes_t *aes, const zip_uint8_t 
*in, zip_uint8_t *out);

bool _zip_crypto_pbkdf2(
        const zip_uint8_t *key,
        zip_uint64_t key_length,
        const zip_uint8_t *salt,
        zip_uint16_t salt_length,
        zip_uint16_t iterations,
        zip_uint8_t* output,
        zip_uint16_t output_length);

_zip_crypto_hmac_t *_zip_crypto_hmac_new(const zip_uint8_t *secret, 
zip_uint64_t secret_length, zip_error_t *error);
void _zip_crypto_hmac_free(_zip_crypto_hmac_t *hmac);
bool _zip_crypto_hmac(_zip_crypto_hmac_t *hmac, zip_uint8_t *data, zip_uint64_t 
length);
bool _zip_crypto_hmac_output(_zip_crypto_hmac_t *hmac, zip_uint8_t *data);

#endif /*  HAD_ZIP_CRYPTO_WIN_H */

Made by MHonArc.