I am trying to convert the following php code to C#:
$m_params = urlencode(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256,$key, json_encode($arParams), MCRYPT_MODE_ECB)));
What the documentation says:
m_params : A JSON array of data of additional parameters encrypted using the Rijindael-256 algorithm and encoded using a base64 algorithm.
What I've assumed?
Step 1: Create an array of params i.e. $arParams For php its declared like:
$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',
);
For C# I've declared it like this:
var additional_params = new object[]
{
new {"http://google.com/new_success_url"},
new {"http://google.com/new_fail_url"},
new {"http://google.com/new_status_url"},
};
Step 2: Encode to JSON string, I've used JsonConvert.SerializeObject(additional_params);
Step 3: Encrypt the result using RIJNDAEL-256 Algorithm using ECB (I've used CBC as well)
Step 4: Encode the result using base64. I've used Convert.ToBase64String(encrypted);
Step 5: Url encode the result. I've used HttpUtility.UrlEncode(base64String, Encoding.UTF8);
Step 6: Save the result in m_params
My current code looks like this:
var additional_params = new object[]
{
new {"http://google.com/new_success_url"},
new {"http://google.com/new_fail_url"},
new {"http://google.com/new_status_url"},
};
string m_params ="";
//converting to Json object additional params
var jsonEncoded = JsonConvert.SerializeObject(additional_params);
try
{
string original = jsonEncoded;
// Create a new instance of the RijndaelManaged
// class. This generates a new key and initialization
// vector (IV).
using (RijndaelManaged myRijndael = new RijndaelManaged())
{
var final_Key = CreateMD5(payeer.m_key + payeer.m_orderid);
var rfc = CreateKey(final_Key);
// Encrypt the string to an array of bytes.
byte[] encrypted = EncryptStringToBytes(original, rfc[0], rfc[1]);
var base64String = Convert.ToBase64String(encrypted);
m_params = HttpUtility.UrlEncode(base64String, Encoding.UTF8);
// Decrypt the bytes to a string.
string roundtrip = DecryptStringFromBytes(encrypted, rfc[0], rfc[1]);
//Display the original data and the decrypted data.
Console.WriteLine("Original: {0}", original);
Console.WriteLine("Round Trip: {0}", roundtrip);
}
static byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encrypted;
// Create an RijndaelManaged object
// with the specified key and IV.
using (RijndaelManaged rijAlg = new RijndaelManaged())
{
rijAlg.Key = Key;
rijAlg.IV = IV;
rijAlg.Mode = CipherMode.ECB;
// rijAlg.KeySize = 256;
rijAlg.BlockSize = 256;
rijAlg.Padding = PaddingMode.PKCS7;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
public static string CreateMD5(string input)
{
// Use input string to calculate MD5 hash
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);
// Convert the byte array to hexadecimal string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("X2"));
}
return sb.ToString();
}
}
public static dynamic CreateKey(string password)
{
var salt = new byte[] { 1, 2, 23, 234, 37, 48, 134, 63, 248, 4 };
const int Iterations = 9872;
using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, Iterations))
{
var key = rfc2898DeriveBytes.GetBytes(32);
var IV = rfc2898DeriveBytes.GetBytes(16);
dynamic[] arr = new dynamic[2];
arr[0] = key;
arr[1] = IV;
return arr;
}
}
Its not giving the same output. Am I missing something??
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.