Porting C# TripleDESCryptoServiceProvider encryption to PHP

2k views Asked by At

There's a project written in C# which uses the following code to encrypt/decrypt data to/from database:

public string EncryptString(string Text)
{
    byte[] IV = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8 };
    string Key = "abcdef";
    byte[] buffer = Encoding.UTF8.GetBytes(Text);
    TripleDESCryptoServiceProvider triple = new TripleDESCryptoServiceProvider();
    MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
    triple.IV = IV;
    triple.Key = md5.ComputeHash(ASCIIEncoding.UTF8.GetBytes(Key));
    byte[] encodeText = triple.CreateEncryptor().TransformFinalBlock(buffer, 0, buffer.Length);
    string user = Convert.ToBase64String(encodeText);
    return user;
}

public static string DecryptString(string EncryptText)
{
    byte[] IV = new byte[8] { 1, 2, 3, 4, 5, 6, 7, 8 };
    string Key = "abcdef";
    byte[] decodeText = null;
    byte[] buffer = Convert.FromBase64String(EncryptText);
    TripleDESCryptoServiceProvider triple = new TripleDESCryptoServiceProvider();
    MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
    triple.IV = IV;
    triple.Key = md5.ComputeHash(ASCIIEncoding.UTF8.GetBytes(Key));
    decodeText = triple.CreateDecryptor().TransformFinalBlock(buffer, 0, buffer.Length);
    return Encoding.UTF8.GetString(decodeText);
}

Now I'm supposed to do the same in PHP. So far I came as close as the following code:

define('CIPHER', 'tripledes');
define('MODE', 'cbc');

function Encrypt($data)
{ 
    $iv = chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8);
    $key = 'abcdef';
    $tripleKey = substr(md5($key), 0, mcrypt_get_key_size(CIPHER, MODE));
    $encodedText = mcrypt_encrypt(CIPHER, $tripleKey, $data, MODE, $iv);
    return base64_encode($encodedText);
}

function Decrypt($data)
{
    $iv = chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8);
    $key = 'abcdef';
    $tripleKey = substr(md5($key), 0, mcrypt_get_key_size(CIPHER, MODE));
    $decodedText = mcrypt_decrypt(CIPHER, $tripleKey, base64_decode($data), MODE, $iv);
    return $decodedText;
}

But I can not decrypt a string encrypted by the C#'s code! I'm no encryption expert, could someone please tell me which encryption algorithm and which mode should I use?

1

There are 1 answers

1
Mormegil On BEST ANSWER

Your main problem lies in the handling of the key. One issue is trivial: In PHP, the md5 function by default returns a hexadecimal dump of the hash, while you need the raw binary value to match C#’s MD5CryptoServiceProvider.ComputeHash(). For that, just add one true parameter to the PHP call (md5(key, true)).

The other issue is a tiny bit more complicated: MD5 returns a 128-bit hash, while 3DES uses a 192-bit key (actually, 168-bit of key with the rest being parity bits, but let’s ignore that here). When you supply a too short key to the PHP implementation, it pads the key using zero bytes; however the .NET/CSP implementation of 3DES seems to repeat the key bytes from the beginning (wrapping the key around). Therefore, to match the .NET behavior in PHP, you need to wrap the key manually ($key . $key).

And the final issue is padding: PHP pads the data using zeros, while .NET uses PKCS#7 padding by default. If you need to handle padding correctly, you need to add it when encrypting, and remove it when decrypting.

define('CIPHER', 'tripledes');
define('MODE', 'cbc');

function Encrypt($data)
{ 
    $iv = chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8);
    $key = 'abcdef';

    // determine key bytes from the key text, using MD5 and wrapping around
    $key = md5($key, true);
    $key = $key . $key;
    $tripleKey = substr($key, 0, mcrypt_get_key_size(CIPHER, MODE));

    // add PKCS#7 padding
    $blocksize = mcrypt_get_block_size(CIPHER, MODE);
    $paddingSize = $blocksize - (strlen($data) % $blocksize);
    $data .= str_repeat(chr($paddingSize), $paddingSize);

    $encodedText = mcrypt_encrypt(CIPHER, $tripleKey, $data, MODE, $iv);
    return base64_encode($encodedText);
}

function Decrypt($data)
{
    $iv = chr(1) . chr(2) . chr(3) . chr(4) . chr(5) . chr(6) . chr(7) . chr(8);

    // determine key bytes from the key text, using MD5 and wrapping around
    $key = 'abcdef';
    $key = md5($key, true);
    $key = $key . $key;
    $tripleKey = substr($key, 0, mcrypt_get_key_size(CIPHER, MODE));

    $decodedText = mcrypt_decrypt(CIPHER, $tripleKey, base64_decode($data), MODE, $iv);

    // check and remove PKCS#7 padding
    if (!$decodedText) return $decodedText;
    $lastByte = ord($decodedText[strlen($decodedText) - 1]);
    if ($lastByte == 0 || $lastByte > mcrypt_get_block_size(CIPHER, MODE)) return FALSE;
    $paddingText = substr($decodedText, -$lastByte, $lastByte);
    $decodedText = substr($decodedText, 0, -$lastByte);
    if ($paddingText != str_repeat(chr($lastByte), $lastByte)) return FALSE;

    return $decodedText;
}

But, as a commentator above has already said, this is not really a good cryptographic implementation for many reasons. (And my implementation of the tweaks is not really hardened, either.)