Cross Platform (PHP to C# .Net) Encryption/Decryption with Rijndael

Cross platform (php to C# .NET) encryption/decryption with Rijndael Part 2

  1. I realized that .NET decoding for Base 64 string is really weird. When I called DecryptRJ256() I was sending in the Key and IV that I received from the php code by a series of conversion base64_string -> byte -> utf8_string before sending both into the function. The solution to this is to just send in the byte array directly and let DecryptRJ256() deal with it directly.

  2. After doing the above, the problem with automated Key and IV generation becomes apparent and no longer is a problem.

Code Modified From Question:

PHP

$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
$text = "This is my encrypted message";

$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC, $iv);

$crypttext = base64_encode($crypttext);
$key= base64_encode($key);
$iv = base64_encode($iv);

C#

string plainText = ValidationControls.DecryptRJ256(Convert.FromBase64String("/*$CRYPTTEXT STRING FROM PHP*/"), Convert.FromBase64String("/*$KEY STRING FROM PHP*/"), Convert.FromBase64String("/*$ STRING FROM PHP*/"));

static public String DecryptRJ256(byte[] cypher, byte[] KeyString, byte[] IVString)
{
...
var Key = KeyString;
//var Key = encoding.GetBytes(KeyString);
var IV = IVString;
//var IV = encoding.GetBytes(IVString);
...
}

php to C# A JSON array of data encrypted using the Rijindael-256 algorithm and encoded using a base64 algorithm

As mentioned in President James K. Polk's comment, Rijndael with a block size of 256 bits is only supported in the .NET Framework, not in .NET Core. You did not specify the version you are running, but since you use a block size of 256 bits in the posted code (rijAlg.BlockSize = 256;), I assume you are running .NET Framework (otherwise, you need to apply a third party library that supports Rijndael with a block size of 256 bits, such as BouncyCastle/C#).

Both codes use a different padding. mcrypt applies Zero padding by default, the C# code explicitly uses PKCS7 padding (which is also the C# default). So that the C# code provides the same result as the PHP code, it is necessary to switch to Zero padding in the C# code (it should be noted that Zero padding is unreliable, unlike PKCS7 padding).

When additional_params is instantiated (which, by the way, does not compile on my machine), the variable names are missing, so they are also missing in the serialization. An anonymous type could be used instead. Also, note that json_encode() escapes the slash (/) by default, i.e. converts it to a \/, which has to be done manually in the C# code, e.g. with Replace("/", "\\/"). One possible implementation of the JSON serialization is:

using Newtonsoft.Json;
...
var additionalParams = new
{
success_url = "http://google.com/new_success_url",
fail_url = "http://google.com/new_fail_url",
status_url = "http://google.com/new_status_url"
};
string jsonEncoded = JsonConvert.SerializeObject(additionalParams).Replace("/", "\\/");

In the PHP code, the key is derived from a password using the MD5 digest. By default, md5() returns the result as a hexadecimal string, which converts the 16 bytes hash into a 32 bytes value that is applied as the key, so that AES-256 is used. PHP represents the hexadecimal digits with lowercase letters, which must also be implemented accordingly in the C# code, e.g.:

using System;
using System.Text;
using System.Security.Cryptography;
...
MD5 md5 = MD5.Create();
string password = "My password"; // test password
byte[] passwordHash = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
string passwordHashHex = BitConverter.ToString(passwordHash).Replace("-", "").ToLower(); // convert byte array to lowercase hex string as in PHP
byte[] key = Encoding.UTF8.GetBytes(passwordHashHex);

where the conversion of the byte array to the hexadecimal string is done with BitConverter, see here.

A possible implementation for the encryption is:

using System;
using System.IO;
using System.Web;
using System.Text;
using System.Security.Cryptography;
...
byte[] encrypted = null;
using (RijndaelManaged rijndael = new RijndaelManaged())
{
rijndael.Key = key;
rijndael.Mode = CipherMode.ECB; // default: CBC
rijndael.BlockSize = 256; // default: 128
rijndael.Padding = PaddingMode.Zeros; // default: PKCS7

ICryptoTransform encryptor = rijndael.CreateEncryptor(rijndael.Key, null);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(jsonEncoded);
}
encrypted = msEncrypt.ToArray();
}
}
}
string base64String = Convert.ToBase64String(encrypted);
string m_params = HttpUtility.UrlEncode(base64String, Encoding.UTF8);
Console.WriteLine(m_params);

where this code with the used test password gives the following result:

C3pldgsLDSqfG28cbt%2fv0uiBNQT6cWn86iRwg%2bv2blTzR7Lsnra%2b2Ok35Ex9f9UbG%2bjhKgITUQ8kO3DrIrWUQWirzYzwGBucHNRThADf60rGUIBDdjZ2kOIhDVXUzlMsZtBvYIgFoIqFJXCbhZq9GGnKtABUOa5pcmIYeUn%2b%2fqG1mdtJenP5vt8n0eTxsAd6CFc1%2bguR0wZx%2fEZAMsBBRw%3d%3d

in accordance with the result of the following PHP code:

$key = md5('My password'); // test password
$arParams = array(
'success_url' => 'http://google.com/new_success_url',
'fail_url' => 'http://google.com/new_fail_url',
'status_url' => 'http://google.com/new_status_url',
);
$m_params = urlencode(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256,$key, json_encode($arParams), MCRYPT_MODE_ECB)));
print($m_params . "\n");

Note that C# uses lowercase letters for the url encoding, while PHP uses uppercase letters, which represents the same url encoding, see RFC 3986, sec. 2.1. If the C# code should nevertheless also apply uppercase letters for the url encoding, this can easily be achieved using regular expressions, see e.g. here.


A few remarks regarding security:

The PHP code applies the insecure ECB mode. For security reasons, a mode with an IV should be used, e.g. CBC or GCM. The latter provides implicit authenticated encryption. The IV is randomly generated for each encryption, is not secret and is sent to the recipient along with the ciphertext (usually prepended).

MD5 as a key derivation function (KDF) is also insecure. Here, a reliable KDF should be used, e.g. PBKDF2.

In addition, using the hexadecimal string as the key weakens the same, since each byte is reduced to the 16 values of the hexadecimal number system. More secure is the use of the binary data generated by the KDF, so that each byte can take 256 different values.

mcrypt is deprecated. A possible alternative is openssl.

PHP Encryption and Decryption with Data from POST Method using C#

The reason it doesn't work is your padding is wrong. PKCS7 is the byte value of the pad length repeated(i.e. 00000010 00000010 if your padding 2 bytes). It is not the string value "0202", It appears there aren't any php functions that do this correctly, so I'd sugest you use a a mode of operation that does not need padding. OFB is supported by both c# and php.

YOU CANNOT USE A Fixed IV. For cbc mode, its fairly insecure, for OFB, its completely insecure. Use mcrypt_create_iv to get a new random one each time. Then just prepend the IV to the ciphertext when you send it ( it does not need to be encrypted). As a note, one problem you may already have hit is that php uses a string and C# uses byts for the IV and you may not be getting the correct conversion even now . I'd probably use hex and the functions to covert to/from that just to be sure.

Second, you need to use something to detect when people tamper with your data, otherwise they potentially read the cipher text via error codes/ timing issues in the underlying crypto libraries. Hmacs work well and are supported here for php and here for c#. HMAC your IV+ciphertext message and prepend the output to it . On the other end, run the c# equivalent function over the same data, and then compare the HMAC values. If they are the same,you safe, if not, reject.



Related Topics



Leave a reply



Submit