1

I have the following PHP sample that I'm trying to mimic in C#. It is using AES 128bit ECB encryption with PKCS7 padding:

$trust_jsonString="hello";
echo "input: '" . $trust_jsonString . "'\n";
echo "input (dump): " . var_dump($trust_jsonString) . "\n";

$trust_key = "9840822c-14fc-49ac-9d68-ac532f9f171e";
echo "key: '" . $trust_key . "'\n";

$blockSize=mcrypt_get_block_size(MCRYPT_RIJNDAEL_128,MCRYPT_MODE_ECB);
echo "block size: '" . $blockSize . "'\n";

$padding = $blockSize - (strlen($trust_jsonString) % $blockSize);
echo "padding: '" . $padding . "'\n";

$trust_jsonString .= str_repeat(chr($padding), $padding);

$trust_jsonString = utf8_encode($trust_jsonString);
echo "utf8 json: '" . $trust_jsonString . "'\n";
echo "utf8 json (dump): " . var_dump($trust_jsonString) . "\n";

$trust_key=utf8_encode($trust_key);
echo "encoded key: " . $trust_key . "\n";

$trust_key=(md5($trust_key));
echo "md5 hash of key (raw): " . $trust_key . "\n";

$mcrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $trust_key, $trust_jsonString, MCRYPT_MODE_ECB);
echo "mcrypt (raw): " . var_dump($mcrypt) . "\n";
echo "mcrypt: " . $mcrypt . "\n";

echo "mcrypt (raw): ";
$byte_array = byteStr2byteArray($mcrypt);
for($i=0;$i<count($byte_array);$i++)
{
   printf("%02x", $byte_array[$i]);
}
echo "\n";

$presid = base64_encode($mcrypt);
echo "presid: " . $presid . "\n";

$sid=strtr($presid,'+/', '-_');
echo "sid: " . $sid . "\n";

function byteStr2byteArray($s) {
    return array_slice(unpack("C*", "\0".$s), 1);
}

I am currently running the following C# code to try to mimic results:

static void Main( string[] args )
{
    string data = "hello";
    Encrypt(data);
}

static void Encrypt( string data )
{
    PaddingMode padding = PaddingMode.PKCS7;
    CipherMode cipherMode = CipherMode.ECB;
    int size = 128;

    Console.WriteLine("input: '" + data + "'");

    string officialKey = "9840822c-14fc-49ac-9d68-ac532f9f171e";
    Console.WriteLine("key: '" + officialKey + "'");

    Console.WriteLine("block size: '16'");
    Console.WriteLine( "padding: '11'" );

    var utf8dataBytes = Encoding.UTF8.GetBytes(data);
    var utf8data = Encoding.UTF8.GetString(utf8dataBytes);
    Console.WriteLine("utf8 json: '" + utf8data + "'");

    Console.WriteLine("encoded key (utf8): " + officialKey);

    var utf8KeyBytes = Encoding.UTF8.GetBytes(officialKey);

    var myMD5 = MD5.Create();
    var md5HashOfKey = myMD5.ComputeHash(utf8KeyBytes);

    Console.WriteLine( "md5 hash of key (raw): " + DumpBinary(md5HashOfKey) );

    byte[] encryptedBlob;

    using ( var aes = new AesManaged() )
    {
        try
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.ECB;
            aes.KeySize = 128;
            aes.BlockSize = 128;
            aes.Key = md5HashOfKey;
            //aes.IV = new byte[] { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 };

            var bytes = utf8dataBytes;
            //var bytes = ForcePaddingManually( utf8dataBytes );

            var cxform = aes.CreateEncryptor();
            encryptedBlob = cxform.TransformFinalBlock( bytes, 0, bytes.Length );
        }
        finally
        {
            aes.Clear();
        }
    }

    //var encryptedBlob = AesAlgo.Encrypt( data, md5HashOfKey, padding, cipherMode, size );
    Console.WriteLine("mcrypt (raw): " + DumpBinary(encryptedBlob));
    Console.WriteLine("mcrypt: " + Encoding.UTF8.GetString(encryptedBlob));

    var encryptedBase64 = Convert.ToBase64String(encryptedBlob);
    Console.WriteLine( "presid: " + encryptedBase64 );

    var encodedEncryptedBlob = encryptedBase64.Replace( "+", "-" ).Replace( "/", "_" );
    Console.WriteLine( "sid: " + encodedEncryptedBlob );
    Console.WriteLine("COMPLETE!");
}

