AES/Rijndael in C++ Microsoft CryptoAPI - c++

How can I convert the following cryptography code (VB.NET 4.0) to the C++ equivalent one, using Microsoft CryptoAPI (CryptDeriveKey, BCrypt[...] functions, CryptAcquireContext, etc.)? (I haven't found a single article on the Internet describing AES using Microsoft CryptoAPI...)
Dim Key(31) As Byte
Dim IV(15) As Byte
Array.Copy(SomeByteArray, IV, 16)
Array.Copy((New SHA512Managed).ComputeHash(SomeByteArray), Key, 32)
Using AESEncr As New RijndaelManaged() With {.Padding = PaddingMode.ISO10126}
FinalEncrypted = AESEncr.CreateEncryptor(Key, IV).TransformFinalBlock(AnotherByteArray, 0, AnotherByteArray.GetLength(0))
End Using
and the decrypting one:
Dim Key(31) As Byte
Dim IV(15) As Byte
Array.Copy(SomeByteArray, IV, 16)
Array.Copy((New SHA512Managed).ComputeHash(SomeByteArray), Key, 32)
Using AESEncr As New RijndaelManaged() With {.Padding = PaddingMode.ISO10126}
FinalDecrypted = AESEncr.CreateDecryptor(Key, IV).TransformFinalBlock(FinalEncrypted, 0, FinalEncrypted.GetLength(0))
End Using
(Note: I already have C++ code about the SHA-512 method, so don't bother with that.)

So, the code I made for AES-256 Encryption/Decryption is the following: (it takes BYTE* Data and BYTE* IV as parameters)
BYTE *hash, *res;
HCRYPTPROV hCrypt = NULL;
HCRYPTKEY hKey = NULL;
struct {
BLOBHEADER hdr;
DWORD len;
BYTE key[32];
} key_blob;
key_blob.hdr.bType = PLAINTEXTKEYBLOB;
key_blob.hdr.bVersion = CUR_BLOB_VERSION;
key_blob.hdr.reserved = 0;
key_blob.hdr.aiKeyAlg = CALG_AES_256;
key_blob.len = 32;
hash = ComputeSHA512Hash(IV);
copy(hash, hash + 32, key_blob.key);
res = new BYTE[16];
copy(Data, Data + 15, res);
res[15] = 0;
// Get the Microsoft Enhanced RSA and AES Cryptographic Service Provider
if (!CryptAcquireContext(&hCrypt, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0))
throw E_FAIL;
// Import our key blob
if (!CryptImportKey(hCrypt, (BYTE *)&key_blob, sizeof(key_blob), NULL, 0, &hKey))
throw E_FAIL;
// Set the mode to Cipher Block Chaining
DWORD dwMode = CRYPT_MODE_CBC;
if (!CryptSetKeyParam(hKey, KP_MODE, (BYTE *)&dwMode, 0))
throw E_FAIL;
// Set the Initialization Vector to ours
if (!CryptSetKeyParam(hKey, KP_IV, IV, 0))
throw E_FAIL;
// Do the main encryption
DWORD pdwDataLen = 15;
if (!CryptEncrypt(hKey, NULL, TRUE, 0, res, &pdwDataLen, 16))
throw E_FAIL;
// Do the main decryption
pdwDataLen = 16;
if (!CryptDecrypt(hKey, NULL, TRUE, 0, res, &pdwDataLen))
throw E_FAIL;
// Destroy whatever was created before (free memory)
delete hash;
delete res;
if (hKey)
CryptDestroyKey(hKey);
if (hCrypt)
CryptReleaseContext(hCrypt, 0);
As I previously said, I already have code for the ComputeSHA512Hash() function, so my code is complete for my purposes. I hope this code will be useful for everyone wanting to write AES-256 code.

Related

Windows CAPI CryptDecrypt error key not working

I am trying to decrypt data from openssl generated private key. I have converted the public key from PEM to DER format, but when i use CryptDecrypt it throws error key does not exist. I have previously used the same method to encrypt data and decrypt using openssl, i am also aware regarding the Endianess difference in openssl and wincrypto. Here is the code if someone can point out where i am going wrong.
#include <Windows.h>
#include <wincrypt.h>
#include <stdio.h>
#pragma comment(lib, "Crypt32.lib")
char default_pub_key[] =
"-----BEGIN PUBLIC KEY-----"
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVTOXB/Ti8SFvP42Z1XFB6GQ+R"
"jnqs42XiTFRXWpsSTlSPMRHi8aXpf1KYzzKHMC+4hU3rrgdbOu8bl7FekDoy38No"
"PX8ACoEmRhdn8mXs+ftmIRCuEE44mtgWUme65A1nTyT8nRmAVF6roo/rry+Xkbe9"
"iC6vRBRbVzprmCv7jwIDAQAB"
"-----END PUBLIC KEY-----";
HCRYPTPROV hProv = NULL;
HCRYPTKEY hKey = NULL;
DWORD dwKeySize = 0;
void SwapBytes(char* pv, size_t n) {
char* p = pv;
size_t lo, hi;
for (lo = 0, hi = n - 1; hi > lo; lo++, hi--)
{
char tmp = p[lo];
p[lo] = p[hi];
p[hi] = tmp;
}
}
int init_crypto()
{
LPBYTE pbBuffer;
DWORD dwKeyBlob, dw_pub_key_len = 0;
unsigned int offset = 22; // 22 = 1024, 24 = 2048 and so on
DWORD dwParamSize = sizeof(DWORD);
CERT_PUBLIC_KEY_INFO* publicKeyInfo;
CryptStringToBinaryA(default_pub_key, 0, CRYPT_STRING_ANY, NULL, &dw_pub_key_len, NULL, NULL);
pbBuffer = (LPBYTE)GlobalAlloc(GPTR, dw_pub_key_len);
CryptStringToBinaryA(default_pub_key, 0, CRYPT_STRING_ANY, pbBuffer, &dw_pub_key_len, NULL, NULL);
dwKeyBlob = 0;
CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, pbBuffer, dw_pub_key_len, 0, NULL, NULL, &dwKeyBlob);
publicKeyInfo = (CERT_PUBLIC_KEY_INFO*)GlobalAlloc(GPTR, dwKeyBlob);
CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, pbBuffer, dw_pub_key_len, 0, NULL, publicKeyInfo, &dwKeyBlob);
CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING, publicKeyInfo, &hKey);
CryptGetKeyParam(hKey, KP_KEYLEN, (BYTE*)&dwKeySize, &dwParamSize, 0);
char da[16];
memset(da, 0, sizeof(da));
wsprintfA(da, "%d", dwKeySize);
MessageBoxA(NULL, da, NULL, MB_OK);
dwKeySize /= 8;
return 0;
}
int main(int argc, char** argv)
{
// start rsa crypto key
init_crypto();
// Read encrypted data
DWORD junk;
HANDLE hEncFile = CreateFile(L"test_enc.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD EncFileSize = GetFileSize(hEncFile, 0);
char* EncFileBuf = (char*)GlobalAlloc(GPTR, EncFileSize + 1);
ReadFile(hEncFile, EncFileBuf, EncFileSize, &junk, NULL);
CloseHandle(hEncFile);
// convert for win32
SwapBytes((char*)EncFileBuf, EncFileSize);
CryptDecrypt(hKey, 0, TRUE, 0,(PBYTE)EncFileBuf, &EncFileSize);
hEncFile = CreateFile(L"test_dec.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hEncFile, EncFileBuf, EncFileSize, &junk, NULL);
CloseHandle(hEncFile);
GlobalFree(EncFileBuf);
// Do proper cleanup
return 0;
}
CryptDecrypt
[in] hKey
A handle to the key to use for the decryption. An application obtains this handle by using either the CryptGenKey or CryptImportKey function.
You pass a wrong hKey to CryptDecrypt. Use CryptImportKey after CryptImportPublicKeyInfo for getting an expected hKey and pass it to CryptDecrypt.

Straight forward example using CryptoAPI Next Generation (CNG) to encrypt data

I'd like to implement data encryption and decryption in a C++ application running on Windows. I've spent considerable time looking around the Web and am thinking I should probably use the Windows Cryptography API: Next Generation (CNG) functions (although I'm open to better alternatives).
What I find everywhere are complex examples that do all sorts of stuff. I don't feel that confident in this area and so I'd like to find a simple example. In the end, I need a method that takes a string and encrypts, and another methods that decrypts the data back to the string. The user would supply a password for both operations.
This must have been done countless times already. Can anyone point me to a complete and competent example? Ultimately, I'll end up with an Encrypt() and Decrypt() method.
Something that is both secure and performant would be ideal.
Before encrypting (and decrypting) you need to derive key from password with key derivation functions (for example PBKDF2 with SHA256). To prevent pre-computed dictionary attacks
in additional to password you will also need random string (called salt).
Next pick cipher algorithm (AES with 256-bit key is good one) and cipher mode (ECB cipher mode considered weak, so use any other for example CBC). Also it will require one more random string (called initialization vector).
So encrypting algorithm will be:
Generate random salt
Derive key(password, salt) = key
Generate random IV
Encrypt(key, IV, plain text) = cipher text
Input parameters: plain text, password
Output parameters: cipher text, salt, IV
Decrypting algorithm will be:
Derive key(password, salt) = key
Decrypt(key, iv, cipher text) = plain text
Input parameters: cipher text, salt, iv, password
Output parameters: plain text
Sample code:
#include <Windows.h>
#include <iostream>
#include <vector>
#include <array>
#pragma comment(lib, "bcrypt")
static NTSTATUS gen_random(BYTE* buf, ULONG buf_len)
{
BCRYPT_ALG_HANDLE hAlg = nullptr;
NTSTATUS status = NTE_FAIL;
do {
status = BCryptOpenAlgorithmProvider(&hAlg, L"RNG", nullptr, 0);
if (status != ERROR_SUCCESS) {
return status;
}
status = BCryptGenRandom(hAlg, buf, buf_len, 0);
} while (0);
if (hAlg) {
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return status;
}
static NTSTATUS derive_key(BYTE* pass, ULONG pass_len, BYTE* salt,
ULONG salt_len, const ULONG iteration, BYTE* derived_key, ULONG derived_key_len)
{
BCRYPT_ALG_HANDLE hPrf = nullptr;
NTSTATUS status = ERROR_SUCCESS;
do {
status = BCryptOpenAlgorithmProvider(&hPrf, L"SHA256", nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG);
if (status != ERROR_SUCCESS) {
break;
}
status = BCryptDeriveKeyPBKDF2(hPrf, pass, pass_len, salt, salt_len, iteration, derived_key, derived_key_len, 0);
} while (0);
if (hPrf) {
BCryptCloseAlgorithmProvider(hPrf, 0);
}
return status;
}
static NTSTATUS do_encrypt(BYTE* key, ULONG key_len, BYTE* plain_text, ULONG plain_text_len,
std::vector<BYTE>& iv, std::vector<BYTE>& cipher_text)
{
NTSTATUS status = NTE_FAIL;
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_KEY_HANDLE hKey = nullptr;
do {
status = BCryptOpenAlgorithmProvider(&hAlg, L"AES", nullptr, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* create key object */
status = BCryptGenerateSymmetricKey(hAlg, &hKey, nullptr, 0, key, key_len, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* set chaining mode */
std::wstring mode = BCRYPT_CHAIN_MODE_CBC;
BYTE* ptr = reinterpret_cast<BYTE*>(const_cast<wchar_t*>(mode.data()));
ULONG size = static_cast<ULONG>(sizeof(wchar_t) * (mode.size() + 1));
status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, ptr, size, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* generate iv */
ULONG block_len = 0;
ULONG res = 0;
status = BCryptGetProperty(hAlg, BCRYPT_BLOCK_LENGTH, reinterpret_cast<BYTE*>(&block_len), sizeof(block_len), &res, 0);
if (status != ERROR_SUCCESS) {
break;
}
iv.resize(block_len);
status = gen_random(iv.data(), static_cast<ULONG>(iv.size()));
if (status != ERROR_SUCCESS) {
break;
}
/* BCryptEncrypt modify iv parameter, so we need to make copy */
std::vector<BYTE> iv_copy = iv;
/* get cipher text length */
ULONG cipher_text_len = 0;
status = BCryptEncrypt(hKey, plain_text, plain_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
nullptr, cipher_text_len, &cipher_text_len, BCRYPT_BLOCK_PADDING);
if (status != ERROR_SUCCESS) {
break;
}
cipher_text.resize(static_cast<size_t>(cipher_text_len));
/* now encrypt */
status = BCryptEncrypt(hKey, plain_text, plain_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
cipher_text.data(), cipher_text_len, &cipher_text_len, BCRYPT_BLOCK_PADDING);
} while (0);
/* cleanup */
if (hKey) {
BCryptDestroyKey(hKey);
}
if (hAlg) {
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return status;
}
static NTSTATUS do_decrypt(BYTE* key, ULONG key_len, BYTE* cipher_text, ULONG cipher_text_len,
const std::vector<BYTE>& iv, std::vector<BYTE>& plain_text)
{
NTSTATUS status = NTE_FAIL;
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_KEY_HANDLE hKey = nullptr;
do {
status = BCryptOpenAlgorithmProvider(&hAlg, L"AES", nullptr, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* create key object */
status = BCryptGenerateSymmetricKey(hAlg, &hKey, nullptr, 0, key, key_len, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* set chaining mode */
std::wstring mode = BCRYPT_CHAIN_MODE_CBC;
BYTE* ptr = reinterpret_cast<BYTE*>(const_cast<wchar_t*>(mode.data()));
ULONG size = static_cast<ULONG>(sizeof(wchar_t) * (mode.size() + 1));
status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, ptr, size, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* BCryptEncrypt modify iv parameter, so we need to make copy */
std::vector<BYTE> iv_copy = iv;
/* get expected plain text length */
ULONG plain_text_len = 0;
status = BCryptDecrypt(hKey, cipher_text, cipher_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
nullptr, plain_text_len, &plain_text_len, BCRYPT_BLOCK_PADDING);
plain_text.resize(static_cast<size_t>(plain_text_len));
/* decrypt */
status = BCryptDecrypt(hKey, cipher_text, cipher_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
plain_text.data(), plain_text_len, &plain_text_len, BCRYPT_BLOCK_PADDING);
/* actualize size */
plain_text.resize(static_cast<size_t>(plain_text_len));
} while (0);
/* cleanup */
if (hKey) {
BCryptDestroyKey(hKey);
}
if (hAlg) {
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return status;
}
NTSTATUS encrypt(BYTE* pass, ULONG pass_len, const std::vector<BYTE>& plain_text,
std::vector<BYTE>& salt, std::vector<BYTE>& iv, std::vector<BYTE>& cipher_text)
{
NTSTATUS status = NTE_FAIL;
salt.resize(8);
std::array<BYTE, 32> key{0x00};
do {
/* generate salt */
status = gen_random(salt.data(), static_cast<ULONG>(salt.size()));
if (status != ERROR_SUCCESS) {
break;
}
/* derive key from password using SHA256 algorithm and 20000 iteration */
status = derive_key(pass, pass_len, salt.data(), static_cast<ULONG>(salt.size()), 20000, key.data(), key.size());
if (status != ERROR_SUCCESS) {
break;
}
/* encrypt */
status = do_encrypt(key.data(), static_cast<ULONG>(key.size()), const_cast<BYTE*>(plain_text.data()),
static_cast<ULONG>(plain_text.size()), iv, cipher_text);
} while (0);
SecureZeroMemory(key.data(), key.size());
return status;
}
NTSTATUS decrypt(BYTE* pass, ULONG pass_len, const std::vector<BYTE>& salt, const std::vector<BYTE>& iv,
const std::vector<BYTE>& cipher_text, std::vector<BYTE>& plain_text)
{
NTSTATUS status = NTE_FAIL;
std::array<BYTE, 32> key{0x00};
do {
/* derive key from password using same algorithm, salt and iteraion count */
status = derive_key(pass, pass_len, const_cast<BYTE*>(salt.data()), static_cast<ULONG>(salt.size()),
20000, key.data(), key.size());
if (status != ERROR_SUCCESS) {
break;
}
/* decrypt */
status = do_decrypt(key.data(), static_cast<ULONG>(key.size()), const_cast<BYTE*>(cipher_text.data()),
static_cast<ULONG>(cipher_text.size()), const_cast<BYTE*>(iv.data()),
static_cast<ULONG>(iv.size()), plain_text);
} while (0);
SecureZeroMemory(key.data(), key.size());
return status;
}
How to use CryptoAPI Next Generation (CNG): Don't use it. Use OTP.
If your user is physically sent the key, then use the simple but unbreakable OTP or One Time Pad (See https://en.wikipedia.org/wiki/One-time_pad).
It is very easy to use.
It is very easy to understand.
Simply supply enough OTP keys to the user, which you or someone randomly types in via a keyboard (I told you it is easy), to last for what you think is a reasonable use by the user.
Do NOT derive the keys from passwords or from anything else that the previous posters listed. That is lazy and is NOT secure. Make the keys as I said and then they will NOT be linked to any code source at all.
Do NOT waste your time with "learn some of the fundamentals of encryption and cryptography" (if that learning is not directly in support of your using One Time Pads).
For you to read some discussions and definitions related to OTP's:
https://www.slideshare.net/AsadAli108/3-l4
https://www.slideshare.net/Jonlitan/one-time-pad-encryption-technique
There is no commonly known encryption that even comes close to OTPs. Example: OTPs can not be mathematically or computationally broken down into a mathematical or computational process.
I think that you have asked for simple, and easy, and not so complicated, but secure, and One Time Pads are all that. If you supply the keys to the user directly, then no human (other than Christians with enough faith) can break them.
Some links to help you with hopefully useful independent examples in C and C++:
A quick search gave me these examples which due to the limit on time for this bounty I have not tested:
https://www.sanfoundry.com/cpp-program-implement-one-time-pad-algorithm/
and
http://www.cplusplus.com/forum/beginner/179981/
and
https://github.com/DDomjosa/One-time-pad-encryption/blob/master/One%20time%20pad%20encryption.cpp
ps: In case you are wondering why I post but do not reply to comments: My browser does not seem to support the "add a comment" on these Stack Overflow pages, so I can post but I cannot (until SO makes their pages backward compatible sufficient for me) reply or comment beyond that.

Crypto API RSA public key can decrypt data, is not asymmetric as expected

The problem I am encountering is that I am able to decrypt data using the same RSA 2048-bit public key that was used to encrypt the data. It seems to me that this defeats the entire purpose of encrypting the data in the first place, if a public key can decrypt it. The only thing I can consider at this time is that I'm generating symmetric key exchange pairs when I think I'm generating asymmetric pairs.
The end-user purpose of this is to use it later for transmitting user credentials to be authenticated when using an application away from the office, when I am unable to use their cached credentials from their workstations on the domain. I would theoretically be able to then decrypt these credentials using only the private key.
I have produced a simple test class and code to reproduce my problem. The steps I'm taking are as follows:
Acquire a context to Microsoft Enhanced Cryptographic Provider v1.0
Generate a public / private key pair.
Export the public and private key BLOBs to separate files.
Load up the public key and encrypt some simple text.
Attempt to decrypt the same encrypted text using the public key (I expected it to fail here except for when I'm using the private key - yet both work).
TestEncryptDecrypt helper class: TestEncryptDecrypt.h
#pragma once
#include <Windows.h>
#include <wincrypt.h>
class TestEncryptDecrypt
{
public:
TestEncryptDecrypt()
{
}
~TestEncryptDecrypt()
{
if (hKey != NULL)
CryptDestroyKey(hKey);
if (hProvider != NULL)
CryptReleaseContext(hProvider, 0);
}
BOOL InitializeProvider(LPCTSTR pszProvider, DWORD dwProvType)
{
if (hProvider != NULL)
{
if (!CryptReleaseContext(hProvider, 0))
return 0;
}
return CryptAcquireContext(&hProvider, NULL, pszProvider, dwProvType, 0);
}
BOOL Generate2048BitKeys(ALG_ID Algid)
{
DWORD dwFlags = (0x800 << 16) | CRYPT_EXPORTABLE;
return CryptGenKey(hProvider, Algid, dwFlags, &hKey);
}
VOID ExportPrivatePublicKey(LPTSTR lpFileName)
{
if (hKey == NULL)
return;
DWORD dwDataLen = 0;
BOOL exportResult = CryptExportKey(hKey, NULL, PRIVATEKEYBLOB, 0, NULL, &dwDataLen);
LPBYTE lpKeyBlob = (LPBYTE)malloc(dwDataLen);
exportResult = CryptExportKey(hKey, NULL, PRIVATEKEYBLOB, 0, lpKeyBlob, &dwDataLen);
WriteBytesFile(lpFileName, lpKeyBlob, dwDataLen);
free(lpKeyBlob);
}
VOID ExportPublicKey(LPTSTR lpFileName)
{
if (hKey == NULL)
return;
DWORD dwDataLen = 0;
BOOL exportResult = CryptExportKey(hKey, NULL, PUBLICKEYBLOB, 0, NULL, &dwDataLen);
LPBYTE lpKeyBlob = (LPBYTE)malloc(dwDataLen);
exportResult = CryptExportKey(hKey, NULL, PUBLICKEYBLOB, 0, lpKeyBlob, &dwDataLen);
WriteBytesFile(lpFileName, lpKeyBlob, dwDataLen);
free(lpKeyBlob);
}
BOOL ImportKey(LPTSTR lpFileName)
{
if (hProvider == NULL)
return 0;
if (hKey != NULL)
CryptDestroyKey(hKey);
LPBYTE lpKeyContent = NULL;
DWORD dwDataLen = 0;
ReadBytesFile(lpFileName, &lpKeyContent, &dwDataLen);
BOOL importResult = CryptImportKey(hProvider, lpKeyContent, dwDataLen, 0, 0, &hKey);
delete[] lpKeyContent;
return importResult;
}
BOOL EncryptDataWriteToFile(LPTSTR lpSimpleDataToEncrypt, LPTSTR lpFileName)
{
DWORD SimpleDataToEncryptLength = _tcslen(lpSimpleDataToEncrypt)*sizeof(TCHAR);
DWORD BufferLength = SimpleDataToEncryptLength * 10;
BYTE *EncryptedBuffer = new BYTE[BufferLength];
SecureZeroMemory(EncryptedBuffer, BufferLength);
CopyMemory(EncryptedBuffer, lpSimpleDataToEncrypt, SimpleDataToEncryptLength);
BOOL cryptResult = CryptEncrypt(hKey, NULL, TRUE, 0, EncryptedBuffer, &SimpleDataToEncryptLength, BufferLength);
DWORD dwGetLastError = GetLastError();
WriteBytesFile(lpFileName, EncryptedBuffer, SimpleDataToEncryptLength);
delete[] EncryptedBuffer;
return cryptResult;
}
BOOL DecryptDataFromFile(LPBYTE *lpDecryptedData, LPTSTR lpFileName, DWORD *dwDecryptedLen)
{
if (hKey == NULL)
return 0;
LPBYTE lpEncryptedData = NULL;
DWORD dwDataLen = 0;
ReadBytesFile(lpFileName, &lpEncryptedData, &dwDataLen);
BOOL decryptResult = CryptDecrypt(hKey, NULL, TRUE, 0, lpEncryptedData, &dwDataLen);
*dwDecryptedLen = dwDataLen;
//WriteBytesFile(L"decryptedtest.txt", lpEncryptedData, dwDataLen);
*lpDecryptedData = new BYTE[dwDataLen + 1];
SecureZeroMemory(*lpDecryptedData, dwDataLen + 1);
CopyMemory(*lpDecryptedData, lpEncryptedData, dwDataLen);
delete[]lpEncryptedData;
return decryptResult;
}
VOID WriteBytesFile(LPTSTR lpFileName, BYTE *content, DWORD dwDataLen)
{
HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, 0x7, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwBytesWritten = 0;
WriteFile(hFile, content, dwDataLen, &dwBytesWritten, NULL);
CloseHandle(hFile);
}
private:
HCRYPTPROV hProvider = NULL;
HCRYPTKEY hKey = NULL;
VOID ReadBytesFile(LPTSTR lpFileName, BYTE **content, DWORD *dwDataLen)
{
HANDLE hFile = CreateFile(lpFileName, GENERIC_READ, 0x7, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwFileLength = 0;
DWORD dwBytesToRead = GetFileSize(hFile, NULL);
DWORD dwBytesRead = 0;
*content = new BYTE[dwBytesToRead + 1];
SecureZeroMemory(*content, dwBytesToRead + 1);
ReadFile(hFile, *content, dwBytesToRead, &dwBytesRead, NULL);
*dwDataLen = dwBytesRead;
CloseHandle(hFile);
}
};
Test Code: Main .cpp file
#include "stdafx.h"
#include "TestEncryptDecrypt.h"
#include <Windows.h>
#include <wincrypt.h>
int main()
{
TestEncryptDecrypt *edc = new TestEncryptDecrypt();
//Initialize the provider
edc->InitializeProvider(MS_ENHANCED_PROV, PROV_RSA_FULL);
//Generate a 2048-bit asymmetric key pair
edc->Generate2048BitKeys(CALG_RSA_KEYX);
//Export the private / public key pair
edc->ExportPrivatePublicKey(L"privpubkey.txt");
//Export only the public key
edc->ExportPublicKey(L"pubkey.txt");
//Import the public key (destroys the private/public key pair already set)
edc->ImportKey(L"pubkey.txt");
//Encrypt and write some test data to file
edc->EncryptDataWriteToFile(TEXT("Hello World!ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"), L"encrypteddata.txt");
//Decrypt the data from file using the same public key (this should fail but it doesn't)
DWORD dwDataLen = 0;
LPBYTE lpDecryptedData = NULL;
edc->DecryptDataFromFile(&lpDecryptedData, L"encrypteddata.txt", &dwDataLen);
//Write the supposedly decrypted data to another file
edc->WriteBytesFile(L"decrypteddata.txt", lpDecryptedData, dwDataLen);
//Clear data
delete[] lpDecryptedData;
delete edc;
return 0;
}
Unfortunately I don't get the opportunity to work with C++ very often so you may notice some problems. Feel free to constructively criticize.
Does anyone know why I am able to decrypt data using the same public key?
My goal is to be able to irreversibly encrypt something on the client side where it can only be decrypted on the server, where the private key will hide.
Edit:
I had considered that the hKey wasn't being destroyed properly by the ImportKey method, so I wrote this test case instead (same results - public key can encrypt and decrypt the data):
// CPPTests.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "TestEncryptDecrypt.h"
#include <Windows.h>
#include <wincrypt.h>
int main()
{
TestEncryptDecrypt *edc = new TestEncryptDecrypt();
//Initialize the provider
edc->InitializeProvider(MS_ENHANCED_PROV, PROV_RSA_FULL);
//Generate a 2048-bit asymmetric key pair
edc->Generate2048BitKeys(CALG_RSA_KEYX);
//Export the private / public key pair
edc->ExportPrivatePublicKey(L"privpubkey.txt");
//Export only the public key
edc->ExportPublicKey(L"pubkey.txt");
//Destroy everything and load up only the public key to write some encrypted data
delete edc;
edc = new TestEncryptDecrypt();
edc->InitializeProvider(MS_ENHANCED_PROV, PROV_RSA_FULL);
edc->ImportKey(L"pubkey.txt");
//Encrypt and write some test data to file
edc->EncryptDataWriteToFile(TEXT("Hello World!ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"), L"encrypteddata.txt");
//Destroy everything and load up only the public key to read some encrypted data
delete edc;
edc = new TestEncryptDecrypt();
edc->InitializeProvider(MS_ENHANCED_PROV, PROV_RSA_FULL);
edc->ImportKey(L"pubkey.txt");
//Decrypt the data from file using the same public key (this should fail but it doesn't)
DWORD dwDataLen = 0;
LPBYTE lpDecryptedData = NULL;
edc->DecryptDataFromFile(&lpDecryptedData, L"encrypteddata.txt", &dwDataLen);
//Write the supposedly decrypted data to another file
edc->WriteBytesFile(L"decrypteddata.txt", lpDecryptedData, dwDataLen);
//Clear data
delete[] lpDecryptedData;
delete edc;
return 0;
}
This API is deprecated according to Microsoft, so if you came here looking for a native cryptography API, you may want to look elsewhere.
After some fighting with the same problem I realized where the error was.
In your first code you were acquiring your context with the last flag set to zero:
CryptAcquireContext(&hProvider, NULL, pszProvider, dwProvType, 0);
But in your solution you changed it into CRYPT_VERIFYCONTEXT.
CryptAcquireContext(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
You solved your problem by changing this flag, not by importing the keys from OpenSSL. I am pretty sure that if you test this in your initial code, it will work as expected.
This CRYPT_VERIFYCONTEXT flag is responsible for not allowing a key to achieve persistence in the system, a persistence which turned the public RSA able to encrypt and decrypt.
The problem is that for some reason Crypto API, using the Microsoft Enhanced Provider w/ RSA, produces symmetrical keys. I am unable to get it to produce asymmetrical keys. The algorithm will, however, work with asymmetrical keys. So this is good news for us. This means to get this to work we only have to generate keys. You could also export these from self signed certificates, use your companies CA, etc.
To solve this I produced a public/private key pair using OpenSSL. I compiled OpenSSL for Windows just for fun then ran the following statements to get myself a pair of unencrypted public/private key files:
openssl genpkey -out private2.pem -outform PEM -des3 -algorithm RSA -pkeyopt rsa_keygen_bits:2048
or
openssl genrsa -des3 -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
openssl rsa -in private.pem -outform PEM -out private_unencrypted.pem
Once I had those I added 2 new functions to my test helper class, ImportPublicKey and ImportPrivateKey. These will only import PEM files without a passphrase. I don't consider that much of a security threat, considering the public is public and the private should hide on a secure server somewhere, perhaps encoded with a hash.
TestEncryptDecrypt.h
#pragma once
#include <Windows.h>
#include <wincrypt.h>
class TestEncryptDecrypt
{
public:
TestEncryptDecrypt()
{
}
~TestEncryptDecrypt()
{
if (hKey != NULL)
CryptDestroyKey(hKey);
if (hProvider != NULL)
CryptReleaseContext(hProvider, 0);
}
BOOL InitializeProvider(LPCTSTR pszProvider, DWORD dwProvType)
{
if (hProvider != NULL)
{
if (!CryptReleaseContext(hProvider, 0))
return 0;
}
return CryptAcquireContext(&hProvider, NULL, pszProvider, dwProvType, CRYPT_VERIFYCONTEXT);
}
BOOL Generate2048BitKeys(ALG_ID Algid)
{
DWORD dwFlags = (0x800 << 16) | CRYPT_EXPORTABLE;
return CryptGenKey(hProvider, Algid, dwFlags, &hKey);
}
VOID ExportPrivatePublicKey(LPTSTR lpFileName)
{
if (hKey == NULL)
return;
DWORD dwDataLen = 0;
BOOL exportResult = CryptExportKey(hKey, NULL, PRIVATEKEYBLOB, 0, NULL, &dwDataLen);
LPBYTE lpKeyBlob = (LPBYTE)malloc(dwDataLen);
exportResult = CryptExportKey(hKey, NULL, PRIVATEKEYBLOB, 0, lpKeyBlob, &dwDataLen);
WriteBytesFile(lpFileName, lpKeyBlob, dwDataLen);
free(lpKeyBlob);
}
VOID ExportPublicKey(LPTSTR lpFileName)
{
if (hKey == NULL)
return;
DWORD dwDataLen = 0;
BOOL exportResult = CryptExportKey(hKey, NULL, PUBLICKEYBLOB, 0, NULL, &dwDataLen);
LPBYTE lpKeyBlob = (LPBYTE)malloc(dwDataLen);
exportResult = CryptExportKey(hKey, NULL, PUBLICKEYBLOB, 0, lpKeyBlob, &dwDataLen);
WriteBytesFile(lpFileName, lpKeyBlob, dwDataLen);
free(lpKeyBlob);
}
BOOL ImportKey(LPTSTR lpFileName)
{
if (hProvider == NULL)
return 0;
if (hKey != NULL)
CryptDestroyKey(hKey);
LPBYTE lpKeyContent = NULL;
DWORD dwDataLen = 0;
ReadBytesFile(lpFileName, &lpKeyContent, &dwDataLen);
BOOL importResult = CryptImportKey(hProvider, lpKeyContent, dwDataLen, 0, 0, &hKey);
delete[] lpKeyContent;
return importResult;
}
BOOL ImportPublicKey(LPTSTR lpFileName)
{
//If a context doesn't exist acquire one
if (hProvider == NULL)
{
BOOL result = CryptAcquireContext(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
if (!result)
return result;
}
if (hKey != NULL)
CryptDestroyKey(hKey);
//Load the PEM
LPBYTE PublicBytes = NULL;
DWORD dwDataLen = 0;
ReadBytesFile(lpFileName, &PublicBytes, &dwDataLen);
//Convert to Unicode
int PublicPEMSize = MultiByteToWideChar(CP_ACP, 0, (LPCCH)PublicBytes, -1, NULL, 0);
TCHAR *PublicPEM = new TCHAR[PublicPEMSize];
MultiByteToWideChar(CP_ACP, 0, (LPCCH)PublicBytes, -1, PublicPEM, PublicPEMSize);
delete[]PublicBytes;
//Convert PEM to DER
LPBYTE PublicDER = NULL;
DWORD dwPublicDERLen = 0;
BOOL result = CryptStringToBinary(PublicPEM, 0, CRYPT_STRING_BASE64HEADER, NULL, &dwPublicDERLen, NULL, NULL);
if (!result)
return result;
PublicDER = new BYTE[dwPublicDERLen];
result = CryptStringToBinary(PublicPEM, 0, CRYPT_STRING_BASE64HEADER, PublicDER, &dwPublicDERLen, NULL, NULL);
if (!result)
return result;
delete[] PublicPEM;
//Decode the object into a public key info struct
CERT_PUBLIC_KEY_INFO *PublicKeyInfo = NULL;
DWORD dwPublicKeyInfoLen = 0;
result = CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, PublicDER, dwPublicDERLen, CRYPT_ENCODE_ALLOC_FLAG, NULL, &PublicKeyInfo, &dwPublicKeyInfoLen);
if (!result)
return result;
//Import the public key
result = CryptImportPublicKeyInfo(hProvider, X509_ASN_ENCODING, PublicKeyInfo, &hKey);
if (!result)
return result;
//cleanup
delete[] PublicDER;
LocalFree(PublicKeyInfo);
return result;
}
BOOL ImportPrivateKey(LPTSTR lpFileName)
{
//If a context doesn't exist acquire one
if (hProvider == NULL)
{
BOOL result = CryptAcquireContext(&hProvider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
if (!result)
return result;
}
if (hKey != NULL)
CryptDestroyKey(hKey);
//Load the PEM
LPBYTE PrivateBytes = NULL;
DWORD dwDataLen = 0;
ReadBytesFile(lpFileName, &PrivateBytes, &dwDataLen);
//Convert to Unicode
int PrivatePEMSize = MultiByteToWideChar(CP_ACP, 0, (LPCCH)PrivateBytes, -1, NULL, 0);
TCHAR *PrivatePEM = new TCHAR[PrivatePEMSize];
MultiByteToWideChar(CP_ACP, 0, (LPCCH)PrivateBytes, -1, PrivatePEM, PrivatePEMSize);
delete[]PrivateBytes;
//Convert PEM to DER
LPBYTE PrivateDER = NULL;
DWORD dwPrivateDERLen = 0;
BOOL result = CryptStringToBinary(PrivatePEM, 0, CRYPT_STRING_BASE64HEADER, NULL, &dwPrivateDERLen, NULL, NULL);
if (!result)
return result;
PrivateDER = new BYTE[dwPrivateDERLen];
result = CryptStringToBinary(PrivatePEM, 0, CRYPT_STRING_BASE64HEADER, PrivateDER, &dwPrivateDERLen, NULL, NULL);
if (!result)
return result;
delete[] PrivatePEM;
//Decode the object into a private key info struct
BYTE *PrivateKeyInfo = NULL;
DWORD dwPrivateKeyInfoLen = 0;
result = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, PrivateDER, dwPrivateDERLen, 0, NULL, NULL, &dwPrivateKeyInfoLen);
if (!result)
return result;
PrivateKeyInfo = new BYTE[dwPrivateKeyInfoLen];
result = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, PrivateDER, dwPrivateDERLen, 0, NULL, PrivateKeyInfo, &dwPrivateKeyInfoLen);
if (!result)
return result;
//Import the private key
result = CryptImportKey(hProvider, PrivateKeyInfo, dwPrivateKeyInfoLen, NULL, 0, &hKey);
if (!result)
return result;
//cleanup
delete[] PrivateDER;
delete[] PrivateKeyInfo;
return result;
}
BOOL EncryptDataWriteToFile(LPTSTR lpSimpleDataToEncrypt, LPTSTR lpFileName)
{
DWORD SimpleDataToEncryptLength = _tcslen(lpSimpleDataToEncrypt)*sizeof(TCHAR);
DWORD BufferLength = SimpleDataToEncryptLength * 10;
BYTE *EncryptedBuffer = new BYTE[BufferLength];
SecureZeroMemory(EncryptedBuffer, BufferLength);
CopyMemory(EncryptedBuffer, lpSimpleDataToEncrypt, SimpleDataToEncryptLength);
BOOL cryptResult = CryptEncrypt(hKey, NULL, TRUE, 0, EncryptedBuffer, &SimpleDataToEncryptLength, BufferLength);
DWORD dwGetLastError = GetLastError();
WriteBytesFile(lpFileName, EncryptedBuffer, SimpleDataToEncryptLength);
delete[] EncryptedBuffer;
return cryptResult;
}
BOOL DecryptDataFromFile(LPBYTE *lpDecryptedData, LPTSTR lpFileName, DWORD *dwDecryptedLen)
{
if (hKey == NULL)
return 0;
LPBYTE lpEncryptedData = NULL;
DWORD dwDataLen = 0;
ReadBytesFile(lpFileName, &lpEncryptedData, &dwDataLen);
BOOL decryptResult = CryptDecrypt(hKey, NULL, TRUE, 0, lpEncryptedData, &dwDataLen);
*dwDecryptedLen = dwDataLen;
//WriteBytesFile(L"decryptedtest.txt", lpEncryptedData, dwDataLen);
*lpDecryptedData = new BYTE[dwDataLen + 1];
SecureZeroMemory(*lpDecryptedData, dwDataLen + 1);
CopyMemory(*lpDecryptedData, lpEncryptedData, dwDataLen);
delete[]lpEncryptedData;
return decryptResult;
}
VOID WriteBytesFile(LPTSTR lpFileName, BYTE *content, DWORD dwDataLen)
{
HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, 0x7, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwBytesWritten = 0;
WriteFile(hFile, content, dwDataLen, &dwBytesWritten, NULL);
CloseHandle(hFile);
}
private:
HCRYPTPROV hProvider = NULL;
HCRYPTKEY hKey = NULL;
VOID ReadBytesFile(LPTSTR lpFileName, BYTE **content, DWORD *dwDataLen)
{
HANDLE hFile = CreateFile(lpFileName, GENERIC_READ, 0x7, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwFileLength = 0;
DWORD dwBytesToRead = GetFileSize(hFile, NULL);
DWORD dwBytesRead = 0;
*content = new BYTE[dwBytesToRead + 1];
SecureZeroMemory(*content, dwBytesToRead + 1);
ReadFile(hFile, *content, dwBytesToRead, &dwBytesRead, NULL);
*dwDataLen = dwBytesRead;
CloseHandle(hFile);
}
};
And here's the test, providing proof that it cannot decrypt using the public key but instead the private key .pem:
int main()
{
TestEncryptDecrypt *edc = new TestEncryptDecrypt();
edc->ImportPublicKey(L"public.pem");
edc->EncryptDataWriteToFile(L"Hello world! hahahahah", L"encrypted.txt");
LPBYTE decodedData = NULL; DWORD decodedLen = 0;
BOOL result = edc->DecryptDataFromFile(&decodedData, L"encrypted.txt", &decodedLen);
if (result == 1)
OutputDebugString(L"We were able to decrypt from a public key! That's not good.");
result = edc->ImportPrivateKey(L"private_unencrypted.pem");
result = edc->DecryptDataFromFile(&decodedData, L"encrypted.txt", &decodedLen);
edc->WriteBytesFile(L"decrypted.txt", decodedData, decodedLen);
return 0;
}
I think the title is a bit misleading in a way that the RSA keys are definitely asymmetric and the Public key is not able to decrypt anything on its own by its very mathematical definition.
However it seems that the Public and Private keys (being generated as a pair) somehow "know" about the existence of one another (they are linked internally). Once generated by the "CryptGenKey" function, the PrivatePublicKeyPair blob is saved in the CSP's (Cryptographic Service Provider) key container.
Even if you destroy the "hKey" handle to the Private key blob as well as the "hProvider" handle to the CSP, the data is not scrubbed from the memory space where it's been generated (unless you reboot your computer of course) and so when you import just the Public key from file it will know where the Private key was previously located even though the handle was destroyed.
Interestingly enough, when you use the "CryptDecrypt" function to decrypt data using just the imported Public key, even though it manages to locate the previously destroyed Private key and successfully decrypt the data, it will silently issue the error "1008 - An attempt was made to reference a token that does not exist". You wouldn't even know about the error being raised if you didn't check with "GetLastError"!
The solution to all this madness is the "CRYPT_VERIFYCONTEXT" flag that removes the persistence of the key container inside the CSP as someone has already mentioned above. Even when using this flag you still need to destroy the "hProvider" handle to the CSP if you used the "CryptGenKey" function to generate the keys before importing them from file. Only then will the imported Public key behave as expected, namely only able to be used for encryption! If you try to use it for decryption you will get error "0x8009000D - Key does not exist" since it won't be able to find its private counterpart anymore!
I realize this topic is rather old by now but since I was recently confronted with this same conundrum I thought I'd share my two cents on the matter.

Why CryptImportKey failed?

I'm trying to use the Windows CryptoAPI functions for AES encryption.
I want to give my own key to the CryptEncrypt function but my CryptImportKey function failes
here is my code:
HCRYPTPROV hProv = NULL;
HCRYPTKEY hKey = NULL;
DWORD dwBlobLen;
PBYTE pbKeyBlob = NULL;
pbKeyBlob = (PBYTE)"1a1dc91c907325c6";
if(!CryptAcquireContext(&hProv, NULL,NULL, PROV_RSA_AES,CRYPT_VERIFYCONTEXT))
{
printf(" Error in AcquireContext 0x%08x \n",GetLastError());
}
if (!CryptImportKey(hProv,pbKeyBlob,sizeof(pbKeyBlob),0,CRYPT_EXPORTABLE,&hKey ))
{
printf("Error 0x%08x in importing the Des key \n",GetLastError());
}
If you want to use AES your key should be at least 16 bytes long, you have 8 byte long key. AES uses 128, 192 or 256 bits long keys.

Bad Data error at CryptDecrypt when using AES 256 (MS CryptoAPI)

I'm trying to decrypt - using the microsoft's CryptoAPI in C++ - a short message encrypted using mcrypt_encrypt in PHP. The php line is:
mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $key, $msg, MCRYPT_MODE_CBC);
where $key and $msg are strings.
In C++ I have the key, and my decryption function looks like this:
bool decrypt( const unsigned char* input_buffer,
const size_t& input_size,
const unsigned char* key,
const size_t& key_size,
unsigned char* output_buffer,
DWORD* out_size)
{
Log(L"START init_crypto");
bool ret = false;
HCRYPTKEY hKey = NULL;
HCRYPTPROV hCryptProv = NULL;
DWORD dwDataLen = 0;
// Attempt to acquire a handle to the crypto provider for AES
if(0 == CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) ){//PROV_RSA_AES
Log(L"CryptAcquireContext failed with code %ld", GetLastError());
goto end;
}
// key creation based on
// http://mirror.leaseweb.com/NetBSD/NetBSD-release-5-0/src/dist/wpa/src/crypto/crypto_cryptoapi.c
struct {
BLOBHEADER hdr;
DWORD len;
BYTE key[32];
} key_blob;
key_blob.hdr.bType = PLAINTEXTKEYBLOB;
key_blob.hdr.bVersion = CUR_BLOB_VERSION;
key_blob.hdr.reserved = 0;
key_blob.hdr.aiKeyAlg = CALG_AES_256;
key_blob.len = 32;//key_size;
memset(key_blob.key, '\0', sizeof(key_blob.key));
assert(key_size <= sizeof(key_blob.key));
CopyMemory(key_blob.key, key, min(key_size, sizeof(key_blob.key)));
if (!CryptImportKey( hCryptProv,
(BYTE *)&key_blob,
sizeof(key_blob),
0,
CRYPT_EXPORTABLE,
&hKey)){
Log(L"Error in CryptImportKey 0x%08x \n", GetLastError());
goto free_context;
}
else{
//---------------------------
// Set Mode
DWORD dwMode = CRYPT_MODE_CBC;
if(!CryptSetKeyParam( hKey, KP_MODE, (BYTE*)&dwMode, 0 )){
// Handle error
Log(L"Cannot set the cbc mode for decryption 0x%08x \n", GetLastError());
goto free_key;
}
//----------------------------
// Set IV
DWORD dwBlockLen = 0;
DWORD dwDataLen = sizeof(dwBlockLen);
if (!CryptGetKeyParam(hKey, KP_BLOCKLEN, (BYTE *)&dwBlockLen, &dwDataLen, 0)){
// Handle error
Log(_USTR("Cannot get the block length 0x%08x \n"), GetLastError());
goto free_key;
}
dwBlockLen /= 8;
BYTE *pbTemp = NULL;
if (!(pbTemp = (BYTE *)LocalAlloc(LMEM_FIXED, dwBlockLen))){
// Handle error
Log(L"Cannot allcoate the IV block 0x%08x \n", GetLastError());
goto free_key;
}
memset(pbTemp, '\0', dwBlockLen);
if (!CryptSetKeyParam(hKey, KP_IV, pbTemp, 0)){
// Handle error
Log(L"Cannot set the IV block 0x%08x \n", GetLastError());
LocalFree(pbTemp);
goto free_key;
}
LocalFree(pbTemp);
}
CopyMemory(output_buffer, input_buffer, min(*out_size, input_size));
*out_size = input_size;
if (!CryptDecrypt(hKey, NULL, TRUE, 0, output_buffer, out_size)){
Log(L"CryptDecrypt failed with code %ld", GetLastError());
goto free_key;
}
else{
Log(L"Decryption...");
ret = true;
}
free_key:
if (hKey)
CryptDestroyKey( hKey );
free_context:
if (hCryptProv)
CryptReleaseContext(hCryptProv, 0);
end:
return ret;
}
I consistently get the "bad data" error at CryptDecrypt()... I may be missing something obvious - if so, please help.
EDIT - To isolate the cause of the problem I replaced the two lines before CryptDecrypt (the CopyMemory stuff) with the following code:
....
strcpy((char*)output_buffer, "stuff");
DWORD plain_txt_len = strlen((char*)output_buffer);
if (!CryptEncrypt(hKey, NULL, TRUE, 0, output_buffer, &plain_txt_len, *out_size)){
Log(L"CryptEncrypt failed with code 0x%08x", GetLastError());
goto free_key;
}
...
and the CryptDecrypt is working -- which makes me believe that the problem is the key/and or message transmission from php to C++ ... If you agree can you give me a hint on how to make sure that the strings I use in PHP are the same with the ones in C++ (at byte level?)
EDIT2 --
After I changed my strings in binary streams (using pack) in php, and after I implemented the workaround(?) for AES vs Rijndael from here : http://kix.in/2008/07/22/aes-256-using-php-mcrypt/ I finaly have CryptDecrypt decrypting my PHP message... the problem is that it also still fails - even if the output contains the decrypted text. Any ideas about why could this happen?
Try passing NULL instead of CRYPT_VERIFYCONTEXT when acquiring the context.
With block encryption algorithms such as AES you need to add padding to the data being encrypted up to a multiple of the block length. Using your code example that already retrieves the block size you can calculate the padding required for encryption:
dwPadding = dwBlockLen - dwDataLen % dwBlockLen
Then append "dwPadding" number of characters (NULL works fine) to the data being encrypted and you will no longer get "Bad Data" errors in decryption.
You can also find out the size of the required encryption buffer directly by making an additional call to "CryptEncrypt" with a NULL buffer before the actual encryption:
dwBuffSize = dwDataLen
CryptEncrypt(hKey, NULL, TRUE, 0, NULL, &dwBuffSize, 0)
dwPadding = dwBuffsize - dwDataLen
Both methods are equivalent and produce the same desired result. The "CryptDecrypt" function already knows about padding and will return the actual size of the decryption buffer in one go so you will know exactly where your decrypted data ends.