Search code examples
c#phpencryptioncryptographyrijndael

Issue matching PHP encryption to C#


I have a C# application that implements encryption. I'm working on adding some functionality in PHP to work with this application. I use Rijndael Managed encryption in the application. I am able to decrypt a string in PHP no problem, but when I try to encrypt a string it doesn't match what was created in the C# application. I've scoured stackoverflow and tried a bunch of combinations but can't seem to get things working right.

My C# code to encrypt a string is as follows:

    public static string Encrypt(string plainText, byte[] key, byte[] iv)
    {
        RijndaelManaged crypto = new RijndaelManaged();
        crypto.Key = key;
        crypto.IV = iv;
        byte[] textBytes = System.Text.Encoding.Unicode.GetBytes(plainText);        
        ICryptoTransform Encryptor = crypto.CreateEncryptor(crypto.Key, crypto.IV);
        MemoryStream mem_stream = new MemoryStream();
        CryptoStream cryptoStream = new CryptoStream(mem_stream, Encryptor, CryptoStreamMode.Write);
        cryptoStream.Write(textBytes, 0, textBytes.Length);
        cryptoStream.FlushFinalBlock();
        byte[] CipherBytes = mem_stream.ToArray();
        mem_stream.Close();
        cryptoStream.Close();
        string Encrypt_Data = Convert.ToBase64String(CipherBytes);
        return Encrypt_Data;
    }

In PHP I use the following code:

class Encryption {
    protected $cipher = MCRYPT_RIJNDAEL_128;
    protected $mode = MCRYPT_MODE_CBC;

    public function getKey()
    {
        return implode(array_map('chr', .....);
    }

    function addPadding($string)
    {
        $blocksize = mcrypt_get_block_size($this->cipher, $this->mode);
        $padding = $blocksize - (strlen($string) % $blocksize);
        $string .= str_repeat(chr($padding), $padding);
        return $string;
    }

    function Encrypt($string, $iv)
    {
        $value = rtrim(base64_encode(mcrypt_encrypt($this->cipher, $this->getKey(), $this->addPadding($string), $this->mode, $iv)));
        return $value;
    }

    function Decrypt($string, $iv)
    {
        return rtrim(mcrypt_decrypt($this->cipher, $this->getKey(), base64_decode($string), $this->mode, $iv));
    }

}

Both use the same private key (a byte array 32 bytes long). I used an existing sample to test it out as seen below:

$iv = hex2bin('B773705230CFADC864401FF2EB1FCF14');
$first = 'Jx7Khz4v+AQE3lUkIQF/SA==';

$enc = new Encryption;
$decrypted = $enc->Decrypt($first, $iv);
print_r(unpack('C*', $decrypted));
print_r(unpack('C*', 'Jackson'));
$encrypted = $enc->Encrypt('Jackson', $iv);

echo 'C# Encrypted: '.$first."\n";
echo 'Decrypted: '.$decrypted."\n";
echo 'PHP Encrypted: '.$encrypted;

This is where it gets interesting. I get the decrypted string just fine (It prints out Jackson), but the byte array for it is very odd. It seems like how the initial string is padded may be causing the issue.

Array
(
    [1] => 74
    [2] => 0
    [3] => 97
    [4] => 0
    [5] => 99
    [6] => 0
    [7] => 107
    [8] => 0
    [9] => 115
    [10] => 0
    [11] => 111
    [12] => 0
    [13] => 110
    [14] => 0
    [15] => 2
    [16] => 2
)
Array
(
    [1] => 74
    [2] => 97
    [3] => 99
    [4] => 107
    [5] => 115
    [6] => 111
    [7] => 110
)
C# Encrypted: Jx7Khz4v+AQE3lUkIQF/SA==
Decrypted: Jackson
PHP Encrypted: /qIq5gSWFPbLpGJMkGg+Zw==

It clearly seems something to do with the padding. I'm just not sure why there seems to be the 0's every other byte. I'm apparently missing something somewhere. I already have the encryption set up in c# with data already encrypted, so changing the C# code is pretty much out of the question unless I have a helper function in the application that adjusts the values before storing in the database....

Thank you very much for any help.

EDIT: Updated with solution

As Kevin pointed out the difference is between UTF-16 in the C# application and UTF-8 in PHP. To solve this, I just had to make a minor change to the Encrypt function as noted below to convert the string to the proper encoding:

    function Encrypt($string, $iv)
    {
        $string = iconv('UTF-8', 'UTF-16LE', $string);
        $value = rtrim(base64_encode(mcrypt_encrypt($this->cipher, $this->getKey(), $this->addPadding($string), $this->mode, $iv)));
        return $value;
    }

Solution

  • The reason for the 0s in the array is that you are using Unicode encoding (16 bits per character) to convert your source string to an array. If you used UTF8 or ASCII (8 bits per character) the 0s would no longer appear in the array. You just have to make sure you are using the same encoding on both sides (to create the array on the sending side and to encode the string on the recieving side). Then as long as your keys and IVs match on both sides you should be good to go.