Search code examples
c#phpphp-openssltripledesecb

Convert C# TripleDES ECB decrypt/encrypt into PHP with openssl_encrypt/openssl_decrypt


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.


Solution

  • 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?