static byte[] ForcePaddingManually( byte[] data )
{
    // force padding manually to test that PKCS7 works like we are expecting
    var pad = ( 16 - data.Length % 16 ) % 16;
    var bytes = new byte[data.Length + pad];
    for ( int i = 0; i < data.Length; ++i )
    {
        bytes[i] = data[i];
    }
    for ( int i = data.Length; i < data.Length + pad; ++i )
    {
        bytes[i] = (byte)pad;
    }

    return bytes;
}

static string DumpBinary( byte[] data )
{
    var sb = new StringBuilder();
    for ( int i = 0; i < data.Length; ++i )
    {
        sb.Append( data[i].ToString( "X2" ) );
    }
    return sb.ToString();
}

When I look at the results for these, this is what I get:

PHP:

input: 'hello'
string(5) "hello"
input (dump): 
key: '9840822c-14fc-49ac-9d68-ac532f9f171e'
block size: '16'
padding: '11'
utf8 json: 'hello'
string(16) "hello"
utf8 json (dump): 
encoded key: 9840822c-14fc-49ac-9d68-ac532f9f171e
md5 hash of key (raw): 6d334201cb7625323da32e0c31b2b138
string(16) "�Ҙ�= �˹��C���"
mcrypt (raw): 
mcrypt: �Ҙ�= �˹��C���
mcrypt (raw): b0d298c83d20a0cbb9f0ea4305cef2ec
presid: sNKYyD0goMu58OpDBc7y7A==
sid: sNKYyD0goMu58OpDBc7y7A==

C#:

input: 'hello'
key: '9840822c-14fc-49ac-9d68-ac532f9f171e'
block size: '16'
padding: '11'
utf8 json: 'hello'
encoded key (utf8): 9840822c-14fc-49ac-9d68-ac532f9f171e
md5 hash of key (raw): 6D334201CB7625323DA32E0C31B2B138
mcrypt (raw): 4CBAD7678AAB2B054371A1B572161280
mcrypt: L??g??+♣Cq??r▬↕?
presid: TLrXZ4qrKwVDcaG1chYSgA==
sid: TLrXZ4qrKwVDcaG1chYSgA==
COMPLETE!

