TripleDES cryptography between .NET (C#) and PHP5

Hi, I’m a PHP guy. We’re setting up some communication channels between a PHP5 project and a .NET project that need to share information back and forth. This is being done using SOAP requests to a Web Service (PHP -> .NET) and RESTful URLs (.NET -> PHP). At least one of these channels needs to handle encrypted data, and since the .NET side is already using Triple DES for some functions we’ve decided to use that for this purpose as well.

On the .NET side, the cryptography provider is configured using something like:

<symmetricCryptoProviders>
  <add algorithmType="System.Security.Cryptography.TripleDESCryptoServiceProvider, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=aaaaaaaa12345678"
    protectedKeyFilename="D:\\Somesite\\www.somesite.com\\wwwroot\\App_Data\\ClientKey_MachineEncrypted.key"
    protectedKeyProtectionScope="LocalMachine" type="Microsoft.Practices.EnterpriseLibrary.Security.Cryptography.SymmetricAlgorithmProvider, Microsoft.Practices.EnterpriseLibrary.Security.Cryptography, Version=4.1.0.0, Culture=neutral, PublicKeyToken=bbbbbbbb12345678"
    name="TripleDESCryptoServiceProvider" />
</symmetricCryptoProviders>

and then called via:

public static string Encrypt(string data)
{
  return Cryptographer.EncryptSymmetric(ConfigurationManager.AppSettings["TripleDESCryptoServiceProvider"], data);
}

I have access to the private key files (both the original and the xxx_MachineEncrypted.key) and the real tokens. I’ve also asked my .NET counterpart to send me the encrypted output of a sample string so that I can make sure I’m generating cyphers the same way on my end.

Setting up a TripleDES cypher on the PHP side is pretty typical, however I’ve been unable to come anywhere near to the same output. I’ve tried the solutions mentioned at http://mishu666.wordpress.com/2007/08/20/problem-and-solve-of-3des-incompatibilities-with-nets-tripledescryptoserviceprovider, but they don’t appear to be compatible when a binary key file is used. I’ve also tried the solution listed at http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_20106825.html (scroll to the very bottom).

Does anyone have any tips on what to try next?

Some specific questions:

  1. What block cipher mode (CBC, OFB, CFB, or ECB) does Cryptographer.EncryptSymmetric use by default?
  2. Does Cryptographer.EncryptSymmetric use the contents of the key file as-is (binary) or convert to some other encoding (hex, base64, etc.)?
  3. Is Cryptographer.EncryptSymmetric documented somewhere so that I can see what goes on inside of it?

FYI - I was able to solve my problem using a solution based on the one described at http://sanity-free.org/131/triple_des_between_php_and_csharp.html. I ended up with the following code to encrypt:

In PHP:

<?php
// very simple ASCII key and IV
$key = "passwordDR0wSS@P6660juht";
$iv = "password";

$cipher = mcrypt_module_open(MCRYPT_3DES, '', 'cbc', '');

// ENCRYPTING
printvar(
  SimpleTripleDes('Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.'),
  'Encrypted:'
);

// DECRYPTING
printvar(
  SimpleTripleDesDecrypt('51196a80db5c51b8523220383de600fd116a947e00500d6b9101ed820d29f198c705000791c07ecc1e090213c688a4c7a421eae9c534b5eff91794ee079b15ecb862a22581c246e15333179302a7664d4be2e2384dc49dace30eba36546793be'),
  'Decrypted:'
);

function SimpleTripleDes($buffer) {
  global $key, $iv, $cipher;
  printvar($buffer, 'Encrypting:');

  // get the amount of bytes to pad
  $extra = 8 - (strlen($buffer) % 8);
  //printvar($extra, 'Padding with n zeros');

  // add the zero padding
  if($extra > 0) {
    for($i = 0; $i < $extra; $i++) {
      $buffer .= "\\0";
    }
  }

  mcrypt_generic_init($cipher, $key, $iv);
  $result = bin2hex(mcrypt_generic($cipher, $buffer));
  mcrypt_generic_deinit($cipher);
  return $result;
}

function SimpleTripleDesDecrypt($buffer) {
  global $key, $iv, $cipher;
  printvar($buffer, 'Decrypting:');

  mcrypt_generic_init($cipher, $key, $iv);
  $result = rtrim(mdecrypt_generic($cipher, hex2bin($buffer)), "\\0");
  mcrypt_generic_deinit($cipher);
  return $result;
}

function hex2bin($data)
{
  $len = strlen($data);
  return pack("H" . $len, $data);
} 

// HELPER FUNCTIONS

function printvar($var, $label="") {
	print "<pre style=\\"border: 1px solid #999; background-color: #f7f7f7; color: #000; overflow: auto; width: auto; text-align: left; padding: 1em;\\">" .
		(
			(
				strlen(
					trim($label)
				)
			) ? htmlentities($label)."\
===================\
" : ""
		) .
		htmlentities(print_r($var, TRUE)) . "</pre>";
}
?>

In C#:

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

public class MyClass
{
	public static void RunSnippet()
	{
		// ENCRYPTING
		string result = SimpleTripleDes("Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.");
		WL(result);
		WL("");
		
		// DECRYPTING
		result = SimpleTripleDesDecrypt("51196a80db5c51b8523220383de600fd116a947e00500d6b9101ed820d29f198c705000791c07ecc1e090213c688a4c7a421eae9c534b5eff91794ee079b15ecb862a22581c246e15333179302a7664d4be2e2384dc49dace30eba36546793be");
		WL(result);
		
		// PAUSE
		RL();
	}
	
	static string SimpleTripleDes(string Data) {
        byte[] key = Encoding.ASCII.GetBytes("passwordDR0wSS@P6660juht");
        byte[] iv = Encoding.ASCII.GetBytes("password");
        byte[] data = Encoding.ASCII.GetBytes(Data);
        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.CreateEncryptor();
        enc = ict.TransformFinalBlock(data, 0, data.Length);
        return ByteArrayToString(enc);
    }
	
	static string SimpleTripleDesDecrypt(string Data) {
        byte[] key = Encoding.ASCII.GetBytes("passwordDR0wSS@P6660juht");
        byte[] iv = Encoding.ASCII.GetBytes("password");
        byte[] data = StringToByteArray(Data);
        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 Encoding.ASCII.GetString(enc);
    }
	
    public static string ByteArrayToString(byte[] ba) {
		string hex = BitConverter.ToString(ba);
		return hex.Replace("-","");
	}
	
	public static byte[] StringToByteArray(String hex) {
		int NumberChars = hex.Length;
		byte[] bytes = new byte[NumberChars / 2];
		for (int i = 0; i < NumberChars; i += 2)
			bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
		return bytes;
	}
	
	#region Helper methods
	
	public static void Main()
	{
		try
		{
			RunSnippet();
		}
		catch (Exception e)
		{
			string error = string.Format("---\
The following error occurred while executing the snippet:\
{0}\
---", e.ToString());
			Console.WriteLine(error);
		}
		finally
		{
			Console.Write("Press any key to continue...");
			Console.ReadKey();
		}
	}

	private static void WL(object text, params object[] args)
	{
		Console.WriteLine(text.ToString(), args);	
	}
	
	private static void RL()
	{
		Console.ReadLine();	
	}
	
	private static void Break() 
	{
		System.Diagnostics.Debugger.Break();
	}

	#endregion
}

(Note the C# code is from my test script using Snippet Compiler and I have no idea how one would make it run outside of that program. I merely needed to show proof of concept.)

The default (at least in .NET 3.5) is CBC (http://msdn.microsoft.com/en-us/library/system.security.cryptography.symmetricalgorithm.mode(v=VS.90).aspx).

Out of curiosity, what platform is the PHP end running on (e.g, *nix, Mac, Windows)?

Cheers,
D.

Windows. The development machines are different for both sites, but they run on the same server in production.