Window C/C++ Crypto API Examples and Tips

Window C/C++ Crypto API Examples and tips

Here's a bunch of examples I've found....

  • Example C Program: Listing the Certificates in a Store
  • Example C Program: Using CryptAcquireContext
  • Example C Program: Enumerating CSP Providers and Provider Types
  • Example C Code for Opening Certificate Stores
  • Example C Program: Sending and Receiving a Signed and Encrypted Message
  • Example C Program: Signing a Hash and Verifying the Hash Signature

MSDN has these examples scattered around the docs

This website provides a good overview of the concepts along with cross-platform examples

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

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:

  1. Generate random salt
  2. Derive key(password, salt) = key
  3. Generate random IV
  4. Encrypt(key, IV, plain text) = cipher text

Input parameters: plain text, password

Output parameters: cipher text, salt, IV

Decrypting algorithm will be:

  1. Derive key(password, salt) = key
  2. 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;
}

Tracing CSP calls within Windows Crypto API

advapi32.dll used to contain cryptography implementation some time ago, but now this functionality has been moved to cryptsp.dll. Windows team sometimes moves implementation of public methods from one DLL into another. See more examples on this in The Old New Thing Blog. Advapi32.dll simply calls into corresponding functions in cryptsp.dll.

Apparently certificate APIs in mmc that you were trying to debug are calling directly into cryptsp.dll. Example from a call stack on Windows 8:

0:000> k
Child-SP RetAddr Call Site
00000000`0059c278 000007fd`6c1b7d8b CRYPTSP!CryptAcquireContextA
00000000`0059c280 000007fd`6c1ace66 CRYPT32!I_CryptGetDefaultCryptProv+0xbc
00000000`0059c2d0 000007fd`6c1ae1b3 CRYPT32!FastCreateCtlElement+0x4a6
00000000`0059c4e0 000007fd`6c1a248a CRYPT32!CreateCtlElement+0x23
00000000`0059c530 000007fd`6c1a2297 CRYPT32!CreateStoreElement+0x139
00000000`0059c610 000007fd`6c1abaa4 CRYPT32!LoadStoreElement+0x244
00000000`0059c6f0 000007fd`6c1a2c76 CRYPT32!OpenFromRegistry+0x39e
00000000`0059c950 000007fd`6c1a2e7c CRYPT32!OpenAllFromRegistryEx+0x96
00000000`0059c9d0 000007fd`6c1a394b CRYPT32!I_CertDllOpenRegStoreProv+0xfc
00000000`0059ca20 000007fd`6c196926 CRYPT32!I_CertDllOpenSystemRegistryStoreProvW+0x28b
00000000`0059cb20 000007fd`6c1a3b72 CRYPT32!CertOpenStore+0x296
00000000`0059cba0 000007fd`6c1a3dc2 CRYPT32!OpenPhysicalStoreCallback+0xc2
00000000`0059cc70 000007fd`6c1a4512 CRYPT32!EnumPhysicalStore+0x648
00000000`0059ce00 000007fd`6c196926 CRYPT32!I_CertDllOpenSystemStoreProvW+0x162
00000000`0059cee0 000007fd`6c1a3b72 CRYPT32!CertOpenStore+0x296
00000000`0059cf60 000007fd`6c1a3dc2 CRYPT32!OpenPhysicalStoreCallback+0xc2
00000000`0059d030 000007fd`6c1a4512 CRYPT32!EnumPhysicalStore+0x648
00000000`0059d1c0 000007fd`6c196926 CRYPT32!I_CertDllOpenSystemStoreProvW+0x162
00000000`0059d2a0 000007fd`47371a27 CRYPT32!CertOpenStore+0x296
00000000`0059d320 000007fd`47363611 certmgr!CCertStore::GetStoreHandle+0xc7

Notice, advapi32.dll is not even present in the call stack.

So the solution for you would be to put breakpoints directly on the functions in cryptsp.dll. E.g.:

> bu CRYPTSP!CryptAcquireContextW
> bu CRYPTSP!CryptAcquireContextA
> bu CRYPTSP!CryptDecrypt
> ... and so on ...
> g

Windows C++ crypto API: how to disable pin code UI for smart card

Found an answer and posting it here in case anyone needs it. The trick is to use CNG API instead of legacy crypto API:

#include <stdio.h>
#include <windows.h>
#include <Wincrypt.h>

#define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

void MyHandleError(const char* s);

int main()
{
BYTE* pbContent = (BYTE*)"Security is our only business.";
DWORD cbContent = strlen((char*)pbContent) + 1;

LPCWSTR pswzRecipientName = L"recipient";
LPCWSTR pswzCertSubject = L"signer";

HCERTSTORE hStoreHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_CURRENT_USER, L"my");

if (hStoreHandle != NULL)
{
printf("The MY system store is open. \n");
}
else
{
MyHandleError("Error getting store handle.");
}

PCCERT_CONTEXT pSignerCert = CertFindCertificateInStore(hStoreHandle, MY_ENCODING_TYPE, 0, CERT_FIND_SUBJECT_STR, pswzCertSubject, NULL);

if (pSignerCert != NULL)
{
printf("Found certificate for %S.\n", pswzCertSubject);
}
else
{
MyHandleError("Signer certificate not found.");
}

