Encoding in RSA in C# for Symfony(PHP) [duplicate] - c#

I'm trying to encrypt some (cookie) data in C# and then decrypt it in PHP. I have chosen to use Rijndael encryption. I've almost got it working, except only part of the text is decrypted! I started working from this example: Decrypt PHP encrypted string in C#
Here's the text (JSON) that I am encrypting (sensitive information removed):
{"DisplayName":"xxx", "Username": "yyy", "EmailAddress":"zzz"}
So I login to the C# app which creates/encodes the cookie from stored Key and IV and then redirects to the PHP app which is supposed to decrypt/read the cookie. When I decrypt the cookie, it comes out like this:
{"DisplayName":"xxx","F�A ;��HP=D�������4��z����ť���k�#E���R�j�5�\�t. t�D��"
UPDATE: i've gotten a little bit further and this is now the result
string(96) "{"DisplayName":"xxx","Username":"yyy","EmailAddress"�)ق��-�J��k/VV-v� �9�B`7^"
As you can see, it starts decrypting it, but then gets messed up...
When Decrypt the string it comes out correct (with padding, which I have a function to remove padding), but if I change the test string by one character I get garbage again:
B�nHL�Ek �¿?�UΣlO����OЏ�M��NO/�f.M���Lƾ�CC�Y>F��~�qd�+
Here's the c# code I use to generate the random Key and IV:
UPDATE: I'm just using static key/IV for now, here they are:
Key: lkirwf897+22#bbtrm8814z5qq=498j5
IV: 741952hheeyy66#cs!9hjv887mxx7#8y
RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.BlockSize = 256;
symmetricKey.KeySize = 256;
symmetricKey.Padding = PaddingMode.Zeros;
symmetricKey.Mode = CipherMode.CBC;
string key = Convert.ToBase64String(symmetricKey.Key);
string IV = Convert.ToBase64String(symmetricKey.IV);
I then save the key and IV to a database to be retrieved later for encoding/decoding.
This is the full encryption class:
public static class Encryption
{
public static string Encrypt(string prm_text_to_encrypt, string prm_key, string prm_iv)
{
var sToEncrypt = prm_text_to_encrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
//FeedbackSize = 256
};
var key = Encoding.ASCII.GetBytes(prm_key);
var IV = Encoding.ASCII.GetBytes(prm_iv);
//var key = Convert.FromBase64String(prm_key);
//var IV = Convert.FromBase64String(prm_iv);
var encryptor = rj.CreateEncryptor(key, IV);
var msEncrypt = new MemoryStream();
var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
var toEncrypt = Encoding.ASCII.GetBytes(sToEncrypt);
csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
csEncrypt.FlushFinalBlock();
var encrypted = msEncrypt.ToArray();
return (Convert.ToBase64String(encrypted));
}
public static string Decrypt(string prm_text_to_decrypt, string prm_key, string prm_iv)
{
var sEncryptedString = prm_text_to_decrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
//FeedbackSize = 256
};
var key = Encoding.ASCII.GetBytes(prm_key);
var IV = Encoding.ASCII.GetBytes(prm_iv);
//var key = Convert.FromBase64String(prm_key);
//var IV = Convert.FromBase64String(prm_iv);
var decryptor = rj.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 (Encoding.ASCII.GetString(fromEncrypt));
}
public static void GenerateKeyIV(out string key, out string IV)
{
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
//FeedbackSize = 256
};
rj.GenerateKey();
rj.GenerateIV();
key = Convert.ToBase64String(rj.Key);
IV = Convert.ToBase64String(rj.IV);
}
}
Here's the PHP code I am using to decrypt the data:
function decryptRJ256($key,$iv,$string_to_decrypt)
{
$string_to_decrypt = base64_decode($string_to_decrypt);
$rtn = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $string_to_decrypt, MCRYPT_MODE_CBC, $iv);
//$rtn = rtrim($rtn, "\0\4");
$rtn = unpad($rtn);
return($rtn);
}
function unpad($value)
{
$blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
//apply pkcs7 padding removal
$packing = ord($value[strlen($value) - 1]);
if($packing && $packing < $blockSize){
for($P = strlen($value) - 1; $P >= strlen($value) - $packing; $P--){
if(ord($value{$P}) != $packing){
$packing = 0;
}//end if
}//end for
}//end if
return substr($value, 0, strlen($value) - $packing);
}
$ky = 'lkirwf897+22#bbtrm8814z5qq=498j5'; // 32 * 8 = 256 bit key
$iv = '741952hheeyy66#cs!9hjv887mxx7#8y'; // 32 * 8 = 256 bit iv
$enc = $_COOKIE["MyCookie"];
$dtext = decryptRJ256($ky, $iv, $enc);
var_dump($dtext);
I am a little unsure about this part, because all of the example code I've seen simply passes in the base64 encoded string directly to the decryptor, but in my example, I have to base64_decode it before I pass it otherwise I get the error that the key and IV are not the correct length.
UPDATE: I'm using ASCII keys in the format needed by PHP. If I generate keys from the RijndaelManaged class they dont work on the PHP side, but I can use keys that are known to work on PHP side and use them in the RijndaelManaged C# side.
Please let me know if I left out any pertinent information. TIA!

For posterity I'm placing the fully completed solution here.
C# Encryption Class
public static class Encryption
{
public static string Encrypt(string prm_text_to_encrypt, string prm_key, string prm_iv)
{
var sToEncrypt = prm_text_to_encrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
};
var key = Convert.FromBase64String(prm_key);
var IV = Convert.FromBase64String(prm_iv);
var encryptor = rj.CreateEncryptor(key, IV);
var msEncrypt = new MemoryStream();
var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
var toEncrypt = Encoding.ASCII.GetBytes(sToEncrypt);
csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
csEncrypt.FlushFinalBlock();
var encrypted = msEncrypt.ToArray();
return (Convert.ToBase64String(encrypted));
}
public static string Decrypt(string prm_text_to_decrypt, string prm_key, string prm_iv)
{
var sEncryptedString = prm_text_to_decrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
};
var key = Convert.FromBase64String(prm_key);
var IV = Convert.FromBase64String(prm_iv);
var decryptor = rj.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 (Encoding.ASCII.GetString(fromEncrypt));
}
public static void GenerateKeyIV(out string key, out string IV)
{
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
};
rj.GenerateKey();
rj.GenerateIV();
key = Convert.ToBase64String(rj.Key);
IV = Convert.ToBase64String(rj.IV);
}
}
PHP Decryption Snippet
<?php
function decryptRJ256($key,$iv,$encrypted)
{
//PHP strips "+" and replaces with " ", but we need "+" so add it back in...
$encrypted = str_replace(' ', '+', $encrypted);
//get all the bits
$key = base64_decode($key);
$iv = base64_decode($iv);
$encrypted = base64_decode($encrypted);
$rtn = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, $iv);
$rtn = unpad($rtn);
return($rtn);
}
//removes PKCS7 padding
function unpad($value)
{
$blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$packing = ord($value[strlen($value) - 1]);
if($packing && $packing < $blockSize)
{
for($P = strlen($value) - 1; $P >= strlen($value) - $packing; $P--)
{
if(ord($value{$P}) != $packing)
{
$packing = 0;
}
}
}
return substr($value, 0, strlen($value) - $packing);
}
?>
<pre>
<?php
$enc = $_COOKIE["MyCookie"];
$ky = ""; //INSERT THE KEY GENERATED BY THE C# CLASS HERE
$iv = ""; //INSERT THE IV GENERATED BY THE C# CLASS HERE
$json_user = json_decode(decryptRJ256($ky, $iv, $enc), true);
var_dump($json_user);
?>

Since the string is partially OK, but there is gibberish at the end it would suggest a padding problem within the encryption which expects exact blocks of 256 bytes. I suggest setting the padding as PKCS7 (PaddingMode.PKCS7) instead of Zeros on the C# side which PHP will understand without issues (as it's the default mode on that parser).
Edit: Oops, I did not notice that you had the following in your PHP:
$enc = $_COOKIE["MyCookie"];
This is the caveat. PHP is likely not getting the encrypted data as-is and is running some urldecode sanitizing. You should print this variable to see that it really matches what is being sent from the C# code.
Edit2:
Convert the whitespaces to missing + characters from the cookie by adding this:
str_replace(' ', '+', $enc);

Related

Replace mcrypt MCRYPT_RIJNDAEL_256 with openssl or change it to AES

I'm building a simple PHP script that need to decode input from C# application.
I've created C# app with below encrypting function (I've also included my decrypting function):
public static string Encrypt(string input, string key)
{
var aes = new RijndaelManaged
{
KeySize = 256,
BlockSize = 256,
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
Key = Encoding.UTF8.GetBytes(key)
};
aes.GenerateIV();
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] buffer;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
byte[] bytes = Encoding.UTF8.GetBytes(input);
cs.Write(bytes, 0, bytes.Length);
}
buffer = ms.ToArray();
}
buffer = buffer.Concat(aes.IV).ToArray();
return Convert.ToBase64String(buffer);
}
private static String Decrypt(string text, string key)
{
RijndaelManaged aes = new RijndaelManaged
{
KeySize = 256,
BlockSize = 256,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
Key = Encoding.UTF8.GetBytes(key)
};
byte[] encoded = Convert.FromBase64String(text);
byte[] buffer = encoded.Take(encoded.Length - aes.IV.Length).ToArray();
aes.IV = encoded.Skip(encoded.Length - aes.IV.Length).ToArray();
var decrypt = aes.CreateDecryptor();
byte[] xBuff;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
{
cs.Write(buffer, 0, buffer.Length);
}
xBuff = ms.ToArray();
}
var output = Encoding.UTF8.GetString(xBuff);
return output;
}
After couple of minutes of searching I've found simple decryption function in PHP using mcrypt:
function strippadding($string)
{
$slast = ord(substr($string, -1));
$slastc = chr($slast);
$pcheck = substr($string, -$slast);
if(preg_match("/$slastc{".$slast."}/", $string)){
$string = substr($string, 0, strlen($string)-$slast);
return $string;
} else {
return false;
}
}
function decrypt($string, $key)
{
$string = base64_decode($string);
$iv = substr($string, -32);
$string = str_replace($iv, "", $string);
return strippadding(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $string, MCRYPT_MODE_CBC, $iv));
}
This works well, but as I read on multiple sites, mcrypt is no longer recommended and sooner or later will be removed.
I'm trying to recreate same function using openssl, but without any luck.
I've tried replacing mcrypt_decrypt with:
openssl_decrypt($string, 'aes-256-cbc', $encryption_key, 0, $iv);
but as I found out MCRYPT_RIJNDAEL_256 doesn't mean AES-256.
I've been trying with different key size and block size, but without luck.
How can I recreate PHP decrypting function using openssl?
EDIT1 :
I've changed RijndaelManaged with AesCryptoServiceProvider in my C# code:
var aes = new AesCryptoServiceProvider()
{
KeySize = 256,
BlockSize = 128,
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
Key = Encoding.UTF8.GetBytes(key)
};
and inside PHP to:
define('AES_128_CBC', 'aes-128-cbc');
function decrypt_openssl($string, $pkey)
{
$key = $pkey;
$string = base64_decode($string);
$iv = substr($string, -32);
$string = str_replace($iv, "", $string);
$decrypted = openssl_decrypt($string, AES_128_CBC, base64_encode($key), 0, base64_encode($iv));
return $decrypted;
}
but still I can't get encoded string to be decoded in PHP.
I need a way to decrypt output of my C# function or change both to get that two way communication working.
EDIT2:
I'm providing full source of my C# class:
public static string EncryptRijndael(string input, string key)
{
var aes = new RijndaelManaged
{
KeySize = 256,
BlockSize = 256,
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
Key = Encoding.UTF8.GetBytes(key)
};
aes.GenerateIV();
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] buffer;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
byte[] bytes = Encoding.UTF8.GetBytes(input);
cs.Write(bytes, 0, bytes.Length);
}
buffer = ms.ToArray();
}
buffer = buffer.Concat(aes.IV).ToArray();
aes.Dispose();
return Convert.ToBase64String(buffer);
}
public static string DecryptRijndael(string input, string key)
{
var aes = new RijndaelManaged
{
KeySize = 256,
BlockSize = 256,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
Key = Encoding.UTF8.GetBytes(key)
};
byte[] xXml = Convert.FromBase64String(input);
var buffer = xXml.Take(xXml.Length - aes.IV.Length).ToArray();
var iv = xXml.Skip(xXml.Length - aes.IV.Length).ToArray();
aes.IV = iv;
var decrypt = aes.CreateDecryptor();
byte[] xBuff;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
{
cs.Write(buffer, 0, buffer.Length);
}
xBuff = ms.ToArray();
}
aes.Dispose();
String output = Encoding.UTF8.GetString(xBuff);
return output;
}
public static string EncryptAes(string input, string key)
{
var aes = new AesCryptoServiceProvider()
{
KeySize = 256,
BlockSize = 128,
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
Key = Encoding.UTF8.GetBytes(key)
};
aes.GenerateIV();
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] buffer;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
byte[] bytes = Encoding.UTF8.GetBytes(input);
cs.Write(bytes, 0, bytes.Length);
}
buffer = ms.ToArray();
}
buffer = buffer.Concat(aes.IV).ToArray();
aes.Dispose();
return Convert.ToBase64String(buffer);
}
public static String DecryptAes(string input, string key)
{
var aes = new AesCryptoServiceProvider()
{
KeySize = 256,
BlockSize = 128,
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
Key = Encoding.UTF8.GetBytes(key)
};
byte[] xXml = Convert.FromBase64String(input);
var buffer = xXml.Take(xXml.Length - aes.IV.Length).ToArray();
var iv = xXml.Skip(xXml.Length - aes.IV.Length).ToArray();
aes.IV = iv;
var decrypt = aes.CreateDecryptor();
byte[] xBuff;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
{
cs.Write(buffer, 0, buffer.Length);
}
xBuff = ms.ToArray();
}
aes.Dispose();
String output = Encoding.UTF8.GetString(xBuff);
return output;
}
My test key is: zjPUcCp9Jn7k8RtEzxTRePjn984LqwyN
My test data as plain text: zażółć geślą jaźń
My test data as base64: emHFvMOzxYLEhyBnZcWbbMSFIGphxbrFhA==
And sample output for both encryption functions:
Rijndael: 4gD/tt3I3hqYToLnwxI/HJ37EHfXrd1uxchIOjuxSuZl0Kyvxb+S6h4gG3cWKJTbj0wDSH1zvbeSvHd9Wu1VaA==
AES: B0dKdL4k9J6CeqlAekaXM+eh/zDqd5B4sKK2p6DFsgYNbV56Xdy01XvYPZX8ZXBc
IV is added at the end of byte array just before creating base64 output. When decrypting I'm reading IV from end of input string and using it to decrypt.
I need to ensure I can encrypt/decrypt utf-8 strings.
This is not an answer but added as an answer because of necessary formatting and length:
Provided by the OP:
Encryption input:
test key is: zjPUcCp9Jn7k8RtEzxTRePjn984LqwyN
test data as plain text: zażółć geślą jaźń (not used, see below)
test data as base64: emHFvMOzxYLEhyBnZcWbbMSFIGphxbrFhA== (used)
Sample output for both encryption functions:
Rijndael: 4gD/tt3I3hqYToLnwxI/HJ37EHfXrd1uxchIOjuxSuZl0Kyvxb+S6h4gG3cWKJTbj0wDSH1zvbeSvHd9Wu1VaA==
AES: B0dKdL4k9J6CeqlAekaXM+eh/zDqd5B4sKK2p6DFsgYNbV56Xdy01XvYPZX8ZXBc
Provided data displayed as hex for consistency, spaces added for legibility:
RijndaelExpected: e200ffb6 ddc8de1a 984e82e7 c3123f1c 9dfb1077 d7addd6e c5c8483a 3bb14ae6 65d0acaf c5bf92ea 1e201b77 162894db 8f4c0348 7d73bdb7 92bc777d 5aed5568
AESExpected: 07474a74 be24f49e 827aa940 7a469733 e7a1ff30 ea779078 b0a2b6a7 a0c5b206 0d6d5e7a 5ddcb4d5 7bd83d95 fc65705c
testData: 7a61c5bc c3b3c582 c4872067 65c59b6c c485206a 61c5bac5 84
testKey: 7a6a5055 63437039 4a6e376b 38527445 7a785452 65506a6e 3938344c 7177794e
testIV: 8f4c0348 7d73bdb7 92bc777d 5aed5568 (from RijndaelExpected trailing bytes).
The above is what one would hope for in a question.
Assumptions: PKCS#7 padding, CBC mode.
Based on the data 7-bytes of padding is expected so the encrypted data should be 32-bytes.
The RijndaelExpected output is 64-bytes, minus 16-bytes for the appended IV is 48-bytes which is incorrect.
The AESExpected output is 48-bytes. If the IV is appended it does not match RijndaelExpected, it the IV is not appended it is the wrong length.
My calculated AES output without an appended IV is (Note an IV generally prefixes the encrypted data):
CryptData: 1a6ec05d 00a6e61b 8196e7f2 879e2f59 25d3b7e2 c103f7e6 41c8c93f 70b32de5
Which does not agree with either expected output.
Also see this online AES calculator Note that PKCS#7 padding has been manually added.
For completness only, my test code:
NSData *testData = [[NSData alloc] initWithBase64EncodedString:#"emHFvMOzxYLEhyBnZcWbbMSFIGphxbrFhA==" options:0];
NSData *testKey = [#"zjPUcCp9Jn7k8RtEzxTRePjn984LqwyN" dataUsingEncoding:NSUTF8StringEncoding];
NSData *testIV = [[NSData alloc] initWithBase64EncodedString:#"j0wDSH1zvbeSvHd9Wu1VaA==" options:0];
size_t movedBytes = 0;
NSMutableData *cryptData = [NSMutableData dataWithLength: testData.length + kCCBlockSizeAES128];
CCCrypt(kCCEncrypt, kCCAlgorithmAES128,
kCCOptionPKCS7Padding, // CBC mode is the default
testKey.bytes, kCCKeySizeAES256,
testIV.bytes,
testData.bytes, testData.length,
cryptData.mutableBytes, cryptData.length,
&movedBytes);
cryptData.length = movedBytes;
Display(#"CryptData: %#", cryptData);

C# Rijndael decrypt no longer works after changing code to generate a random iv

This block of code used to have both the key and the iv set, taken from C# Encryption to PHP Decryption
I've attempted to change it to use a generated iv that is prepended to the resulting output. The encrypt works fine (testing decrypt is done via php and works great) however I can't get the same results with the C# decrypt function using the C# encrypted text. Can anyone point out what I'm overlooking?
The error I'm seeing is Bad PKCS7 padding. Invalid Length x where x varies.
private static readonly byte[] Key = Convert.FromBase64String("REMOVED");
private const int IvBytes = 32;
public static string Encrypt(string prm_text_to_encrypt)
{
var sToEncrypt = prm_text_to_encrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
Key = Key,
};
rj.GenerateIV ();
var encryptor = rj.CreateEncryptor();
var msEncrypt = new MemoryStream();
var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
var toEncrypt = Encoding.ASCII.GetBytes(sToEncrypt);
csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
csEncrypt.FlushFinalBlock();
var encrypted = msEncrypt.ToArray();
return (Convert.ToBase64String(rj.IV.Concat(encrypted).ToArray()));
}
public static string Decrypt(string prm_text_to_decrypt)
{
var sEncryptedString = prm_text_to_decrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
Key = Key
};
var decryptor = rj.CreateDecryptor();
var tmp = Convert.FromBase64String(sEncryptedString);
var fromEncrypt = new byte[tmp.Length];
byte[] IV = tmp.Take(IvBytes).ToArray();
byte[] sEncrypted = tmp.Skip(IvBytes).ToArray();
rj.IV = IV;
var msDecrypt = new MemoryStream(sEncrypted);
var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
return (Encoding.ASCII.GetString(fromEncrypt));
}

C# <-> PHP dynamic AES key exchange

I want to make an encrypted communication system with a dynamic key exchange between C# and PHP. At the moment I have a working encrypt/decrypt PHP <-> PHP and C# <-> C# but it's not working PHP <-> C# for some reason. The problem is that the C# code decrypted string generates a longer output than the PHP would expect, but the data is the same when viewing the output as a simple string. E.g a string "daa" sent from C# to PHP, decrypted length is 28, which is not what is supposed to be. Same goes with the string sent from PHP to C#, I get a compiler error ArgumentException: length
C# code:
public static string EncryptTest(string input)
{
string key = "256 bit key (32 char)";
input = Md5Sum(input).Substring(0, 4) + input;
var encoding = new UTF8Encoding();
var Key = encoding.GetBytes(key);
byte[] encrypted;
byte[] result;
using (var rj = new RijndaelManaged())
{
try
{
rj.Padding = PaddingMode.PKCS7;
rj.Mode = CipherMode.CBC;
rj.KeySize = 256;
rj.BlockSize = 256;
rj.Key = Key;
rj.GenerateIV();
using (ICryptoTransform encryptor = rj.CreateEncryptor())
{
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter writer = new StreamWriter(cs))
{
writer.Write(input);
}
}
encrypted = ms.ToArray();
result = new byte[rj.BlockSize / 8 + encrypted.Length];
// Result is built as: IV (plain text) + Encrypted(data)
Array.Copy(rj.IV, result, rj.BlockSize / 8);
Array.Copy(encrypted, 0, result, rj.BlockSize / 8, encrypted.Length);
}
}
}
finally
{
rj.Clear();
}
}
return Convert.ToBase64String(result);
}
public static string DecryptTest(string input)
{
string key = "256 bit key (32 char)";
byte[] data = Convert.FromBase64String(input);
if (data.Length < 32)
return null;
var encoding = new UTF8Encoding();
var Key = encoding.GetBytes(key);
using (RijndaelManaged aes = new RijndaelManaged())
{
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
aes.KeySize = 256;
aes.BlockSize = 256;
aes.Key = Key;
// Extract the IV from the data first.
byte[] iv = new byte[aes.BlockSize / 8];
Array.Copy(data, iv, iv.Length);
aes.IV = iv;
// The remainder of the data is the encrypted data we care about.
byte[] encryptedData = new byte[data.Length - iv.Length];
Array.Copy(data, iv.Length, encryptedData, 0, encryptedData.Length);
using (ICryptoTransform decryptor = aes.CreateDecryptor())
{
using (MemoryStream ms = new MemoryStream(encryptedData))
{
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
using (StreamReader reader = new StreamReader(cs))
{
string output = reader.ReadToEnd();
if (output.Length < 4)
return null;
string dataHash = output.Substring(0, 4);
string dataInput = output.Substring(4);
string dataInputHash = Md5Sum(dataInput).Substring(0, 4);
if (dataHash != dataInputHash)
return null;
return dataInput;
}
}
}
}
}
}
private static string Md5Sum(string strToEncrypt)
{
UTF8Encoding ue = new UTF8Encoding();
byte[] bytes = ue.GetBytes(strToEncrypt);
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] hashBytes = md5.ComputeHash(bytes);
string hashString = "";
for (int i = 0; i < hashBytes.Length; i++)
{
hashString += Convert.ToString(hashBytes[i], 16).PadLeft(2, '0');
}
return hashString.PadLeft(32, '0');
}
PHP code:
$key = "256 bit key (32 char)";
function iv()
{
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
return mcrypt_create_iv($iv_size, MCRYPT_RAND);
}
function encrypt($data, $key32)
{
# Prepend 4-chars data hash to the data itself for validation after decryption
$data = substr(md5($data), 0, 4).$data;
# Prepend $iv to decrypted data
$iv = iv();
$enc = $iv.mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key32, $data, MCRYPT_MODE_CBC, $iv);
return base64_encode($enc);
}
function decrypt($data, $key32)
{
$data = base64_decode($data);
if ($data === false || strlen($data) < 32)
return null;
$iv = substr($data, 0, 32);
$encrypted = substr($data, 32);
$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key32, $encrypted, MCRYPT_MODE_CBC, $iv), "\0");
if ($decrypted === false || is_null($decrypted) || strlen($decrypted) < 4)
return null;
$dataHash = substr($decrypted, 0, 4);
$data = substr($decrypted, 4);
if (substr(md5($data), 0, 4) !== $dataHash)
return null; // it breaks here, md5 sum is not correct because of the length
return $data;
}
PHP / mcrypt is not using PKCS#7 padding, it uses zero padding of 0..n bytes where n is the block size.
To PKCS#7-pad the plaintext before encryption, use:
$pad = $blockSize - (strlen($data) % $blockSize);
$pdata = $data . str_repeat(chr($pad), $pad);
to unpad the plaintext after decryption simply perform:
$pad = ord($pdata[strlen($pdata) - 1]);
$data = substr($pdata, 0, strlen($pdata) - $pad);
PKCS#7 is now the ad hoc standard for padding. Zero padding is non-deterministic; it may alter the plaintext if the plaintext ends with zero's itself.

C# Encryption to PHP Decryption

I'm trying to encrypt some (cookie) data in C# and then decrypt it in PHP. I have chosen to use Rijndael encryption. I've almost got it working, except only part of the text is decrypted! I started working from this example: Decrypt PHP encrypted string in C#
Here's the text (JSON) that I am encrypting (sensitive information removed):
{"DisplayName":"xxx", "Username": "yyy", "EmailAddress":"zzz"}
So I login to the C# app which creates/encodes the cookie from stored Key and IV and then redirects to the PHP app which is supposed to decrypt/read the cookie. When I decrypt the cookie, it comes out like this:
{"DisplayName":"xxx","F�A ;��HP=D�������4��z����ť���k�#E���R�j�5�\�t. t�D��"
UPDATE: i've gotten a little bit further and this is now the result
string(96) "{"DisplayName":"xxx","Username":"yyy","EmailAddress"�)ق��-�J��k/VV-v� �9�B`7^"
As you can see, it starts decrypting it, but then gets messed up...
When Decrypt the string it comes out correct (with padding, which I have a function to remove padding), but if I change the test string by one character I get garbage again:
B�nHL�Ek �¿?�UΣlO����OЏ�M��NO/�f.M���Lƾ�CC�Y>F��~�qd�+
Here's the c# code I use to generate the random Key and IV:
UPDATE: I'm just using static key/IV for now, here they are:
Key: lkirwf897+22#bbtrm8814z5qq=498j5
IV: 741952hheeyy66#cs!9hjv887mxx7#8y
RijndaelManaged symmetricKey = new RijndaelManaged();
symmetricKey.BlockSize = 256;
symmetricKey.KeySize = 256;
symmetricKey.Padding = PaddingMode.Zeros;
symmetricKey.Mode = CipherMode.CBC;
string key = Convert.ToBase64String(symmetricKey.Key);
string IV = Convert.ToBase64String(symmetricKey.IV);
I then save the key and IV to a database to be retrieved later for encoding/decoding.
This is the full encryption class:
public static class Encryption
{
public static string Encrypt(string prm_text_to_encrypt, string prm_key, string prm_iv)
{
var sToEncrypt = prm_text_to_encrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
//FeedbackSize = 256
};
var key = Encoding.ASCII.GetBytes(prm_key);
var IV = Encoding.ASCII.GetBytes(prm_iv);
//var key = Convert.FromBase64String(prm_key);
//var IV = Convert.FromBase64String(prm_iv);
var encryptor = rj.CreateEncryptor(key, IV);
var msEncrypt = new MemoryStream();
var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
var toEncrypt = Encoding.ASCII.GetBytes(sToEncrypt);
csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
csEncrypt.FlushFinalBlock();
var encrypted = msEncrypt.ToArray();
return (Convert.ToBase64String(encrypted));
}
public static string Decrypt(string prm_text_to_decrypt, string prm_key, string prm_iv)
{
var sEncryptedString = prm_text_to_decrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
//FeedbackSize = 256
};
var key = Encoding.ASCII.GetBytes(prm_key);
var IV = Encoding.ASCII.GetBytes(prm_iv);
//var key = Convert.FromBase64String(prm_key);
//var IV = Convert.FromBase64String(prm_iv);
var decryptor = rj.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 (Encoding.ASCII.GetString(fromEncrypt));
}
public static void GenerateKeyIV(out string key, out string IV)
{
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
//FeedbackSize = 256
};
rj.GenerateKey();
rj.GenerateIV();
key = Convert.ToBase64String(rj.Key);
IV = Convert.ToBase64String(rj.IV);
}
}
Here's the PHP code I am using to decrypt the data:
function decryptRJ256($key,$iv,$string_to_decrypt)
{
$string_to_decrypt = base64_decode($string_to_decrypt);
$rtn = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $string_to_decrypt, MCRYPT_MODE_CBC, $iv);
//$rtn = rtrim($rtn, "\0\4");
$rtn = unpad($rtn);
return($rtn);
}
function unpad($value)
{
$blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
//apply pkcs7 padding removal
$packing = ord($value[strlen($value) - 1]);
if($packing && $packing < $blockSize){
for($P = strlen($value) - 1; $P >= strlen($value) - $packing; $P--){
if(ord($value{$P}) != $packing){
$packing = 0;
}//end if
}//end for
}//end if
return substr($value, 0, strlen($value) - $packing);
}
$ky = 'lkirwf897+22#bbtrm8814z5qq=498j5'; // 32 * 8 = 256 bit key
$iv = '741952hheeyy66#cs!9hjv887mxx7#8y'; // 32 * 8 = 256 bit iv
$enc = $_COOKIE["MyCookie"];
$dtext = decryptRJ256($ky, $iv, $enc);
var_dump($dtext);
I am a little unsure about this part, because all of the example code I've seen simply passes in the base64 encoded string directly to the decryptor, but in my example, I have to base64_decode it before I pass it otherwise I get the error that the key and IV are not the correct length.
UPDATE: I'm using ASCII keys in the format needed by PHP. If I generate keys from the RijndaelManaged class they dont work on the PHP side, but I can use keys that are known to work on PHP side and use them in the RijndaelManaged C# side.
Please let me know if I left out any pertinent information. TIA!
For posterity I'm placing the fully completed solution here.
C# Encryption Class
public static class Encryption
{
public static string Encrypt(string prm_text_to_encrypt, string prm_key, string prm_iv)
{
var sToEncrypt = prm_text_to_encrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
};
var key = Convert.FromBase64String(prm_key);
var IV = Convert.FromBase64String(prm_iv);
var encryptor = rj.CreateEncryptor(key, IV);
var msEncrypt = new MemoryStream();
var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
var toEncrypt = Encoding.ASCII.GetBytes(sToEncrypt);
csEncrypt.Write(toEncrypt, 0, toEncrypt.Length);
csEncrypt.FlushFinalBlock();
var encrypted = msEncrypt.ToArray();
return (Convert.ToBase64String(encrypted));
}
public static string Decrypt(string prm_text_to_decrypt, string prm_key, string prm_iv)
{
var sEncryptedString = prm_text_to_decrypt;
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
};
var key = Convert.FromBase64String(prm_key);
var IV = Convert.FromBase64String(prm_iv);
var decryptor = rj.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 (Encoding.ASCII.GetString(fromEncrypt));
}
public static void GenerateKeyIV(out string key, out string IV)
{
var rj = new RijndaelManaged()
{
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 256,
BlockSize = 256,
};
rj.GenerateKey();
rj.GenerateIV();
key = Convert.ToBase64String(rj.Key);
IV = Convert.ToBase64String(rj.IV);
}
}
PHP Decryption Snippet
<?php
function decryptRJ256($key,$iv,$encrypted)
{
//PHP strips "+" and replaces with " ", but we need "+" so add it back in...
$encrypted = str_replace(' ', '+', $encrypted);
//get all the bits
$key = base64_decode($key);
$iv = base64_decode($iv);
$encrypted = base64_decode($encrypted);
$rtn = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, $iv);
$rtn = unpad($rtn);
return($rtn);
}
//removes PKCS7 padding
function unpad($value)
{
$blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$packing = ord($value[strlen($value) - 1]);
if($packing && $packing < $blockSize)
{
for($P = strlen($value) - 1; $P >= strlen($value) - $packing; $P--)
{
if(ord($value{$P}) != $packing)
{
$packing = 0;
}
}
}
return substr($value, 0, strlen($value) - $packing);
}
?>
<pre>
<?php
$enc = $_COOKIE["MyCookie"];
$ky = ""; //INSERT THE KEY GENERATED BY THE C# CLASS HERE
$iv = ""; //INSERT THE IV GENERATED BY THE C# CLASS HERE
$json_user = json_decode(decryptRJ256($ky, $iv, $enc), true);
var_dump($json_user);
?>
Since the string is partially OK, but there is gibberish at the end it would suggest a padding problem within the encryption which expects exact blocks of 256 bytes. I suggest setting the padding as PKCS7 (PaddingMode.PKCS7) instead of Zeros on the C# side which PHP will understand without issues (as it's the default mode on that parser).
Edit: Oops, I did not notice that you had the following in your PHP:
$enc = $_COOKIE["MyCookie"];
This is the caveat. PHP is likely not getting the encrypted data as-is and is running some urldecode sanitizing. You should print this variable to see that it really matches what is being sent from the C# code.
Edit2:
Convert the whitespaces to missing + characters from the cookie by adding this:
str_replace(' ', '+', $enc);

Converting AES encryption token code in C# to php

I have the following .Net code which takes two inputs. 1) A 128 bit base 64 encoded key and 2) the userid. It outputs the AES encrypted token.
I need the php equivalent of the same code, but dont know which corresponding php classes are to be used for RNGCryptoServiceProvider,RijndaelManaged,ICryptoTransform,MemoryStream and CryptoStream.
Im stuck so any help regarding this would be really appreciated.
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;
class AESToken
{
[STAThread]
static int Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Usage: AESToken key userId\n");
Console.WriteLine("key Specifies 128-bit AES key base64 encoded supplied by MediaNet to the partner");
Console.WriteLine("userId specifies the unique id");
return -1;
}
string key = args[0];
string userId = args[1];
StringBuilder sb = new StringBuilder();
// This example code uses the magic string “CAMB2B”. The implementer
// must use the appropriate magic string for the web services API.
sb.Append("CAMB2B");
sb.Append(args[1]); // userId
sb.Append('|'); // pipe char
sb.Append(System.DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ssUTC")); //timestamp
Byte[] payload = Encoding.ASCII.GetBytes(sb.ToString());
byte[] salt = new Byte[16]; // 16 bytes of random salt
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(salt); // the plaintext is 16 bytes of salt followed by the payload.
byte[] plaintext = new byte[salt.Length + payload.Length];
salt.CopyTo(plaintext, 0);
payload.CopyTo(plaintext, salt.Length);
// the AES cryptor: 128-bit key, 128-bit block size, CBC mode
RijndaelManaged cryptor = new RijndaelManaged();
cryptor.KeySize = 128;
cryptor.BlockSize = 128;
cryptor.Mode = CipherMode.CBC;
cryptor.GenerateIV();
cryptor.Key = Convert.FromBase64String(args[0]); // the key
byte[] iv = cryptor.IV; // the IV.
// do the encryption
ICryptoTransform encryptor = cryptor.CreateEncryptor(cryptor.Key, iv);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write);
cs.Write(plaintext, 0, plaintext.Length);
cs.FlushFinalBlock();
byte[] ciphertext = ms.ToArray();
ms.Close();
cs.Close();
// build the token
byte[] tokenBytes = new byte[iv.Length + ciphertext.Length];
iv.CopyTo(tokenBytes, 0);
ciphertext.CopyTo(tokenBytes, iv.Length);
string token = Convert.ToBase64String(tokenBytes);
Console.WriteLine(token);
return 0;
}
}
Please help.
Thank You.
We are also trying figure out the same C# in PHP. You can post your code without the key.
First approach:
// Open the cipher:
// Using Rijndael 128 in CBC mode.
$m = mcrypt_module_open('rijndael-128', '', 'cbc', '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($m), MCRYPT_RAND);
// Initialize the encryption:
mcrypt_generic_init($m, (base64_decode($key_)), $iv);
// Encrypt the data:
$cryptext = mcrypt_generic($m, $plain_text);
//echo "IV SIZE ".mcrypt_enc_get_iv_size($m);
$tx2 = base64_encode($iv.$cipherText);
// Close the encryption handler:
mcrypt_generic_deinit($m);
// Close the cipher:
mcrypt_module_close($m);
Second approach for initialization:
$m = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$iv_size = mcrypt_enc_get_iv_size($m);
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($m), MCRYPT_RAND);
$key128 = base64_decode($key_);
// Encrypt the data:
$cryptext = mcrypt_generic($m, $plain_text);
$tx2 = base64_encode($iv.$cipherText);
// Close the encryption handler:
mcrypt_generic_deinit($m);
You would use the mcrypt library in PHP to implement the same functionality.
You can see the following code that works:
<?php
class UserData
{
public $email;
public $name;
public $expires;
}
class Application
{
private $api_key = "<private_key>";
private $app_key = "appkey";
public function run()
{
$user = new UserData();
$date = new DateTime(null, new DateTimeZone('UTC'));
$date->modify('+5 minute');
$user->expires = $date->format('c');
$user->email = "testing#domain.com";
$user->name = "PHP5 Example";
$encrypted_data = $this->encryptUserData($user);
// Example login URL
printf("http://<domain>/multipass?sso=%s", $encrypted_data);
}
private function encryptUserData($user_data)
{
$app_key = $this->app_key;
$api_key = $this->api_key;
$json = json_encode($user_data);
$salted = $api_key . $app_key;
$saltedHash = substr(sha1($salted, true), 0, 16);
$pad = 16 - (strlen($json) % 16);
$data = $json . (str_repeat(chr($pad), $pad));
if (!function_exists('mcrypt_encrypt'))
throw new Exception('Mcrypt extension is not installed for PHP.');
$aes = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $saltedHash, $data, MCRYPT_MODE_CBC, str_repeat("\0", 16));
$b64token = base64_encode($aes);
$b64token = rtrim(str_replace(array('+', '/'), array('-', '_'), $b64token), '=');
return $b64token;
}
}
$app = new Application();
$app->run();
?>
I hope it will be helpful for you. Thanks.

Categories

Resources