Related
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.
I'm trying to setup server-side encryption using SSPI. I'm successfully (as far as I can tell) loading a certificate stored as a PFX file, but the call to m_pSSPI->AcquireCredentialsHandleA() returns 0x8009030e.
This method seems to successfully load the file and return a CERT_CONTEXT object.
HRESULT CTLSPackage::LoadCertContextFromFilePFX (PCWSTR pcwzFile, PCWSTR pcwzPassword, __deref_out PCCERT_CONTEXT* ppctxCert)
{
HRESULT hr;
HANDLE hFile, hSection = NULL;
BOOL (WINAPI* pfnPFXIsPFXBlob)(CRYPT_DATA_BLOB*);
HCERTSTORE (WINAPI* pfnPFXImportCertStore)(CRYPT_DATA_BLOB*, LPCWSTR, DWORD);
PCCERT_CONTEXT (WINAPI* pfnCertEnumCertificatesInStore)(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext);
CRYPT_DATA_BLOB blob; blob.pbData = NULL;
HCERTSTORE pfxStore = NULL;
hFile = CreateFile(pcwzFile, FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
CheckIfGetLastError(INVALID_HANDLE_VALUE == hFile);
blob.cbData = GetFileSize(hFile, NULL);
hSection = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0);
CheckIfGetLastError(NULL == hSection);
blob.pbData = reinterpret_cast<PBYTE>(MapViewOfFile(hSection, FILE_MAP_READ, 0, 0, 0));
CheckIfGetLastError(NULL == blob.pbData);
Check(TGetFunction(m_hCrypt32, "PFXIsPFXBlob", &pfnPFXIsPFXBlob));
Check(TGetFunction(m_hCrypt32, "PFXImportCertStore", &pfnPFXImportCertStore));
Check(TGetFunction(m_hCrypt32, "CertEnumCertificatesInStore", &pfnCertEnumCertificatesInStore));
CheckIf(!pfnPFXIsPFXBlob(&blob), E_FAIL);
pfxStore = pfnPFXImportCertStore(&blob, pcwzPassword, CRYPT_MACHINE_KEYSET | CRYPT_EXPORTABLE);
CheckIf(NULL == pfxStore, SEC_E_NO_CREDENTIALS);
*ppctxCert = pfnCertEnumCertificatesInStore(pfxStore, NULL);
CheckIf(NULL == *ppctxCert, SEC_E_NO_CREDENTIALS);
Cleanup:
if(pfxStore)
{
BOOL (WINAPI* pfnCertCloseStore)(HCERTSTORE, DWORD);
if(SUCCEEDED(TGetFunction(m_hCrypt32, "CertCloseStore", &pfnCertCloseStore)))
pfnCertCloseStore(pfxStore, 0);
}
if(blob.pbData)
UnmapViewOfFile(blob.pbData);
SafeCloseHandle(hSection);
SafeCloseFileHandle(hFile);
return hr;
}
The result is immediately passed to another class method, which makes the failing AcquireCredentialsHandleA() call.
HRESULT CTLSPackage::AcquireCredentials (__in_opt PCCERT_CONTEXT pCertContext, PCredHandle phCreds)
{
SCHANNEL_CRED SchannelCred;
TimeStamp tsExpiry;
ZeroMemory(&SchannelCred, sizeof(SchannelCred));
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
if(pCertContext)
{
SchannelCred.cCreds = 1;
SchannelCred.paCred = &pCertContext;
}
SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2;
if(!m_fServer)
SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS | SCH_USE_STRONG_CRYPTO;
//
// Create an SSPI credential.
//
return m_pSSPI->AcquireCredentialsHandleA(
NULL, // Name of principal
m_fServer ? NEGOSSP_NAME_A : UNISP_NAME_A, // Name of package
m_fServer ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND,
NULL, // Pointer to logon ID
&SchannelCred, // Package specific data
NULL, // Pointer to GetKey() func
NULL, // Value to pass to GetKey()
phCreds, // (out) Cred Handle
&tsExpiry); // (out) Lifetime (optional)
}
My CTLSPackage::AcquireCredentials() code path is also used for setting up client-side encryption, and that works. For the server-side path, m_fServer is TRUE. The m_hCrypt32 member was loaded from Crypt32.dll.
I've cobbled together this code from samples, but I must be missing something for the server case. I only need to setup SSL/TLS-style encryption, so the "No credentials are available in the security package" error is weird because I have no need for credential authentication.
Does anyone know what might be missing? Thanks!
With a hint from RbMm, I then found this article:
https://www.codeproject.com/articles/125124/how-to-use-certificate-from-disk-with-microsoft-cr
The short answer is that CryptAcquireCertificatePrivateKey() needed to be used when loading a PFX from a file, and UNISP_NAME_A needed to be passed to AcquireCredentialsHandleA().
For reference, here is the revised code:
HRESULT CTLSPackage::LoadCertContextFromFilePFX (PCWSTR pcwzFile, PCWSTR pcwzPassword, __deref_out PCCERT_CONTEXT* ppctxCert)
{
HRESULT hr;
HANDLE hFile, hSection = NULL;
BOOL (WINAPI* pfnPFXIsPFXBlob)(CRYPT_DATA_BLOB*);
HCERTSTORE (WINAPI* pfnPFXImportCertStore)(CRYPT_DATA_BLOB*, LPCWSTR, DWORD);
PCCERT_CONTEXT (WINAPI* pfnCertFindCertificateInStore)(HCERTSTORE hCertStore, DWORD dwCertEncodingType, DWORD dwFindFlags, DWORD dwFindType, const void* pvFindPara, PCCERT_CONTEXT pPrevCertContext);
BOOL (WINAPI* pfnCryptAcquireCertificatePrivateKey)(PCCERT_CONTEXT pCert, DWORD dwFlags, void* pvReserved, HCRYPTPROV_OR_NCRYPT_KEY_HANDLE *phCryptProvOrNCryptKey, DWORD* pdwKeySpec, BOOL* pfCallerFreeProvOrNCryptKey);
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hProv;
DWORD dwKeySpec;
BOOL fFreeProv = FALSE;
CRYPT_DATA_BLOB blob; blob.pbData = NULL;
HCERTSTORE hpfxStore = NULL;
hFile = CreateFile(pcwzFile, FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
CheckIfGetLastError(INVALID_HANDLE_VALUE == hFile);
blob.cbData = GetFileSize(hFile, NULL);
hSection = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0);
CheckIfGetLastError(NULL == hSection);
blob.pbData = reinterpret_cast<PBYTE>(MapViewOfFile(hSection, FILE_MAP_READ, 0, 0, 0));
CheckIfGetLastError(NULL == blob.pbData);
Check(TGetFunction(m_hCrypt32, "PFXIsPFXBlob", &pfnPFXIsPFXBlob));
Check(TGetFunction(m_hCrypt32, "PFXImportCertStore", &pfnPFXImportCertStore));
Check(TGetFunction(m_hCrypt32, "CertFindCertificateInStore", &pfnCertFindCertificateInStore));
Check(TGetFunction(m_hCrypt32, "CryptAcquireCertificatePrivateKey", &pfnCryptAcquireCertificatePrivateKey));
CheckIf(!pfnPFXIsPFXBlob(&blob), HRESULT_FROM_WIN32(ERROR_BAD_FORMAT));
hpfxStore = pfnPFXImportCertStore(&blob, pcwzPassword, 0);
if(NULL == hpfxStore && pcwzPassword && L'\0' == *pcwzPassword)
{
hpfxStore = pfnPFXImportCertStore(&blob, NULL, 0);
CheckIf(NULL == hpfxStore, SEC_E_NO_CREDENTIALS);
}
*ppctxCert = pfnCertFindCertificateInStore(hpfxStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL);
CheckIfGetLastError(NULL == *ppctxCert);
// Acquire the private key and make it available for the later AcquireCredentalsHandle() call.
if(!pfnCryptAcquireCertificatePrivateKey(*ppctxCert, 0, NULL, &hProv, &dwKeySpec, &fFreeProv))
{
DWORD dwError = GetLastError();
FreeCertificateContext(*ppctxCert);
*ppctxCert = NULL;
CheckWin32Error(dwError);
}
Cleanup:
if(fFreeProv)
FreeProvOrNCryptKey(hProv, dwKeySpec);
if(hpfxStore)
{
BOOL (WINAPI* pfnCertCloseStore)(HCERTSTORE, DWORD);
if(SUCCEEDED(TGetFunction(m_hCrypt32, "CertCloseStore", &pfnCertCloseStore)))
pfnCertCloseStore(hpfxStore, 0);
}
if(blob.pbData)
UnmapViewOfFile(blob.pbData);
SafeCloseHandle(hSection);
SafeCloseFileHandle(hFile);
return hr;
}
HRESULT CTLSPackage::AcquireCredentials (__in_opt PCCERT_CONTEXT pCertContext, PCredHandle phCreds)
{
SCHANNEL_CRED SchannelCred;
TimeStamp tsExpiry;
ZeroMemory(&SchannelCred, sizeof(SchannelCred));
SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
if(pCertContext)
{
SchannelCred.cCreds = 1;
SchannelCred.paCred = &pCertContext;
}
SchannelCred.grbitEnabledProtocols = SP_PROT_SSL3 | SP_PROT_TLS1 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2;
SchannelCred.dwFlags = SCH_USE_STRONG_CRYPTO;
if(!m_fServer)
SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
//
// Create an SSPI credential.
//
return m_pSSPI->AcquireCredentialsHandleA(
NULL, // Name of principal
UNISP_NAME_A, // Name of package
m_fServer ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND,
NULL, // Pointer to logon ID
&SchannelCred, // Package specific data
NULL, // Pointer to GetKey() func
NULL, // Value to pass to GetKey()
phCreds, // (out) Cred Handle
&tsExpiry); // (out) Lifetime (optional)
}
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.
I need to sign some data with the SHA256withRSA algorithm (RSASSA-PKCS1-V1_5-SIGN with SHA-256 hash function) in Windows, and I'm having some validation errors. I was wondering if anyone has experience trying to do it.
The summary of what I'm doing:
CryptAcquireContextW with MS_ENH_RSA_AES_PROV and PROV_RSA_AES
CryptImportKey after running CryptStringToBinary on a pem and applying to that CryptDecodeObjectEx with PKCS_RSA_PRIVATE_KEY
CryptCreateHash with CALG_SHA_256
CryptHashData and CryptSignHash using AT_KEYEXCHANGE to use the imported key (AT_SIGNATURE didn't work)
If you want the detailed version, it's here:
Create a CSP
WCHAR const container[] = L"ContainerName";
HCRYPTPROV provider;
CryptAcquireContextW(
&provider,
container,
MS_ENH_RSA_AES_PROV,
PROV_RSA_AES,
0));
Import my private key
char const key_string[] = "-----BEGIN RSA PRIVATE KEY-----"
"<base64 encoded key>"
"-----END RSA PRIVATE KEY-----";
DWORD key_bytes_size;
BYTE* key_bytes;
CryptStringToBinaryA(
key_string,
0,
CRYPT_STRING_BASE64HEADER,
nullptr, &key_bytes_size,
nullptr, nullptr);
key_bytes = new BYTE[key_bytes_size];
CryptStringToBinaryA(
key_string,
0,
CRYPT_STRING_BASE64HEADER,
key_bytes, &key_bytes_size,
nullptr, nullptr);
DWORD key_blob_size;
BYTE* key_blob;
CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
PKCS_RSA_PRIVATE_KEY,
key_bytes, key_bytes_size,
0, nullptr,
nullptr, &key_blob_size);
key_blob = new BYTE[key_blob_size];
CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
PKCS_RSA_PRIVATE_KEY,
key_bytes, key_bytes_size,
0, nullptr,
key_blob, &key_blob_size);
HCRYPTKEY key_handle;
CryptImportKey(
provider, // The one I created before
key_blob, key_blob_size,
nullptr, 0,
&key_handle);
(I hate that the API is so verbose, especially the two-step dance to ask for a length and then fill the buffer)
Once I've done this, I then hash and sign my data:
char const data[] = "Stuff to sign";
HCRYPTHASH hash_handle;
CryptCreateHash(provider, CALG_SHA_256, 0, 0, &hash_handle);
DWORD data_length = std::strlen(data);
CryptHashData(
hash_handle,
reinterpret_cast<const BYTE*>(data), data_length,
0);
DWORD signature_size;
BYTE* signature;
CryptSignHash(
hash_handle,
AT_KEYEXCHANGE, // AT_SIGNATURE didn't work
nullptr, 0,
nullptr, signature_size);
signature = new BYTE[signature_size];
CryptSignHash(
hash_handle,
AT_KEYEXCHANGE, // AT_SIGNATURE didn't work
nullptr, 0,
signature, signature_size);
Will this produce the kind of signature I want? Should I set different parameters for the AcquireContext call?
I am creating a little tool for encrypt and decrypt using a pair keys (public and private keys).
I export public and private key on my computer and I can encrypt and decrypt files without problems. I have problem when I try decrypt files in other machine with the same public key.
// initializing CSP HCRYPTPROV hProv; HCRYPTKEY hKey;
if(!CryptAcquireContext(hProv, NULL, NULL, PROV_RSA_FULL, 0)){ if(GetLastError() == NTE_BAD_KEYSET){ if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)){ return FALSE; } } }
// create a pair keys if (!CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_ARCHIVABLE, &hKey)) return FALSE;
// public key if (!CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, NULL, sizePublicKey)) return FALSE;
*publicKey = (BYTE *) LocalAlloc(LPTR, *sizePublicKey * sizeof(DWORD)); if(*publicKey == NULL) return FALSE;
if (!CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, *publicKey, sizePublicKey)) return FALSE; // save public key on file
// private key if (!CryptExportKey(hKey, 0, PRIVATEKEYBLOB, 0, NULL, sizePrivateKey)) return FALSE;
*privateKey = (BYTE *) LocalAlloc(LPTR, *sizePrivateKey * sizeof(DWORD)); if(*publicKey == NULL) return FALSE;
if (!CryptExportKey(hKey, 0, PRIVATEKEYBLOB, 0, *privateKey, sizePrivateKey)) return FALSE;
PrivateKey.key = (BYTE *) LocalAlloc(LPTR, *sizePrivateKey * sizeof(DWORD)); if(*publicKey == NULL) return FALSE; // save private key on file
//I encrypt file using if(!CryptEncrypt(hKey, 0, TRUE, 0, cache, &sizeCache, BLOCK_SIZE_ENCRYPT)){
free(cache);
return FALSE; }
//To decrypt file //First import public key
CryptImportKey(hProv, publicKey, sizePublicKey, 0, 0, &hKey)
//To decrypt: if (!CryptDecrypt(hKey, 0, TRUE, 0, cache, &sizeCache)){
free(cache);
return FALSE; }
In the same computer that key ware created the application encrypt and decrypt correctly but if I try decrypt files in other computer the CryptDecrypt() failed with error 80090003 (error got by GetLastError())
Any idea? what am I doing wrong...?
How to I can export the public key to other computer?
Thanks!
Probably you are not exporting the key, just using the CSP containing the key, while you are in the same computer, the key is stored in the container where you "link" by using the cryptoapi. Once you go to other computer the container is not present, so you can not use the key.
Make sure that the Private Key is exportable.