PCCERT_CONTEXT pRecipCert = CertFindCertificateInStore(hStoreHandle, MY_ENCODING_TYPE, 0, CERT_FIND_SUBJECT_STR, pswzRecipientName, NULL);

if (pRecipCert != NULL)
{
printf("Certificate for %S found. \n", pswzRecipientName);
}
else
{
MyHandleError("Could not find the countersigner's "
"certificate.");
}

NCRYPT_HANDLE rsaKey;
DWORD dwKeySpec = 0;
if (!CryptAcquireCertificatePrivateKey(pSignerCert, CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, NULL, &rsaKey, &dwKeySpec, NULL))
{
MyHandleError("Cannot acquire private key.");
}

if (NCryptSetProperty(rsaKey, NCRYPT_PIN_PROPERTY, (BYTE*)L"123456", 6, 0) != ERROR_SUCCESS)
{
MyHandleError("NCryptSetProperty failed.");
}

CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm;
memset(&HashAlgorithm, 0, sizeof(HashAlgorithm));
HashAlgorithm.pszObjId = szOID_RSA_SHA256RSA;

CRYPT_SIGN_MESSAGE_PARA SignParam;
memset(&SignParam, 0, sizeof(SignParam));
SignParam.cbSize = sizeof(SignParam);
SignParam.dwMsgEncodingType = MY_ENCODING_TYPE;
SignParam.pSigningCert = pSignerCert;
SignParam.cMsgCert = 1;
SignParam.rgpMsgCert = &pSignerCert;
SignParam.HashAlgorithm = HashAlgorithm;
SignParam.dwFlags = CRYPT_MESSAGE_SILENT_KEYSET_FLAG;

CRYPT_ALGORITHM_IDENTIFIER CryptAlg;
memset(&CryptAlg, 0, sizeof(CryptAlg));
CryptAlg.pszObjId = szOID_RSA_DES_EDE3_CBC;

CRYPT_ENCRYPT_MESSAGE_PARA EncryptParam;
memset(&EncryptParam, 0, sizeof(EncryptParam));
EncryptParam.cbSize = sizeof(EncryptParam);
EncryptParam.dwMsgEncodingType = MY_ENCODING_TYPE;
EncryptParam.ContentEncryptionAlgorithm = CryptAlg;

DWORD cbEncodedBlob = 0;
if (!CryptSignAndEncryptMessage(&SignParam, &EncryptParam, 1, &pRecipCert, pbContent, cbContent, NULL, &cbEncodedBlob))
{
MyHandleError("CryptSignAndEncrypteMessage failed.");
}

BYTE* pbEncodedBlob = (BYTE*)malloc(cbEncodedBlob);
if (!CryptSignAndEncryptMessage(&SignParam, &EncryptParam, 1, &pRecipCert, pbContent, cbContent, pbEncodedBlob, &cbEncodedBlob))
{
MyHandleError("CryptSignAndEncrypteMessage failed.");
}
else
{
printf("Message signed and encrypted successfully. \n");
FILE* fp = fopen("signed-encrypted.p7m", "wb");
fwrite(pbEncodedBlob, cbEncodedBlob, 1, fp);
fclose(fp);
}

free(pbEncodedBlob);

CertFreeCertificateContext(pSignerCert);
CertFreeCertificateContext(pRecipCert);

CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG);

return 0;
}

void MyHandleError(const char* s)
{
fprintf(stderr, "An error occurred in running the program. \n");
fprintf(stderr, "%s\n", s);
fprintf(stderr, "Error number %x.\n", GetLastError());
fprintf(stderr, "Program terminating. \n");
exit(1);
}

Implement Windows CryptoAPI CryptDeriveKey Using OpenSSL APIs

After all, this code worked:

int generateKey(const string& strSecter)
{
SHA256_CTX sha256Ctx;
unsigned char hash[SHA256_DIGEST_LENGTH];
SecureZeroMemory(hash, sizeof hash);
SHA256_Init(&sha256Ctx);
SHA256_Update(&sha256Ctx, strSecter.c_str(), strSecter.length());
SHA256_Final(hash, &sha256Ctx);
memcpy(Key, hash, AES_BLOCK_SIZE);
memcpy(IV, hash + AES_BLOCK_SIZE, AES_BLOCK_SIZE);

return 0;
}

Hope this will help someone.

Cryptographic API vs manually implemented algorithm

MD5 is probably available everywhere. CRC32 is so simple (and not really cryptography) that you can just include or implement it directly in your application.

The Windows crypto API supports multiple providers and the default provider is probably fully implemented in user mode without switching to kernel mode for most things. The PRng and AES encryption might be implemented in hardware.

What is your goal? Speed? No backdoors? Obscure algorithms?

rsa encryption in windows crypto and decryption in openssl

CryptEncrypt, for reasons that probably made sense to the author at the time, writes the bytes down backwards. Or, rather, it writes them in byte minor/little-endian order whereas almost every other cryptography library (including the Windows CNG BCryptEncrypt and NCryptEncrypt routines) writes them in byte major/big-endian order.

So you need to reverse data coming out of CryptEncrypt, and reverse it going in to CryptDecrypt.

For example, in .NET Core's RSACryptoServiceProvider.Encrypt it calls CapiHelper.EncryptKey which calls CryptEncrypt then Array.Reverse before returning.

The CryptEncrypt documentation has as the last sentence in the Remarks section

The ciphertext is returned in little-endian format.



Related Topics



Leave a reply



Submit