2

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;
    }
SparcU
  • 742
  • 1
  • 7
  • 27
  • 2
    What is your question/problem exactly? – Mat Apr 21 '12 at 11:00
  • 3
    Your key is too short (120 bits), minimum is 128 bits. [MSDN](http://msdn.microsoft.com/en-us/library/system.security.cryptography.tripledes.key.aspx): This algorithm supports key lengths from 128 bits to 192 bits in increments of 64 bits – Ňuf Apr 21 '12 at 11:20
  • What is the output from the C# code? Base64 or Hex encode that output and add it to your question. In order to duplicate that output exactly in C++, it would help to see what it looks like in C#. –  Apr 27 '12 at 15:36

4 Answers4

8

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;
}
Skrymsli
  • 5,173
  • 7
  • 34
  • 36
  • This sample worked great. Not sure what I did wrong but now it works fine – SparcU May 01 '12 at 13:53
  • Yeah, I couldn't see the problem either. I don't think the problem was in the code you posted or I just couldn't squint hard enough to see it. – Skrymsli May 01 '12 at 16:36
  • @Skrymsli When I set the IV to 24 bytes, I get "Specified initialization vector (IV) does not match the block size for this algorithm" error message. Is this something to do with CipherMode? – Joshua Son Apr 01 '14 at 10:13
  • 3DES has a 64bit block size. The IV has to match this, so 24 bytes isn't valid for 3DES. Are you using a different algorithm? If not See http://en.wikipedia.org/wiki/Triple_DES – Skrymsli Apr 09 '14 at 15:39
1

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!

Community
  • 1
  • 1
jbtule
  • 31,383
  • 12
  • 95
  • 128
  • I fixed the key issue. Now it is 16 bytes. Still encrypted data is different using .NET and C++ methods – SparcU Apr 27 '12 at 15:28
0

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.

MiMo
  • 11,793
  • 1
  • 33
  • 48
  • C++ is UNICODE but char and string are not affected as much as I know – SparcU Apr 21 '12 at 12:22
  • If strings are Unicode `(LPBYTE)cipherText.c_str()` is `0x35 0x00 0x36 0x00 . . .`, whereas `Encoding.ASCII.GetBytes("5656b2e4ba2f22e");` is `0x35 0x36 . . .` – MiMo Apr 21 '12 at 12:31
  • You're right - UNICODE would be `wstring` - to be sure I checked `(LPBYTE)cipherText.c_str()` and it is the sequence of the ASCII codes. – MiMo Apr 23 '12 at 07:46
0

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.

dascandy
  • 7,184
  • 1
  • 29
  • 50