Reputation: 21
I'm trying to recreate the following C# implementation of TripleDES ECB with PKCS7 padding in PHP using openssl_encrypt and openssl_decrypt.
private static string Key = "<some random key with umlauts and special characters length of 24>";
public static string Decrypt(string cypherText)
{
using(var des = CreateDes(Key))
{
var ct = des.CreateDecryptor();
var input = Convert.FromBase64String(cypherText);
var output = ct.TransformFinalBlock(input, 0, input.Length);
return Encoding.UTF8.GetString(output);
}
}
public static string Encrypt(string plainText)
{
using(var des = CreateDes(Key))
{
var ct = des.CreateEncryptor();
var input = Encoding.UTF8.GetBytes(plainText);
var output = ct.TransformFinalBlock(input, 0, input.Length);
return Convert.ToBase64String(output);
}
}
private static TripleDES CreateDes(string key)
{
MD5 md5 = new MD5CryptoServiceProvider();
TripleDES des = new TripleDESCryptoServiceProvider();
var desKey = md5.ComputeHash(Encoding.UTF8.GetBytes(key));
des.Key = desKey;
des.IV = new byte[des.BlockSize / 8];
des.Padding = PaddingMode.PKCS7;
des.Mode = CipherMode.ECB;
return des;
}
So far I've managed to figure out that I had to use the raw_output parameter of the md5-function in PHP to get the exact same key (compared with breakpoint in C# and getByteFromString-function in PHP) and the encryption/decryption basically is working on both sides. Except that values encrypted in C# cannot be decrypted in PHP and vice versa as the encryption results are not the same.
What I've got in PHP so far:
function getByteFromString( $value )
{
$ret = '';
for($i = 0; $i < strlen($value); $i++)
{
$ret .= '[' . $i . '] => ' . ord($value[$i])."<br/>";
}
return $ret;
}
function encrypt( $key, $value )
{
if( function_exists( 'openssl_encrypt' ) )
{
return base64_encode( openssl_encrypt( $value, 'DES-EDE3', $key, OPENSSL_RAW_DATA ) );
}
return 'openssl missing';
}
function decrypt( $key, $value )
{
if( function_exists( 'openssl_decrypt' ) )
{
return openssl_decrypt( base64_decode( $value ), 'DES-EDE3', $key, OPENSSL_RAW_DATA );
}
return 'openssl missing';
}
$sKey = md5("<the same random key with umlauts and special characters length of 24 as in c#>", true);
$number = '1234567890';
$encrypted = encrypt( $sKey, $number );
$decrypted = decrypt( $sKey, $encrypted );
// For key debugging only:
echo 'key:<br>' . getByteFromString($sKey) . '<br>';
echo 'encrypted: ' . var_export($encrypted, true) . '<br>';
echo 'decrypted: ' . var_export($decrypted, true). '<br>';
I know TripleDES should not be used anymore and ECB mode especially not, but I cannot change the C# Code, so the PHP Code has to create the same results as C# and has to be able to decrypt values encrypted in C# aswell as encrypt values so that C# can decrypt them - using TripleDES and ECB. I just can't figure out what I'm missing on PHP side.
Upvotes: 2
Views: 1262
Reputation: 1
I think the reason you need to add additional 8 bytes to the sKey is that MD5() function with the binary param set to true returns raw binary format with a length of 16 (as per the doc https://www.php.net/manual/en/function.md5.php). Then for the tripleDES to work a 24 byte key is expected. Adding first 8 bytes at the end adds up to 24 bytes. Here (https://en.wikipedia.org/wiki/Triple_DES) under "Keying options" you can check Keying option 2 - K1 and K2 are independent, and K3 = K1. Sometimes known as 2TDEA or double-length keys. (As per Wikipedia)
Upvotes: 0
Reputation: 21
Okay so I found the solution by reading through related questions. I've found it in a 7 years old question and even though it was asked for deprecated mcrypt functions, somehow it's still working even with openssl functions.
All that had to be done, was to append the first 8 bytes of the raw key to itself like this:
$sKey = md5("<the same random key with umlauts and special characters length of 24 as in c#>", true);
$sKey .= substr( $sKey, 0, 8 );
Which gives the following, working example in PHP:
function encrypt( $key, $value )
{
if( function_exists( 'openssl_encrypt' ) )
{
return base64_encode( openssl_encrypt( $value, 'DES-EDE3', $key, OPENSSL_RAW_DATA ) );
}
return 'openssl missing';
}
function decrypt( $key, $value )
{
if( function_exists( 'openssl_decrypt' ) )
{
return openssl_decrypt( base64_decode( $value ), 'DES-EDE3', $key, OPENSSL_RAW_DATA );
}
return 'openssl missing';
}
$sKey = md5("<the same random key with umlauts and special characters length of 24 as in c#>", true);
$sKey .= substr( $sKey, 0, 8 ); //Added this line to fix it
$number = '1234567890';
$encrypted = encrypt( $sKey, $number );
$decrypted = decrypt( $sKey, $encrypted );
echo 'encrypted: ' . var_export($encrypted, true) . '<br>';
echo 'decrypted: ' . var_export($decrypted, true). '<br>';
If anyone can comment why this is the solution, please do so. While I'm pleased with it working, I'd really like to understand why it's working. I cannot see anything like this happening in C# so why do I have to do it in PHP?
Upvotes: 0