There is a lot of diagnostic and misc code in there, but the basic issue is that when the same MD5 hash of the key is passed in (binaries are the same) and the same input data is passed in (bytes are the same, or I can force the padding to be the same in the C# code on input) I get different output results. I'm sure that this is something simple, but it isn't popping out to me. Is there someone who can identify the problem here?

The basic issue is that the SID shown at the bottom (the AES encrypted result) is different - what is causing the difference?

  • 1
    I know that AES with ECB isn't as secure, so please don't offer help in this regard of design - I'm stuck with a partner's existing implementation that I need to match. Thanks in advance for the thought, but this design aspect isn't something I can change. – Jeremy Buch Oct 22 '15 at 23:53
  • Mcrypt doesn't do PKCS7 for you, but OpenSSL does. Your first clue is "md5 hash of key" differing. – Scott Arciszewski Oct 23 '15 at 05:47

2 Answers2

0

The main problem is that the actual key in the PHP code is created by md5() without the optional argument to get the raw output. This means that the actual key is 32 bytes long, because it is hex encoded and it is used as-is without decoding. Essentially, you're dealing with AES-256 here.

Since you can't change the PHP code, you need to re-create this mishap in C#:

aes.KeySize = 256;
aes.Key = Encoding.ASCII.GetBytes(BitConverter.ToString(md5HashOfKey).Replace("-","").ToLower());

Output as expected:

mcrypt (raw): B0D298C83D20A0CBB9F0EA4305CEF2EC
mcrypt: �Ҙ�= �˹��C���
presid: sNKYyD0goMu58OpDBc7y7A==
sid: sNKYyD0goMu58OpDBc7y7A==

BitConverter.ToString() returns a hex string of the form "6D-33-42-01-....", which means that the dashes must be removed and it must be converted to lowercase to create the actual key.

DEMO

Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • The binary result of the md5 hash is 32 hex characters long, or 16 bytes - this is a 128bit key. The algorithm is also specifying 128bit, so I don't think this is the case. – Jeremy Buch Oct 23 '15 at 05:28
  • 32 random bytes that just so happen to be all hex characters would also be 256 bits. – Scott Arciszewski Oct 23 '15 at 05:45
  • Artjom - I tried adding your code modification in, but there are errors due to the key size if I just change KeySize and Key as you mentioned above. `aes.Padding = PaddingMode.PKCS7; aes.Mode = CipherMode.ECB; //aes.KeySize = 128; aes.BlockSize = 128; aes.KeySize = 256; aes.Key = Encoding.ASCII.GetBytes(BitConverter.ToString(md5HashOfKey)); //aes.Key = md5HashOfKey;` – Jeremy Buch Oct 23 '15 at 16:54
  • @JeremyBuch Sorry, I forgot that BitConverter.ToString() returns a hex representation with dashes. It returns the expected result now. – Artjom B. Oct 23 '15 at 17:22
  • Thanks Artjom - this results in C# matching the PHP result - THANK YOU for your help. – Jeremy Buch Oct 23 '15 at 17:57
  • @Jeremy You can [accept](http://meta.stackexchange.com/q/5234/266187) an answer if it solved your problem. – Artjom B. Oct 23 '15 at 18:01
0

Well the first thing that would help narrow down problem is the "md5 hash of key (raw):" is identical in both cases, but the base64 encoding doesn't match "md5 hash of key (base64):", so that kind of narrows down the step where the problem diverges to the code for the base64call.

Note your input is a hex string, not binary in PHP.

Testing with an online converter that is specifically for hex strings to base64, I can verify that bTNCAct2JTI9oy4MMbKxOA== is the correct base64 encoding, for the input of 0x6d334201cb7625323da32e0c31b2b138 which matches C#'s output.

So that means that PHP's base64 encoding is the culprit. There's probably a better way to retrieve the key in binary, but what you need to do is convert the hex string into binary:

<?php
echo base64_encode(pack("H*" , '6d334201cb7625323da32e0c31b2b138')) ;
?>

This produces the expected output of bTNCAct2JTI9oy4MMbKxOA==

There could be more problems than that, but that explains that difference in output.

AaronLS
  • 37,329
  • 20
  • 143
  • 202
  • Thanks Aaron - I'll take a look in the morning. The PHP code is authoritative and I need to match it (vs. change it), so I'll look at how to change the C# code so that it mimics the translation in the PHP. – Jeremy Buch Oct 23 '15 at 05:32
  • @JeremyBuch Yes, on second look you're not doing anything with the bad base64 result in PHP, so that would not effect the final result, but simply produce misleading diagnostics. – AaronLS Oct 23 '15 at 16:18
  • @JeremyBuch I found when doing encryption between C# and PHP, defaults for many functions were different. For example I found the padding methods differed, in some cases PHP didn't have a way to change that, and it was a matter of determining what method PHP used, and finding how to modify that default in C#. I didn't do exactly what you are doing, I was doing some HMAC hashes, but I know in my case I ended up not using mcrypt because it was difficult to determine what defaults/settings it was using. https://en.wikipedia.org/wiki/Padding_(cryptography) – AaronLS Oct 23 '15 at 16:23
  • @JeremyBuch Not sure if this is a problem, but according to this, using anything other than a 32 byte key will result in a different algorithm in PHP, you are using 16 byte keys: http://php.net/manual/en/function.mcrypt-encrypt.php – AaronLS Oct 23 '15 at 16:40
  • The sample on the web page you provided uses a 64 character hex key for AES 256 (32 byte). The 32 character hex key used in the sample should be for AES 128 (16 byte). Each hex character represents a nibble (half a byte), so the key size should be proper for the example that I provided from what I can see. – Jeremy Buch Oct 23 '15 at 17:49