Reputation: 752
I have some code for encryption in C# that I have to rewrite in C++ I saw several similar questions here on SO but somehow I still could not figure this out. Encoding the same string with the same password yields different results.
The C# code
byte[] TestEncrypt(string data)
{
byte[] plainText = System.Text.Encoding.ASCII.GetBytes(data);
TripleDES des3 = new System.Security.Cryptography.TripleDESCryptoServiceProvider();
des3.Mode = CipherMode.CBC;
des3.Key = System.Text.Encoding.ASCII.GetBytes("12656b2e4ba2f22e");
des3.IV = System.Text.Encoding.ASCII.GetBytes("d566gdbc");
ICryptoTransform transform = des3.CreateEncryptor();
MemoryStream memStreamEncryptedData = new MemoryStream();
CryptoStream encStream = new CryptoStream(memStreamEncryptedData,
transform, CryptoStreamMode.Write);
encStream.Write(plainText, 0, plainText.Length);
encStream.FlushFinalBlock();
encStream.Close();
byte[] cipherText = memStreamEncryptedData.ToArray();
return cipherText;
}
Result 255,142,22,151,93,255,156,10,174,10,250,92,144,0,60,142 EDITED: Added new C++ version
string Test3DES()
{
string key = "12656b2e4ba2f22e";
HCRYPTPROV hCryptProv = NULL;
HCRYPTHASH hHash = NULL;
HCRYPTKEY hCryptKey = NULL;
char pIV[] = "d566gdbc"; //simple test IV for 3DES
CryptAcquireContext(&hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL,CRYPT_VERIFYCONTEXT);
PlainTextKeyBlob keyBlob ={0};
keyBlob.hdr.bType = PLAINTEXTKEYBLOB;
keyBlob.hdr.bVersion = CUR_BLOB_VERSION;
keyBlob.hdr.reserved = 0;
keyBlob.hdr.aiKeyAlg = CALG_3DES_112;
keyBlob.cbKeySize = key.size();
memcpy(keyBlob.key, key.c_str(), key.size());
DWORD dwSizeBlob = sizeof(BLOBHEADER)+sizeof(DWORD)+key.size();
ret = CryptImportKey( hCryptProv, (const BYTE*)&keyBlob, dwSizeBlob, 0, CRYPT_EXPORTABLE, &hCryptKey );
DWORD dwMode = CRYPT_MODE_CBC;
CryptSetKeyParam(hCryptKey, KP_MODE, (BYTE*)&dwMode, 0);
CryptSetKeyParam(hCryptKey, KP_IV,(const BYTE*) pIV, 0) ;
DWORD dwFilled = 0;
BOOL ret = CryptEncrypt( hCryptKey, NULL, TRUE, 0, (LPBYTE)cipherText.c_str(), &dwFilled, (DWORD)str.size());
cipherText.resize(dwFilled);
if( hCryptKey ) CryptDestroyKey( hCryptKey );
if( hHash ) CryptDestroyHash( hHash );
if( hCryptProv ) CryptReleaseContext( hCryptProv, 0 );
return cipherText;
}
result 167,177,201,56,123,240,169,174
Old C++ version
C++
string Test3DES()
{
string key = "12656b2e4ba2f22e";
HCRYPTPROV hCryptProv = NULL;
HCRYPTHASH hHash = NULL;
HCRYPTKEY hCryptKey = NULL;
char pIV[] = "d566gdbc"; //simple test IV for 3DES
CryptAcquireContext(&hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
CryptCreateHash( hCryptProv, CALG_MD5, NULL, 0, &hHash );
CryptHashData( hHash, (LPBYTE)key.c_str(), (DWORD)key.size(), 0 );
DWORD dwMode = CRYPT_MODE_CBC;
CryptDeriveKey(hCryptProv, CALG_3DES, hHash, 0, &hCryptKey);
CryptSetKeyParam(hCryptKey, KP_MODE, (BYTE*)&dwMode, 0);
CryptSetKeyParam(hCryptKey, KP_IV,(const BYTE*) pIV, 0) ;
DWORD dwFilled = 0;
BOOL ret = CryptEncrypt( hCryptKey, NULL, TRUE, 0, (LPBYTE)cipherText.c_str(), &dwFilled, (DWORD)str.size());
cipherText.resize(dwFilled);
if( hCryptKey ) CryptDestroyKey( hCryptKey );
if( hHash ) CryptDestroyHash( hHash );
if( hCryptProv ) CryptReleaseContext( hCryptProv, 0 );
return cipherText;
}
Upvotes: 2
Views: 3317
Reputation: 5313
I set up some sample projects starting with your code. You didn't include everything so I had to add some stuff. By the time I compiled and tested I was getting the same answer in both C++ and C#. I suspect the problem may be something in the way you are specifying the cipherText buffer? This is all of my test code so it should be easy for you to set up some sample project and see if you get the same result too, then maybe you can figure it out from there:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace _3dtest
{
class Program
{
static byte[] TestEncrypt(string data)
{
byte[] plainText = System.Text.Encoding.ASCII.GetBytes(data);
TripleDES des3 = new System.Security.Cryptography.TripleDESCryptoServiceProvider();
des3.Mode = CipherMode.CBC;
des3.Key = System.Text.Encoding.ASCII.GetBytes("12656b2e4ba2f22e");
des3.IV = System.Text.Encoding.ASCII.GetBytes("d566gdbc");
ICryptoTransform transform = des3.CreateEncryptor();
MemoryStream memStreamEncryptedData = new MemoryStream();
CryptoStream encStream = new CryptoStream(memStreamEncryptedData,
transform, CryptoStreamMode.Write);
encStream.Write(plainText, 0, plainText.Length);
encStream.FlushFinalBlock();
encStream.Close();
byte[] cipherText = memStreamEncryptedData.ToArray();
return cipherText;
}
static void Main(string[] args)
{
var info = TestEncrypt("password");
foreach (byte b in info)
{
Console.Write(b.ToString());
Console.Write(", ");
}
Console.WriteLine();
}
}
}
C++
#include "stdafx.h"
#include <Windows.h>
#include <WinCrypt.h>
#include <cassert>
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
struct PlainTextKeyBlob {
BLOBHEADER hdr;
DWORD cbKeySize;
BYTE key[16];
};
std::wstring LastError(DWORD lasterr)
{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
return (wchar_t*)lpMsgBuf; // Leaking, don't care
}
std::vector<BYTE> Test3DES(const std::string& passwd)
{
string key = "12656b2e4ba2f22e";
unsigned char pIV[] = "d566gdbc"; //simple test IV for 3DES
HCRYPTPROV hCryptProv = NULL;
HCRYPTHASH hHash = NULL;
HCRYPTKEY hCryptKey = NULL;
DWORD ret = CryptAcquireContext(&hCryptProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL,CRYPT_VERIFYCONTEXT);
if( ret == 0 ) std::wcout << LastError(GetLastError()) << std::endl;
PlainTextKeyBlob keyBlob ={0};
keyBlob.hdr.bType = PLAINTEXTKEYBLOB;
keyBlob.hdr.bVersion = CUR_BLOB_VERSION;
keyBlob.hdr.reserved = 0;
keyBlob.hdr.aiKeyAlg = CALG_3DES_112;
keyBlob.cbKeySize = key.size();
memcpy(keyBlob.key, key.c_str(), key.size());
DWORD dwSizeBlob = sizeof(BLOBHEADER)+sizeof(DWORD)+key.size();
ret = CryptImportKey( hCryptProv, (const BYTE*)&keyBlob, dwSizeBlob, 0, CRYPT_EXPORTABLE, &hCryptKey );
if( ret == 0 ) std::wcout << LastError(GetLastError()) << std::endl;
DWORD dwMode = CRYPT_MODE_CBC;
CryptSetKeyParam(hCryptKey, KP_MODE, (BYTE*)&dwMode, 0);
CryptSetKeyParam(hCryptKey, KP_IV,(const BYTE*) pIV, 0) ;
std::vector< BYTE > buffer( 1024 );
memcpy( &buffer[0], passwd.c_str(), passwd.size() );
DWORD dwFilled = passwd.size();
ret = CryptEncrypt( hCryptKey, NULL, TRUE, 0, (LPBYTE)&buffer[0], &dwFilled, (DWORD)buffer.size());
if( ret == 0 ) std::wcout << LastError(GetLastError()) << std::endl;
buffer.resize(dwFilled);
if( hCryptKey ) CryptDestroyKey( hCryptKey );
if( hHash ) CryptDestroyHash( hHash );
if( hCryptProv ) CryptReleaseContext( hCryptProv, 0 );
return buffer;
}
int _tmain(int argc, _TCHAR* argv[])
{
auto result = Test3DES("password");
std::for_each( begin(result), end(result), [](BYTE b) {
cout << to_string( (_ULonglong)b ) << " ";
});
cout << std::endl;
return 0;
}
Upvotes: 8
Reputation: 7302
Keep in mind that the DES keys are self-parity keys. That means that one bit of each byte is used to check whether your key is correct. Perhaps you have these bits wrong? You should be able to find that out using the DES wikipedia page.
Also, you need at least two whole (64-bit) keys for TDES to work.
Upvotes: 0
Reputation: 31809
So your key System.Text.Encoding.ASCII.GetBytes("5656b2e4ba2f22e")
is 15 bytes, which isn't a valid key length for Triple DES you want 16 bytes or 24 bytes. My guess is that the two implementations are compensating for that fact that they want a larger key in different ways.
P.S.
It is really odd to take the raw byte value of an ascii constant string to use for the Key and IV. In your example the Key and IV are hexadecimal numbers in a string. It seems like they should be double the byte length you want so that every two characters represented a full range of possible bytes and then converted using a Hexadecimal conveter, How can I convert a hex string to a byte array?, C++ convert string to hexadecimal and vice versa
The security implication of having always having a hexidecimal represented key, that you only get the ASCII bytes of for your algorithm, is that you are cutting the possible the keyspace down by a square root!
Upvotes: 1
Reputation: 11983
Your C++ is compiled with wide/Unicode strings - so you are using as key the wide/Unicode bytes corresponding to the string "5656b2e4ba2f22e"
(30 bytes probably), whereas in C# your are converting the same string to bytes using the ASCII encoding, so you get the ASCII bytes for the same string (15 bytes).
Try to use Unicode encoding in C#, or better declare the keys as arrays of bytes instead than strings.
EDIT
Wrong answer - string
are always 8 bits per character, so the problem cannot be due to a difference in encoding.
Upvotes: 0