Skip to content

Instantly share code, notes, and snippets.

@mcdurdin
Last active October 29, 2019 09:05
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mcdurdin/5626617 to your computer and use it in GitHub Desktop.
Save mcdurdin/5626617 to your computer and use it in GitHub Desktop.
RSA Key Exchange between CryptoAPI and CNG
/*
Example program to demonstrate sharing public keys from CryptoApi to CNG
Note: Reversing the process would allow sharing public keys from CNG to
CryptoApi. You would need to be careful of padding parameters and
versions.
License (yes, yes, BSD):
Copyright (c) 2013, Marc Durdin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* The name of Marc Durdin may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MARC DURDIN BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <intsafe.h>
//
// Structures
//
struct PUBLIC_KEY_VALUES {
BLOBHEADER blobheader;
RSAPUBKEY rsapubkey;
BYTE modulus[4096];
};
//
// Forward declarations
//
HRESULT GetEncryptedKeyFromCryptoApiApp(
_In_ PBYTE pbKey,
_In_ DWORD dwKeyLen,
_Out_ PBYTE *ppbMessage,
_Out_ DWORD *pdwMessage
);
HRESULT GetEncryptedKeyFromCNGApp(
PBYTE pbKey,
DWORD dwKeyLen,
PBYTE *ppbMessage,
DWORD *pdwMessage
);
HRESULT ImportRsaPublicKey(
_In_ BCRYPT_ALG_HANDLE hAlg,
_In_ PUBLIC_KEY_VALUES *pKey,
_In_ BCRYPT_KEY_HANDLE *phKey
);
void ReverseMemCopy(
_Out_ BYTE *pbDest,
_In_ BYTE const *pbSource,
_In_ DWORD cb
);
//
// main:
//
// Creates an RSA key pair with CryptoAPI, passes the public
// key to a symmetric key generation function, and then
// decrypts the returned key using the private key from the
// RSA pair.
//
int main(
int argc,
char *argv[]
) {
HRESULT hr = E_FAIL;
HCRYPTPROV hCryptProv = NULL;
HCRYPTKEY hKey = NULL;
PBYTE pbData = NULL;
DWORD dwDataLen = 0;
PBYTE pbMessage = NULL;
DWORD dwMessage = 0;
int mode = 0;
if(argc == 2) {
if(!_stricmp(argv[1], "CNG")) {
mode = 1;
} else if(!_stricmp(argv[1], "CryptoAPI")) {
mode = 2;
}
}
if(!mode) {
printf("Usage: %s <CryptoAPI|CNG>\n\n"
"Source demonstrates key exchange between CNG and CryptoAPI\n", argv[0]);
return 1;
}
//
// Setup Crypto API with ephemeral keyset.
// We specify the provider and type for consistency
// across platforms and versions.
//
if(!CryptAcquireContext(
&hCryptProv, // Filled with provider handle
NULL, // No key container, we are in memory only
MS_ENHANCED_PROV, // Use the enhanced provider with bigger bitsize support
PROV_RSA_FULL, // RSA public key type (FULL required with enhanced provider)
CRYPT_VERIFYCONTEXT | // In memory only
CRYPT_SILENT)) { // No UI, probably not required
hr = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
//
// Create a new RSA key
//
if(!CryptGenKey(
hCryptProv, // Provider handle
CALG_RSA_KEYX, // RSA key exchange algorithm
0x08000000, // 2048 bit key, no other flags
&hKey)) { // Filled with key handle
hr = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
if(!CryptExportKey(
hKey, // Key handle
NULL, // Key to encrypt export, unused for public key
PUBLICKEYBLOB, // Export the public key
0, // No flags
NULL, // NULL, so get required buffer size
&dwDataLen)) { // Filled with required buffer size
hr = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
pbData = (PBYTE) CoTaskMemAlloc(dwDataLen);
if(!pbData) {
hr = E_OUTOFMEMORY;
goto cleanup;
}
if(!CryptExportKey(
hKey, // Key handle
NULL, // Key to encrypt export, unused for public key
PUBLICKEYBLOB, // Export the public key
0, // No flags
pbData, // Pointer to output buffer
&dwDataLen)) { // Output buffer size
hr = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
switch(mode) {
case 1:
hr = GetEncryptedKeyFromCNGApp(pbData, dwDataLen, &pbMessage, &dwMessage);
break;
case 2:
hr = GetEncryptedKeyFromCryptoApiApp(pbData, dwDataLen, &pbMessage, &dwMessage);
break;
}
if(!SUCCEEDED(hr)) {
goto cleanup;
}
if(!CryptDecrypt(
hKey, // Key handle
NULL, // Hash handle, unused
TRUE, // Final means no more to decrypt
0, // No flags
pbMessage, // Message data
&dwMessage)) { // Length of message data
hr = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
printf("Decrypted message (%d bytes): %s\n", dwMessage, (char *) pbMessage);
hr = S_OK;
cleanup:
if(pbMessage) {
CoTaskMemFree(pbMessage);
}
if(pbData) {
CoTaskMemFree(pbData);
}
if(hKey) {
CryptDestroyKey(hKey);
}
if(hCryptProv) {
CryptReleaseContext(hCryptProv, 0);
}
if(!SUCCEEDED(hr)) {
printf("Failed with error %x\n", hr);
return 1;
}
return 0;
}
//
// GetEncryptedKeyFromCryptoApiApp:
//
// Generates and encrypts a symmetric session key with the
// public key passed in pbKey, and returns it in ppbMessage.
//
// This version uses CryptoApi for the encryption.
//
// Memory allocated for ppbMessage by this function must be
// freed with CoTaskMemFree.
//
HRESULT GetEncryptedKeyFromCryptoApiApp(
_In_ PBYTE pbKey,
_In_ DWORD dwKeyLen,
_Out_ PBYTE *ppbMessage,
_Out_ DWORD *pdwMessage
) {
HRESULT hr = E_FAIL;
HCRYPTPROV hCryptProv = NULL;
HCRYPTKEY hKey = NULL;
PBYTE bufIn = NULL;
if(!CryptAcquireContext(
&hCryptProv,
NULL,
MS_ENH_RSA_AES_PROV,
PROV_RSA_AES,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
hr = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
if(!CryptImportKey(
hCryptProv,
pbKey,
dwKeyLen,
0,
0,
&hKey)) {
hr = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
bufIn = (PBYTE) CoTaskMemAlloc(512); // big enough for our string and the encrypted output
if(!bufIn) {
hr = E_OUTOFMEMORY;
goto cleanup;
}
char *szTest = "This is a very secret key";
strcpy_s((char *)bufIn, 512, szTest);
DWORD dwDataLen = strlen(szTest) + 1; // + terminating nul byte
DWORD dwBufLen = 512;
if(!CryptEncrypt(
hKey,
NULL,
TRUE,
0,
bufIn,
&dwDataLen,
dwBufLen)) {
hr = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
*ppbMessage = (PBYTE) CoTaskMemAlloc(dwDataLen);
if(!*ppbMessage) {
hr = E_OUTOFMEMORY;
goto cleanup;
}
*pdwMessage = dwDataLen;
memcpy(*ppbMessage, bufIn, dwDataLen);
hr = S_OK;
cleanup:
if(bufIn) {
CoTaskMemFree(bufIn);
}
if(hKey) {
CryptDestroyKey(hKey);
}
if(hCryptProv) {
CryptReleaseContext(hCryptProv, 0);
}
return hr;
}
//
// GetEncryptedKeyFromCNGApp:
//
// Generates and encrypts a symmetric session key with the
// public key passed in pbKey, and returns it in ppbMessage.
//
// This version uses CNG for the encryption.
//
// Memory allocated for ppbMessage by this function must be
// freed with CoTaskMemFree.
//
HRESULT GetEncryptedKeyFromCNGApp(
PBYTE pbKey,
DWORD dwKeyLen,
PBYTE *ppbMessage,
DWORD *pdwMessage
) {
UNREFERENCED_PARAMETER(dwKeyLen);
BCRYPT_ALG_HANDLE hAlgorithm = NULL;
BCRYPT_KEY_HANDLE hKey = NULL;
PBYTE bufIn = NULL, bufOut = NULL;
*ppbMessage = NULL;
*pdwMessage = 0;
HRESULT hr = E_FAIL;
//
// Initialise CNG for RSA and import the key
//
if(!SUCCEEDED(hr = HRESULT_FROM_NT(
BCryptOpenAlgorithmProvider(
&hAlgorithm,
BCRYPT_RSA_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
0)))) {
goto cleanup;
}
if(!SUCCEEDED(hr = HRESULT_FROM_NT(
ImportRsaPublicKey(
hAlgorithm,
(PUBLIC_KEY_VALUES *)pbKey,
&hKey)))) {
goto cleanup;
}
//
// Here's a silly message. It's our "secret key"
//
bufIn = (PBYTE) CoTaskMemAlloc(64); // big enough for our string
if(!bufIn) {
hr = E_OUTOFMEMORY;
goto cleanup;
}
char *szTest = "This is a very secret key";
strcpy_s((char *)bufIn, 64, szTest);
DWORD dwDataLen = strlen(szTest) + 1; // + terminating nul byte
//
// Calculate output buffer size and encrypt the "key"
//
if(!SUCCEEDED(hr = HRESULT_FROM_NT(
BCryptEncrypt(
hKey, // Public key handle
bufIn, // Input message
dwDataLen, // Length of input message
NULL, // No padding info needed for PKCS1
NULL, // No initialization vector
0, // IV size
NULL, // Output = NULL, so get buffer size required
0, // Size of output buffer
pdwMessage, // Filled with required buffer size
BCRYPT_PAD_PKCS1)))) { // PKCS1 padding is what CryptoAPI expects
goto cleanup;
}
bufOut = (PBYTE) CoTaskMemAlloc(*pdwMessage);
if(!bufOut) {
hr = E_OUTOFMEMORY;
goto cleanup;
}
if(!SUCCEEDED(hr = HRESULT_FROM_NT(
BCryptEncrypt(
hKey, // Public key handle
bufIn, // Input message
dwDataLen, // Length of input message
NULL, // No padding info needed for PKCS1
NULL, // No initialization vector
0, // IV size
bufOut, // Output buffer
*pdwMessage, // Size of output buffer
pdwMessage, // Filled with required buffer size
BCRYPT_PAD_PKCS1)))) { // PKCS1 padding is what CryptoAPI expects
goto cleanup;
}
//
// Copy encrypted buffer little endian
//
*ppbMessage = (PBYTE) CoTaskMemAlloc(*pdwMessage);
if(!*ppbMessage) {
hr = E_OUTOFMEMORY;
goto cleanup;
}
ReverseMemCopy(*ppbMessage, bufOut, *pdwMessage);
hr = S_OK;
cleanup:
if(bufIn) {
CoTaskMemFree(bufIn);
}
if(bufOut) {
CoTaskMemFree(bufOut);
}
if(hKey) {
BCryptDestroyKey(hKey);
}
if(hAlgorithm) {
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
}
return hr;
}
//
// ImportRsaPublicKey:
//
// Imports an RSA public key in CryptoApi format into
// a CNG key.
//
HRESULT ImportRsaPublicKey(
_In_ BCRYPT_ALG_HANDLE hAlg, // CNG provider
_In_ PUBLIC_KEY_VALUES *pKey, // Pointer to the RSAPUBKEY blob.
_In_ BCRYPT_KEY_HANDLE *phKey // Receives a handle the imported public key.
) {
HRESULT hr = S_OK;
BYTE *pbPublicKey = NULL;
DWORD cbKey = 0;
// Layout of the RSA public key blob:
// +----------------------------------------------------------------+
// | BCRYPT_RSAKEY_BLOB | BE( dwExp ) | BE( Modulus ) |
// +----------------------------------------------------------------+
//
// sizeof(BCRYPT_RSAKEY_BLOB) cbExp cbModulus
// <--------------------------><------------><---------------------->
//
// BE = Big Endian Format
DWORD cbModulus = (pKey->rsapubkey.bitlen + 7) / 8;
DWORD dwExp = pKey->rsapubkey.pubexp;
DWORD cbExp = (dwExp & 0xFF000000) ? 4 :
(dwExp & 0x00FF0000) ? 3 :
(dwExp & 0x0000FF00) ? 2 : 1;
BCRYPT_RSAKEY_BLOB *pRsaBlob;
PBYTE pbCurrent;
if(!SUCCEEDED(hr = DWordAdd(cbModulus, sizeof(BCRYPT_RSAKEY_BLOB), &cbKey))) {
goto cleanup;
}
cbKey += cbExp;
pbPublicKey = (PBYTE) CoTaskMemAlloc(cbKey);
if(pbPublicKey == NULL) {
hr = E_OUTOFMEMORY;
goto cleanup;
}
ZeroMemory(pbPublicKey, cbKey);
pRsaBlob = (BCRYPT_RSAKEY_BLOB *)(pbPublicKey);
//
// Make the Public Key Blob Header
//
pRsaBlob->Magic = BCRYPT_RSAPUBLIC_MAGIC;
pRsaBlob->BitLength = pKey->rsapubkey.bitlen;
pRsaBlob->cbPublicExp = cbExp;
pRsaBlob->cbModulus = cbModulus;
pRsaBlob->cbPrime1 = 0;
pRsaBlob->cbPrime2 = 0;
pbCurrent = (PBYTE)(pRsaBlob + 1);
//
// Copy pubExp Big Endian
//
ReverseMemCopy(pbCurrent, (PBYTE)&dwExp, cbExp);
pbCurrent += cbExp;
//
// Copy Modulus Big Endian
//
ReverseMemCopy(pbCurrent, pKey->modulus, cbModulus);
//
// Import the public key
//
hr = HRESULT_FROM_NT(BCryptImportKeyPair(
hAlg,
NULL,
BCRYPT_RSAPUBLIC_BLOB,
phKey,
(PUCHAR)pbPublicKey,
cbKey,
0
));
cleanup:
CoTaskMemFree(pbPublicKey);
return hr;
}
//
// ReverseMemCopy:
//
// Copies memory and reverses endianness of the buffer.
//
void ReverseMemCopy(
_Out_ BYTE *pbDest,
_In_ BYTE const *pbSource,
_In_ DWORD cb
) {
for(DWORD i = 0; i < cb; i++) {
pbDest[cb - 1 - i] = pbSource[i];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment