Rijndael 256 Encrypt/Decrypt Between C# and PHP

Rijndael 256 Encrypt/decrypt between c# and php?

If you want to use Rijndael256 in your C# application you have to set the BlockSize to 256.

RijndaelManaged rj = new RijndaelManaged();
rj.BlockSize = 256;

And then your iv has to be 256 bits long as well.

see SymmetricAlgorithm.BlockSize Property


Or the other way round: Currently your C# application uses Rijndael128 and so must your php script.

<?php
class Foo {
protected $mcrypt_cipher = MCRYPT_RIJNDAEL_128;
protected $mcrypt_mode = MCRYPT_MODE_CBC;

public function decrypt($key, $iv, $encrypted)
{
$iv_utf = mb_convert_encoding($iv, 'UTF-8');
return mcrypt_decrypt($this->mcrypt_cipher, $key, base64_decode($encrypted), $this->mcrypt_mode, $iv_utf);
}
}

$encrypted = "UmzUCnAzThH0nMkIuMisqg==";
$key = "qwertyuiopasdfghjklzxcvbnmqwerty";
$iv = "1234567890123456";

$foo = new Foo;
echo $foo->decrypt($key, $iv, $encrypted);

prints hello world

Rijndael Encryption/Decryption C# vs PHP

finally got it sorted. Whole day was fighting with it and now would love to share the code with you.

Code is 100% working - Tested and Verified!

Content of C# CryptoMaster.cs file (Client Side):

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace EncryptionClient
{
class CryptoMaster
{
private string encryptedText;

public void StartEncryption()
{
Console.WriteLine("");
Console.WriteLine("----- Client Start -----");
string plainText = "Hello, this is a message we need to encrypt";
Console.WriteLine("Plain Text = " + plainText);
string passPhrase ="Pass Phrase Can be any length";
string saltValue = DateTime.Now.ToLongTimeString(); //slat should be 8 bite len, in my case im using Time HH:MM:SS as it is dynamic value
string hashAlgorithm = "SHA1";
int passwordIterations = 1;
string initVector = "InitVector Should be 32 bite len";
int keySize = 256;

encryptedText = Encrypt(plainText, passPhrase, saltValue, hashAlgorithm, passwordIterations, initVector, keySize);
Console.WriteLine("Encrypted Text = " + encryptedText);

string decryptedText = Decrypt(encryptedText, passPhrase, saltValue, hashAlgorithm, passwordIterations, initVector, keySize);
Console.WriteLine("Decripted Text = " + decryptedText);
Console.WriteLine("----- Client End -----");

SendDataToWebServer(plainText, passPhrase, saltValue, hashAlgorithm, passwordIterations, initVector, keySize);
}

private void SendDataToWebServer(string plainText, string passPhrase, string saltValue, string hashAlgorithm, int passwordIterations, string initVector, int keySize)
{

NameValueCollection POST = new NameValueCollection();
//NOTE: I'm Including all this data to POST only for TESTING PURPOSE
//and to avoid manual entering of the same data at server side.
//In real live example you have to keep sensative data hidden
POST.Add("plainText", plainText);
POST.Add("passPhrase", passPhrase);
POST.Add("saltValue", saltValue);
POST.Add("hashAlgorithm", hashAlgorithm);
POST.Add("passwordIterations", passwordIterations+"");
POST.Add("initVector", initVector);
POST.Add("keySize", keySize+"");
POST.Add("encryptedText", encryptedText);

WebClient web = new WebClient();
string URL = "http://localhost/Encryptor.php";
Console.WriteLine("");
string serverRespond = Encoding.UTF8.GetString(web.UploadValues(URL, "POST", POST));
Console.WriteLine("----- Server Start -----");
Console.WriteLine(serverRespond);
Console.WriteLine("----- Server End -----");

}

public string Encrypt(string plainText, string passPhrase, string saltValue, string hashAlgorithm, int passwordIterations, string initVector, int keySize)
{

byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

Rfc2898DeriveBytes password = new Rfc2898DeriveBytes(passPhrase, saltValueBytes, passwordIterations);

byte[] keyBytes = password.GetBytes(keySize / 8);

RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.BlockSize = 256;
symmetricKey.KeySize = 256;
symmetricKey.Padding = PaddingMode.Zeros;
symmetricKey.Mode = CipherMode.CBC;

ICryptoTransform encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);

MemoryStream memoryStream = new MemoryStream();
CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);

cryptoStream.FlushFinalBlock();
byte[] cipherTextBytes = memoryStream.ToArray();

memoryStream.Close();
cryptoStream.Close();

string cipherText = Convert.ToBase64String(cipherTextBytes);

return cipherText;
}

public static string Decrypt(string cipherText, string passPhrase, string saltValue, string hashAlgorithm, int passwordIterations, string initVector, int keySize)
{

byte[] initVectorBytes = Encoding.ASCII.GetBytes(initVector);
byte[] saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
byte[] cipherTextBytes = Convert.FromBase64String(cipherText);

Rfc2898DeriveBytes password = new Rfc2898DeriveBytes(passPhrase, saltValueBytes, passwordIterations);

byte[] keyBytes = password.GetBytes(keySize / 8);

RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.BlockSize = 256;
symmetricKey.KeySize = 256;
symmetricKey.Padding = PaddingMode.Zeros;
symmetricKey.Mode = CipherMode.CBC;

ICryptoTransform decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);

MemoryStream memoryStream = new MemoryStream(cipherTextBytes);

CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);

byte[] plainTextBytes = new byte[cipherTextBytes.Length];

int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);

memoryStream.Close();
cryptoStream.Close();

string plainText = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);

return plainText;
}
}
}

Content of PHP Encryptor.PHP file (Server Side):

<?php
error_reporting(0);

if (isset($_POST['plainText'])) {

$plainText = $_POST['plainText'];
$passPhrase = $_POST['passPhrase'];
$saltValue = $_POST['saltValue'];
$hashAlgorithm = $_POST['hashAlgorithm'];
$passwordIterations = $_POST['passwordIterations'];
$initVector = $_POST['initVector'];
$keySize = $_POST['keySize'];
$clientEncryptedText = $_POST['encryptedText'];

$key = getKey($passPhrase,$saltValue, $passwordIterations, $keySize, $hashAlgorithm);

echo "Plain Text = ".$plainText."\n";
echo "Client Encrypted Text = ".$clientEncryptedText."\n";

$encryptedText = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $plainText, MCRYPT_MODE_CBC, $initVector));
echo "Server Encrypted Text = ".$encryptedText."\n";

$decryptedText = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($encryptedText), MCRYPT_MODE_CBC, $initVector), "\0");
echo "Server Decrypted Text = ".$decryptedText."\n";

$decryptedText = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($clientEncryptedText), MCRYPT_MODE_CBC, $initVector), "\0");
echo "Client Decrypted Text = ".$decryptedText;

}

function getKey( $passPhrase, $saltValue, $passwordIterations, $keySize, $hashAlgorithm ) {

$hl = strlen(hash($hashAlgorithm, null, true));
$kb = ceil($keySize / $hl);
$dk = '';

for ( $block = 1; $block <= $kb; $block ++ ) {

$ib = $b = hash_hmac($hashAlgorithm, $saltValue . pack('N', $block), $passPhrase, true);

for ( $i = 1; $i < $passwordIterations; $i ++ )

$ib ^= ($b = hash_hmac($hashAlgorithm, $b, $passPhrase, true));

$dk .= $ib;
}

return substr($dk, 0, $keySize);
}

?>

Console Output can be viewed by this link

How do I convert this C# Rijndael encryption to PHP?

I think that the PHP version may actually add 00h valued bytes to the key and IV. They both have an invalid size : 8 bytes for each. They need to be extended to 16 bytes for AES-128. In your C# code you use 32 bytes for the key, which will therefore use AES with a key size of 256 bits.

Futhermore, you don't specify the number of iterations in PasswordDeriveBytes, you should specify it as the class does not specify the default number of iterations - according to your comments, this would be 100, lets assume it is.

Oh, and you use the incorrect encryption method. MCRYPT_RIJNDAEL_256 specifies the Rijndael algorithm using a blocksize of 256 bits, not keys of 256 bits. Presumably, the bitsize of the keys is simply the number of bytes of the key times 8.

Could you replace your Encrypt function with this and try again?

function Encrypt($pass, $salt)
{
$derived = PBKDF1($pass, $salt, 100, 48);
$key = bin2hex(substr($derived, 0, 32));
$iv = bin2hex(substr($derived, 32, 16));
return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $pass, MCRYPT_MODE_CBC, $iv);
}

Finally, please check if the generated IV and key match with the ones in PHP before performing encryption or decryption. Are you sure that that PHP PBKDF1 function is correct?

UPDATE:
Here is some more information on the M$ PBKDF1 routines in PasswordDeriveBytes (including Java code which you may try and convert):

ha, I see your point.

Interestingly, using .NET: the results are different when calling 48
or calling 32 followed by 16:

.NET GetBytes( 32 +16 ):
04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922655D8DD89AE1CAAC60A8041FCD7E8DA4

.NET GetBytes( 32 )
04DD9D139DCB9DE889946D3662B319682159FF9C9B47FA15ED205C7CAF890922
Followed by GetBytes( 16 ) 89946D3662B3196860A8041FCD7E8DA4

True Microsoft code, and they cannot change it because it could break applications in the field. Note that they also would return different results when calling it with 16 and then 8 bytes or directly by 24 bytes by design. You'd better upgrade to PBKDF2, and keep PBKDF1 limited to 20 bytes max, as defined in the standards.

PHP Encryption and C# Decryption [Rijndael]

How about this:

  public static byte[] DecryptRJ256(string prm_key, string prm_iv, string prm_text_to_decrypt)
{
var sEncryptedString = prm_text_to_decrypt;

var myRijndael = new RijndaelManaged();
myRijndael.Padding = PaddingMode.Zeros;
myRijndael.Mode = CipherMode.CBC;
myRijndael.KeySize = 256;
myRijndael.BlockSize = 256;

byte[] key;
byte[] IV;

key = Encoding.ASCII.GetBytes(prm_key);
IV = Encoding.ASCII.GetBytes(prm_iv);

var decryptor = myRijndael.CreateDecryptor(key, IV);

var sEncrypted = Convert.FromBase64String(sEncryptedString);
var fromEncrypt = new byte[sEncrypted.Length];

var msDecrypt = new MemoryStream(sEncrypted);
var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);

csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);

//Return (System.Text.Encoding.ASCII.GetString(fromEncrypt))
return fromEncrypt;

}

PHP & C# compatible Rijndael managed CBC mode, 256 bit encryption/decryption

The problem seems to be your padding, on PHP-side you are manually doing PKCS7-Padding:

$padding = $block - (strlen($strToEncrypt) % $block);
$strToEncrypt .= str_repeat(chr($padding), $padding);

And on C#-side you are using:

Padding = PaddingMode.Zeros

To fix this you could either modify the PHP-code by removing the above mentioned two lines since mcrypt() does automatically do ZeroBytePadding for you.

Or you could change the padding in C# to:

Padding = PaddingMode.PKCS7

c# MCRYPT_RIJNDAEL_256 Encryption Decryption Class in php

Your PHP code produces different results because you're not using the same key. In your C# code you're using PasswordDeriveBytes to create the key, which is Microsoft's implementation of PBKDF1.

PHP doesn't support PBKDF1, but we could write a function that is compatible with C#. The code below is inspired from this great answer by Maarten Bodewes, translated to PHP.

function passwordDeriveBytes($password, $salt, $iterations = 100, $len = 32) {
$key = $password . $salt;
for($i = 0; $i < $iterations; $i++) {
$key = sha1($key, true);
}
if (strlen($key) < $len) {
$hx = passwordDeriveBytes($password, $salt, $iterations - 1, 20);
$counter = 0;
while (strlen($key) < $len) {
$counter += 1;
$key .= sha1($counter . $hx, true);
}
}
return substr($key, 0, $len);
}

Also, you're bese64 encoding and padding your data manually. You're preforming zero byte padding, but in your C# code you're using PKCS7 (the default and preferred) padding. It's best to let openssl pad and encode your data.

function encrypt_stuff($key, $str, $iv) {
return openssl_encrypt($str, "aes-256-cbc", $key, 0, $iv);
}

function decrypt_stuff($key, $str, $iv) {
return openssl_decrypt($str, "aes-256-cbc", $key, 0, $iv);
}

Using the key derived from passwordDeriveBytes, this PHP code produces the same results as your C# code.

$key = passwordDeriveBytes("testing", null);
$enc = encrypt_stuff($key,"437217","aw90rela942f65u2");
echo $enc;
//C9xJGa03dRQx9ePm0nLnHg==

However, I don't recommend using this code for the following reasons.

  • It's best to use PBKDF2 for your key. You can use Rfc2898DeriveBytes in C#:

    Rfc2898DeriveBytes kdf = new Rfc2898DeriveBytes(password, salt, iterations);
    byte[] key = kdf.GetBytes(32);

    and hash_pbkdf2 in PHP:

    $key = hash_pbkdf2("sha1", $password, $salt, $iterations, 32, true);

    The salt should be at least 8 bytes long, and the number of iterations should be at least 10,000.

  • You're not using a salt. You should use a random salt for each password, it makes your keys stronger.

  • You're using a static IV. The IV should be unique and unpredictable. You can create a random IV with RNGCryptoServiceProvider:

    byte[] iv = new byte[16];
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    rng.GetBytes(iv);

    and openssl_random_pseudo_bytes:

    $iv = openssl_random_pseudo_bytes(16);

    You could use this code for the salt too.

  • You're not using authenticated encryption. If you're using PHP 7 you could use GCM, but unfortunately .NET doesn't provide any AEAD algorithms. However, you could use bouncycastle, if you choose to use authenticated encryption.


But your encryption code should produce different results every time you use it, because you should be using a random IV. You can decrypt the ciphertext correctly if you have the IV. The IV doesn't have to be secret; you could store it next to the ciphertext.

If you were using mcrypt with MCRYPT_RIJNDAEL_256, you could still make your PHP code compatible with C#, as RijndaelManaged supports 256 bit block size.

symmetricKey.BlockSize = 256;

However you shouldn't use mcrypt because it's not maintained, and it's deprecated in PHP 7.



Related Topics



Leave a reply



Submit