3

I've spent a couple hours now trying to figure this out, but I just can't get it to work. I've got a C# encryption routine that I need to match in php. I can't change the C# version, that's not an option (3rd party is firm on this).

Here's the C# code:

//In C#
// Console.WriteLine(ApiEncode("testing", "56dsfkj3kj23asdf83kseegflkj43458afdl"));
// Results in: 
//     XvHbR/CsLTo=
public static string ApiEncode(string data, string secret)
{
  byte[] clear;

  var encoding = new UTF8Encoding();
  var md5 = new MD5CryptoServiceProvider();

  byte[] key = md5.ComputeHash(encoding.GetBytes(secret));

  TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
  des.Key = key;
  des.Mode = CipherMode.ECB;
  des.Padding = PaddingMode.PKCS7;

  byte[] input = encoding.GetBytes(data);
  try { clear = des.CreateEncryptor().TransformFinalBlock(input, 0, input.Length); }
  finally
  {
    des.Clear();
    md5.Clear();
  }

  return Convert.ToBase64String(clear);
}

Here's the best of what I've come up with in PHP:

//In PHP
// echo apiEncode("testing", "56dsfkj3kj23asdf83kseegflkj43458afdl");
// Results in: 
//    5aqvY6q1T54=
function apiEncode($data, $secret)
{    
  //Generate a key from a hash
  $key = md5(utf8_encode($secret), true);
  //Create init vector  
  $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_3DES, MCRYPT_MODE_ecb), MCRYPT_RAND); 

  //Pad for PKCS7
  $blockSize = mcrypt_get_block_size('tripledes', 'ecb');
  $len = strlen($data);
  $pad = $blockSize - ($len % $blockSize);
  $data .= str_repeat(chr($pad), $pad);

  //Encrypt data
  $encData = mcrypt_encrypt('tripledes', $key, $data, 'ecb'); //, $iv);
  return base64_encode($encData);
}

To the best of my knowledge, I'm handling the PKCS7 padding properly on the PHP side. I'm not sure what else to try.

One thing to note, the C# is happening on windows, and the PHP on linux, not sure that should make a difference.

