I am currently working on a project written in C++ that leverages the CryptoAPI to perform a Diffie-Hellman key exchange. I'm having a bit of trouble getting this to work as the eventual RC4 session key I get cannot be used to encrypt the same text in Python (using pycrypto).
The C++ code to perform the Diffie-Hellman key exchange was taken from msdn, but is included here for posterity:
#include <tchar.h>
#include <windows.h>
#include <wincrypt.h>
#pragma comment(lib, "crypt32.lib")
// The key size, in bits.
#define DHKEYSIZE 512
// Prime in little-endian format.
static const BYTE g_rgbPrime[] =
{
0x91, 0x02, 0xc8, 0x31, 0xee, 0x36, 0x07, 0xec,
0xc2, 0x24, 0x37, 0xf8, 0xfb, 0x3d, 0x69, 0x49,
0xac, 0x7a, 0xab, 0x32, 0xac, 0xad, 0xe9, 0xc2,
0xaf, 0x0e, 0x21, 0xb7, 0xc5, 0x2f, 0x76, 0xd0,
0xe5, 0x82, 0x78, 0x0d, 0x4f, 0x32, 0xb8, 0xcb,
0xf7, 0x0c, 0x8d, 0xfb, 0x3a, 0xd8, 0xc0, 0xea,
0xcb, 0x69, 0x68, 0xb0, 0x9b, 0x75, 0x25, 0x3d,
0xaa, 0x76, 0x22, 0x49, 0x94, 0xa4, 0xf2, 0x8d
};
// Generator in little-endian format.
static BYTE g_rgbGenerator[] =
{
0x02, 0x88, 0xd7, 0xe6, 0x53, 0xaf, 0x72, 0xc5,
0x8c, 0x08, 0x4b, 0x46, 0x6f, 0x9f, 0x2e, 0xc4,
0x9c, 0x5c, 0x92, 0x21, 0x95, 0xb7, 0xe5, 0x58,
0xbf, 0xba, 0x24, 0xfa, 0xe5, 0x9d, 0xcb, 0x71,
0x2e, 0x2c, 0xce, 0x99, 0xf3, 0x10, 0xff, 0x3b,
0xcb, 0xef, 0x6c, 0x95, 0x22, 0x55, 0x9d, 0x29,
0x00, 0xb5, 0x4c, 0x5b, 0xa5, 0x63, 0x31, 0x41,
0x13, 0x0a, 0xea, 0x39, 0x78, 0x02, 0x6d, 0x62
};
BYTE g_rgbData[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
int _tmain(int argc, _TCHAR* argv[])
{
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
BOOL fReturn;
HCRYPTPROV hProvParty1 = NULL;
HCRYPTPROV hProvParty2 = NULL;
DATA_BLOB P;
DATA_BLOB G;
HCRYPTKEY hPrivateKey1 = NULL;
HCRYPTKEY hPrivateKey2 = NULL;
PBYTE pbKeyBlob1 = NULL;
PBYTE pbKeyBlob2 = NULL;
HCRYPTKEY hSessionKey1 = NULL;
HCRYPTKEY hSessionKey2 = NULL;
PBYTE pbData = NULL;
/************************
Construct data BLOBs for the prime and generator. The P and G
values, represented by the g_rgbPrime and g_rgbGenerator arrays
respectively, are shared values that have been agreed to by both
parties.
************************/
P.cbData = DHKEYSIZE/8;
P.pbData = (BYTE*)(g_rgbPrime);
G.cbData = DHKEYSIZE/8;
G.pbData = (BYTE*)(g_rgbGenerator);
/************************
Create the private Diffie-Hellman key for party 1.
************************/
// Acquire a provider handle for party 1.
fReturn = CryptAcquireContext(
&hProvParty1,
NULL,
MS_ENH_DSS_DH_PROV,
PROV_DSS_DH,
CRYPT_VERIFYCONTEXT);
if(!fReturn)
{
goto ErrorExit;
}
// Create an ephemeral private key for party 1.
fReturn = CryptGenKey(
hProvParty1,
CALG_DH_EPHEM,
DHKEYSIZE << 16 | CRYPT_EXPORTABLE | CRYPT_PREGEN,
&hPrivateKey1);
if(!fReturn)
{
goto ErrorExit;
}
// Set the prime for party 1's private key.
fReturn = CryptSetKeyParam(
hPrivateKey1,
KP_P,
(PBYTE)&P,
0);
if(!fReturn)
{
goto ErrorExit;
}
// Set the generator for party 1's private key.
fReturn = CryptSetKeyParam(
hPrivateKey1,
KP_G,
(PBYTE)&G,
0);
if(!fReturn)
{
goto ErrorExit;
}
// Generate the secret values for party 1's private key.
fReturn = CryptSetKeyParam(
hPrivateKey1,
KP_X,
NULL,
0);
if(!fReturn)
{
goto ErrorExit;
}
/************************
Create the private Diffie-Hellman key for party 2.
************************/
// Acquire a provider handle for party 2.
fReturn = CryptAcquireContext(
&hProvParty2,
NULL,
MS_ENH_DSS_DH_PROV,
PROV_DSS_DH,
CRYPT_VERIFYCONTEXT);
if(!fReturn)
{
goto ErrorExit;
}
// Create an ephemeral private key for party 2.
fReturn = CryptGenKey(
hProvParty2,
CALG_DH_EPHEM,
DHKEYSIZE << 16 | CRYPT_EXPORTABLE | CRYPT_PREGEN,
&hPrivateKey2);
if(!fReturn)
{
goto ErrorExit;
}
// Set the prime for party 2's private key.
fReturn = CryptSetKeyParam(
hPrivateKey2,
KP_P,
(PBYTE)&P,
0);
if(!fReturn)
{
goto ErrorExit;
}
// Set the generator for party 2's private key.
fReturn = CryptSetKeyParam(
hPrivateKey2,
KP_G,
(PBYTE)&G,
0);
if(!fReturn)
{
goto ErrorExit;
}
// Generate the secret values for party 2's private key.
fReturn = CryptSetKeyParam(
hPrivateKey2,
KP_X,
NULL,
0);
if(!fReturn)
{
goto ErrorExit;
}
/************************
Export Party 1's public key.
************************/
// Public key value, (G^X) mod P is calculated.
DWORD dwDataLen1;
// Get the size for the key BLOB.
fReturn = CryptExportKey(
hPrivateKey1,
NULL,
PUBLICKEYBLOB,
0,
NULL,
&dwDataLen1);
if(!fReturn)
{
goto ErrorExit;
}
// Allocate the memory for the key BLOB.
if(!(pbKeyBlob1 = (PBYTE)malloc(dwDataLen1)))
{
goto ErrorExit;
}
// Get the key BLOB.
fReturn = CryptExportKey(
hPrivateKey1,
0,
PUBLICKEYBLOB,
0,
pbKeyBlob1,
&dwDataLen1);
if(!fReturn)
{
goto ErrorExit;
}
/************************
Export Party 2's public key.
************************/
// Public key value, (G^X) mod P is calculated.
DWORD dwDataLen2;
// Get the size for the key BLOB.
fReturn = CryptExportKey(
hPrivateKey2,
NULL,
PUBLICKEYBLOB,
0,
NULL,
&dwDataLen2);
if(!fReturn)
{
goto ErrorExit;
}
// Allocate the memory for the key BLOB.
if(!(pbKeyBlob2 = (PBYTE)malloc(dwDataLen2)))
{
goto ErrorExit;
}
// Get the key BLOB.
fReturn = CryptExportKey(
hPrivateKey2,
0,
PUBLICKEYBLOB,
0,
pbKeyBlob2,
&dwDataLen2);
if(!fReturn)
{
goto ErrorExit;
}
/************************
Party 1 imports party 2's public key.
The imported key will contain the new shared secret
key (Y^X) mod P.
************************/
fReturn = CryptImportKey(
hProvParty1,
pbKeyBlob2,
dwDataLen2,
hPrivateKey1,
0,
&hSessionKey2);
if(!fReturn)
{
goto ErrorExit;
}
/************************
Party 2 imports party 1's public key.
The imported key will contain the new shared secret
key (Y^X) mod P.
************************/
fReturn = CryptImportKey(
hProvParty2,
pbKeyBlob1,
dwDataLen1,
hPrivateKey2,
0,
&hSessionKey1);
if(!fReturn)
{
goto ErrorExit;
}
/************************
Convert the agreed keys to symmetric keys. They are currently of
the form CALG_AGREEDKEY_ANY. Convert them to CALG_RC4.
************************/
ALG_ID Algid = CALG_RC4;
// Enable the party 1 public session key for use by setting the
// ALGID.
fReturn = CryptSetKeyParam(
hSessionKey1,
KP_ALGID,
(PBYTE)&Algid,
0);
if(!fReturn)
{
goto ErrorExit;
}
// Enable the party 2 public session key for use by setting the
// ALGID.
fReturn = CryptSetKeyParam(
hSessionKey2,
KP_ALGID,
(PBYTE)&Algid,
0);
if(!fReturn)
{
goto ErrorExit;
}
/************************
Encrypt some data with party 1's session key.
************************/
// Get the size.
DWORD dwLength = sizeof(g_rgbData);
fReturn = CryptEncrypt(
hSessionKey1,
0,
TRUE,
0,
NULL,
&dwLength,
sizeof(g_rgbData));
if(!fReturn)
{
goto ErrorExit;
}
// Allocate a buffer to hold the encrypted data.
pbData = (PBYTE)malloc(dwLength);
if(!pbData)
{
goto ErrorExit;
}
// Copy the unencrypted data to the buffer. The data will be
// encrypted in place.
memcpy(pbData, g_rgbData, sizeof(g_rgbData));
// Encrypt the data.
dwLength = sizeof(g_rgbData);
fReturn = CryptEncrypt(
hSessionKey1,
0,
TRUE,
0,
pbData,
&dwLength,
sizeof(g_rgbData));
if(!fReturn)
{
goto ErrorExit;
}
/************************
Decrypt the data with party 2's session key.
************************/
dwLength = sizeof(g_rgbData);
fReturn = CryptDecrypt(
hSessionKey2,
0,
TRUE,
0,
pbData,
&dwLength);
if(!fReturn)
{
goto ErrorExit;
}
ErrorExit:
if(pbData)
{
free(pbData);
pbData = NULL;
}
if(hSessionKey2)
{
CryptDestroyKey(hSessionKey2);
hSessionKey2 = NULL;
}
if(hSessionKey1)
{
CryptDestroyKey(hSessionKey1);
hSessionKey1 = NULL;
}
if(pbKeyBlob2)
{
free(pbKeyBlob2);
pbKeyBlob2 = NULL;
}
if(pbKeyBlob1)
{
free(pbKeyBlob1);
pbKeyBlob1 = NULL;
}
if(hPrivateKey2)
{
CryptDestroyKey(hPrivateKey2);
hPrivateKey2 = NULL;
}
if(hPrivateKey1)
{
CryptDestroyKey(hPrivateKey1);
hPrivateKey1 = NULL;
}
if(hProvParty2)
{
CryptReleaseContext(hProvParty2, 0);
hProvParty2 = NULL;
}
if(hProvParty1)
{
CryptReleaseContext(hProvParty1, 0);
hProvParty1 = NULL;
}
return 0;
}
I believe that I can complete the Diffie-Hellman key exchange in Python, as I can generate the same public and private keys without error. I've based my Diffie-Hellman key exchange on this repository.
I haven't been able to test this, however as I can't seem to get the shared secret exported from the C++ code (similar to this thread, that was never satisfactorily answered). I can however get the RC4 session key with the following code:
// Get the key length
DWORD keylen;
CryptExportKey(
hSessionKey1,
NULL,
PLAINTEXTKEYBLOB,
0,
NULL,
&keylen);
// Get the session key
CryptExportKey(
hSessionKey1,
NULL,
PLAINTEXTKEYBLOB,
0,
encKey,
&keylen);
The output from this function gets me:
08 02 00 00 01 68 00 00 10 00 00 00 75 2c 59 8c 6e e0 8c 9f ed 30 17 7e 9d a5 85 2b
I know there is a 12 byte header+length on this, so that leaves me with the following 16 byte RC4 session key:
75 2c 59 8c 6e e0 8c 9f ed 30 17 7e 9d a5 85 2b
So I am currently trying to validate that I can encrypt the same plaintext using the RC4 that I have acquired from the CryptExportKey
. I am currently trying to encrypt g_rgbData
from the C++ code above, which is set to:
BYTE g_rgbData[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
With the C++ code I get the following encrypted output:
cc 94 aa ec 86 6e a8 26
Using pycrypto I have the following code:
from Crypto.Cipher import ARC4
key = '75 2c 59 8c 6e e0 8c 9f ed 30 17 7e 9d a5 85 2b'
key = key.replace(' ', '').decode('hex')
plaintext = '0102030405060708'
plaintext = plaintext.replace(' ', '').decode('hex')
rc4 = ARC4.new(key)
encrypted = rc4.encrypt(plaintext)
print encrypted.encode('hex')
This results in the following output:
00 5b 64 25 4e a5 62 e3
Which doesn't match the C++ output. I've played around with endianess, but I suspect something else might be going on.
Sorry if this is long winded, but it brings me to my two questions:
Whenever you transition from the shared key to RC4 (using CryptSetKeyParam
with CALG_RC4
), what is actually going on under the hood here? I can't seem to find any information about this process anywhere so that I can implement it in Python.
Any idea why my RC4 will not work with the same key and the same plaintext in Python?
Any help would be greatly appreciated!
Finally had some time to look over your code. When I run your code locally, I am able to export the session key and can use it successfully in pycrypto. My guess is that you are either not exporting the session key correctly (e.g. is what you posted what you are running?) or the data you are encrypting in C++ is not the same data that you are encrypting in Python - double check that the data you are encrypting is also correct. I suspect that it's probably the latter, as there isn't really much you can screw up with the CryptExportKey
you've posted.
According to the PyCrypto docs your key must be minimum 40 bytes:
key (byte string) - The secret key to use in the symmetric cipher. It can have any length, with a minimum of 40 bytes. Its cryptograpic strength is always capped to 2048 bits (256 bytes).
but then there is a contradiction just above:
key_size = xrange(1, 257)
Size of a key (in bytes)
allowing key-lengths to be from 1-256, so I'm not sure if this even helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With