Last active
October 29, 2019 09:05
-
-
Save mcdurdin/5626617 to your computer and use it in GitHub Desktop.
RSA Key Exchange between CryptoAPI and CNG
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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