Redth
  • 5,464
  • 6
  • 34
  • 54
  • could it be the character encoding thats different - its explicitly utf-8 in c#, but what is it in php? what are the outputs of the md5 call in both languages. – time4tea Jan 14 '11 at 20:14
  • Possible duplicate of [tripledes encryption not yielding same results in PHP and C#](http://stackoverflow.com/questions/2467419/tripledes-encryption-not-yielding-same-results-in-php-and-c). – netcoder Jan 14 '11 at 20:20
  • @netcoder - Not quite the same answer, so I would say not a duplicate. – erickson Jan 14 '11 at 20:31
  • @time4tea - since these particular strings contain only ASCII characters, it probably won't make a difference; the only encoding I know of that doesn't encode these characters the same as UTF-8 is EBCDIC. – erickson Jan 14 '11 at 20:34
  • 1
    i saw the other question, and i tried working with it, but I still can't get this working right, which is why I posted another question.. – Redth Jan 14 '11 at 20:35
  • Yeah I've tried explicitly using utf8_encode which makes no difference :( – Redth Jan 14 '11 at 20:35
  • 1
    If I do: $strArr = str_split($key); foreach ($strArr as $strChar) echo(" " . ord($strChar)); I get: 77 173 222 29 122 78 74 3 80 240 63 130 5 137 93 188 Which is identical to the bytes in my c# byte[] key after I compute the md5 hash. The keys are the same. – Redth Jan 14 '11 at 20:41

4 Answers4

10

The padding length in your PHP version is based on the length of the password. This is incorrect. It should be based on the length of your message instead.

Try replacing strlen($password) with strlen($data).


The second problem is that the mcrypt library requires 24-byte keys. Triple DES applies regular DES three times, so you can call the 8-byte key used in each round of DES K1, K2, and K3. There are different ways to choose these keys. The most secure is to choose three distinct keys. Another way is to set K3 equal to K1. The least secure method (equivalent to DES) is to make K1 = K2 = K3.

Most libraries are "smart" enough to interpret a 16-byte 3DES key as the second option above: K3 = K1. The .NET implementation is doing this for you, but the mcrypt library is not; instead, it's setting K3 = 0. You'll need to fix this yourself, and pass mcrypt a 24-byte key.

After computing the MD5 hash, take the first 8 bytes of $key, and append them to the end of $key, so that you have a 24-byte value to pass to mcrypt_encrypt().

erickson
  • 265,237
  • 58
  • 395
  • 493
  • Fixed the sample code above, and my code i was testing.. I still don't get the same encrypted string from both, so that's not it apparently... – Redth Jan 14 '11 at 20:37
  • @Redth - What do you get as a result now? Do you still get 2 blocks of cipher text (24 characters) when you should have just one (12 characters)? – erickson Jan 14 '11 at 20:40
  • @erickson no, you're right, i do get a single block of cipher text now: 5aqvY6q1T54= is my base64_encode($encData) result now from PHP – Redth Jan 14 '11 at 20:43
  • @Redth - Hmm. Since you are using ECB mode, the IV is not necessary. Perhaps its presence is interfering with proper encryption. Is there another version of the `mcrypt_encrypt()` function that doesn't require the `iv` parameter, or can you pass `null`, `0`, or whatever PHP's equivalent of "undefined" is instead of generating the IV? – erickson Jan 14 '11 at 20:46
  • I think there's an issue somehow with the md5 still. If I switch c# to: byte[] key = encoding.GetBytes("ShHhd8a08JhJiho98ayslcjh"); and switch php to: $key = "ShHhd8a08JhJiho98ayslcjh"; my encryption then works. So, something's off with the md5 hashing that's different between systems, which is strange since I get the same bytes on the keys when I inspect them both... – Redth Jan 14 '11 at 20:46
  • @erickson mcrypt_encrypt() doesn't require $iv to be specified but it uses \0's in that case. My result is the same whether or not I specify the IV... – Redth Jan 14 '11 at 20:48
  • more interesting... if I call mcrypte_get_key_size('tripledes','ecb') I get 24 back. My key is 16, so it's being padded with \0's according to the documentation, to fill 24. Now on the c# side, my keysize is shown as 16... perhaps something's funny here? – Redth Jan 14 '11 at 20:58
  • yup, that's it... If I change my c# to use a byte[24] key, then copy the 16 bytes from the md5 hash into it, and use the 24 byte key, it works... Now, since I can't change the c# source, how do i get php to not use a 24 byte key size? – Redth Jan 14 '11 at 21:00
  • @Redth - Okay, that makes sense. `mcrypt` isn't handling the various keying options supported by 3DES for you like most libraries do. I amended my answer to explain how to work around this. – erickson Jan 14 '11 at 21:12
  • @erickson I tried just doing $key .= substr($key, 0, 8); which doesn't seem to work either... got an example? – Redth Jan 14 '11 at 21:18
  • looks actually like c# is not taking the first 8 bytes and appending them to the key. If I manually do that, then my php modification matches up, but again, I can't modify the c#. I wonder what c# is doing then in the case that I only provide 16 bytes.... I guess it's a matter of how it fills out the rest of the 24 bytes... – Redth Jan 14 '11 at 21:21
  • @Redth - Hmm, it's working for others; see comments at [the mcrypt manual.](http://php.net/manual/en/function.mcrypt-encrypt.php#47973) But it looks like you are doing the exact same things with bad results. – erickson Jan 14 '11 at 21:31
  • @erickson maybe for ECB mode they do it differently in .NET? As I mentioned it works if I force the 24 byte key in .NET (by appending the first 8 bytes to the end of the original 16 byte key). But it's not working If I just use a 16 byte key in .NET, and then on the php, append the first 8 bytes of the key again onto itself... – Redth Jan 14 '11 at 21:41
  • 1
    @Redth - I did some testing myself (in Java), and I get the same results as your .NET when using a 16-byte key. Also, if I create a 24-byte key as described (setting the last 8 bytes equal to the first 8 bytes), I get the same cipher text result (my results don't change). It sounds like you're saying that your .NET code produces different result if you create the 24-byte key yourself. What is the ciphertext then? And, when you create the 24-byte key in PHP, what is your ciphertext? – erickson Jan 14 '11 at 21:45
  • @erickson you're right. I had forgotten to change md5(""); back to md5("", true); after some testing I was doing.. It's all working now! Thanks so much for your help! – Redth Jan 15 '11 at 14:16
1

I found a solution, check this link, may help you. http://sanity-free.com/131/triple_des_between_php_and_csharp.html

And here is the decrypt function just in case:

    public static string Decrypt(string cypherString)
    {

        byte[] key = Encoding.ASCII.GetBytes("icatalogDR0wSS@P6660juht");
        byte[] iv = Encoding.ASCII.GetBytes("iCatalog");
        byte[] data = Convert.FromBase64String(cypherString);
        byte[] enc = new byte[0];
        TripleDES tdes = TripleDES.Create();
        tdes.IV = iv;
        tdes.Key = key;
        tdes.Mode = CipherMode.CBC;
        tdes.Padding = PaddingMode.Zeros;
        ICryptoTransform ict = tdes.CreateDecryptor();
        enc = ict.TransformFinalBlock(data, 0, data.Length);
        return UTF8Encoding.UTF8.GetString(enc, 0, enc.Length);
    }
Peter O.
  • 32,158
  • 14
  • 82
  • 96
Sebastian
  • 31
  • 1
  • 4
0

Take a look at encoding.getBytes, you need the secret key Bytes from UTF8...

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
-1

It appears the C# version does not set the IV. This could be an issue if you dont know what it is because msdn says:

The IV property is automatically set to a new random value whenever you create a new instance of one of the SymmetricAlgorithm classes or when you manually call the GenerateIV method.

It looks like in the PHP version, you are using an IV. You could try not supplying the IV and hope the C# version also uses zeros.

Edit: Looks like for ECB, the IV is ignored.

You might also need to encoding the key like in the C# version using utf8-encode

SwDevMan81
  • 48,814
  • 22
  • 151
  • 184