At the moment I'm stucked on the symmetrical encryption between PHP and C#, no matter how I rewrite my script I always get either an error message or the encrypted text even more encrypted. I have been trying allmost every suggestion that is offered on the internet for 3 days without success, I hope someone can help me to finish the encryption and decryption process. You can find examples of my scripts below.
This is how I build and send the message containing the Key, IV and Encrypted text:
function alphaNumeric () : string {
$number = rand(32, 127);
return $number >= 48 && $number <= 57
|| $number >= 65 && $number <= 90
|| $number >= 97 && $number <= 122
? chr($number)
: alphaNumeric();
}
function randomBytes (int $length, string $byteString = '') : string {
return $length > 0
? randomBytes($length - 1, $byteString.alphaNumeric())
: $byteString;
}
$key = randomBytes(16);
$iv = randomBytes(16);
$data = 'This text should be encrypted in PHP and decrypted in C#!';
$encrypted = openssl_encrypt($data, 'aes-128-cbc', $key, 1, $iv);
$message = $key.$iv.$encrypted;
file_put_contents('message.txt', $message);
echo $message;
die;
this is what I send from PHP and what I receive again in C#:
UeWeXUAnu98RKTkMiBGLWpMNy4CRKJErOqTTUfJWrtXziFTELGG+647lw/XT846dj8tlNMITLVBg2cKS3dFINeKot4zlb+gVpfq4oIb/M3a8n3a9XWaeIOrHpNedZmMrYiZoCQ==
UeWeXUAnu98RKTkMiBGLWpMNy4CRKJErOqTTUfJWrtXziFTELGG+647lw/XT846dj8tlNMITLVBg2cKS3dFINeKot4zlb+gVpfq4oIb/M3a8n3a9XWaeIOrHpNedZmMrYiZoCQ==
and at the end this is the c# code which should decrypt the message:
public static void Main()
{
var client = new HttpClient();
var requestUri = "http://localhost/message.php";
while (Console.ReadLine() == string.Empty)
{
var response = client.GetAsync(requestUri).Result;
if (!response.IsSuccessStatusCode)
{
continue;
}
var content = response.Content.ReadAsStringAsync().Result;
if (string.IsNullOrWhiteSpace(content) || content.Length < 48)
{
continue;
}
File.WriteAllText("../../../message.txt", content);
var keyString = content.Substring(0, 16);
var keyBytes = Encoding.UTF8.GetBytes(keyString);
var ivString = content.Substring(16, 16);
var ivBytes = Encoding.UTF8.GetBytes(ivString);
var encString = content.Substring(32);
var encBytes = Encoding.UTF8.GetBytes(encString);
Console.WriteLine($"{keyBytes.Length}: {keyString}");
Console.WriteLine($"{ivBytes.Length}: {ivString}");
Console.WriteLine($"{encBytes.Length}: {encString}");
try
{
var plainText = Decrypt(encBytes, keyBytes, ivBytes);
Console.WriteLine(plainText);
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
static string Decrypt(byte[] encrypted, byte[] key, byte[] iv)
{
using var alg = AesCryptoServiceProvider.Create();
//alg.IV = iv;
//alg.Key = key;
//alg.KeySize = 128;
//alg.BlockSize = 256;
//alg.Mode = CipherMode.CBC;
alg.Padding = PaddingMode.PKCS7;
var decryptor = alg.CreateDecryptor(key, iv);
using var ms = new MemoryStream(encrypted);
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var sr = new StreamReader(cs);
return sr.ReadToEnd();
}
this is the message I'm currently getting:
Thanks in advance.
There are the following issues in the C# code:
In the PHP code a 32 bytes key is generated, but because of the specified AES-128 (aes-128-cbc), only the first 16 bytes are taken into account. Accordingly, in the C# code only the first 16 bytes of the key may be considered and not the full 32 bytes (see first comment).
In the PHP code openssl_encrypt returns the ciphertext Base64 encoded by default, so this part of the ciphertext must be Base64 decoded in the C# code and not UTF8 encoded (see second comment).
AesCryptoServiceProvider uses CBC mode and PKCS7 padding by default, so both do not need to be explicitly specified in the C# code.
The following C# code decrypts the ciphertext encrypted with the PHP code:
string content = "UeWeXUAnu98RKTkMiBGLWpMNy4CRKJErOqTTUfJWrtXziFTELGG+647lw/XT846dj8tlNMITLVBg2cKS3dFINeKot4zlb+gVpfq4oIb/M3a8n3a9XWaeIOrHpNedZmMrYiZoCQ==";
var keyString = content.Substring(0, 16);
var keyBytes = Encoding.UTF8.GetBytes(keyString);
var ivString = content.Substring(32, 16);
var ivBytes = Encoding.UTF8.GetBytes(ivString);
var encString = content.Substring(48);
var encBytes = Convert.FromBase64String(encString);
using var alg = AesCryptoServiceProvider.Create();
alg.IV = ivBytes;
alg.Key = keyBytes;
var decryptor = alg.CreateDecryptor(keyBytes, ivBytes);
using var ms = new MemoryStream(encBytes);
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var sr = new StreamReader(cs);
string decrypted = sr.ReadToEnd();
Console.WriteLine(decrypted);
Please consider with regard to the PHP-Code, that it is inconsistent when a 32 bytes key is generated for AES-128. Instead, a 16 bytes key should be generated. Alternatively, you can switch to AES-256 (aes-256-cbc). And also keep in mind the hint in the first comment: A key must generally not be sent with the ciphertext, because any attacker could easily decrypt the data.
Related
I got stuck also a bit lost in C# and CryptoJS differences.i have two server side
functions to encrypt data by AES256 bit as follows;
public string EncryptText_PBKDF2(string input, string password)
{
// Get the bytes of the strings
byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
// Hash the password with SHA256 for AES key
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
byte[] bytesEncrypted = AES_Encrypt_PBKDF2(bytesToBeEncrypted, passwordBytes);
string result = Convert.ToBase64String(bytesEncrypted);
return result;
}
private byte[] AES_Encrypt_PBKDF2(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
byte[] saltBytes = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
using (MemoryStream ms = new MemoryStream())
{
aes.KeySize = 256;
aes.BlockSize = 128;
aes.IV = MD5.Create().ComputeHash(passwordBytes);//16 byte
var PBKDF2_key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
aes.Key = PBKDF2_key.GetBytes(aes.KeySize / 8);//32 byte
aes.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, aes.CreateEncryptor(aes.Key, aes.IV), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
}
return encryptedBytes;
}
then i send it to view by a controller action;
string res = aesIsl.EncryptText_PBKDF2("my text", "my key");
ViewBag.Crypted1 = res;
on the client side i get it from a hidden field
<div id="crypted"><input id="hidden1" data-enc="#ViewBag.Crypted1" type="hidden" /></div>
and decrypt it by crypto-js library(i have 4 attempts indicated on top of them)
here is my decrypting function and attempts:
function AesleDesifrele_PBKDF2(ciphertext, pwd) {
var salt = "0000000000000000";
var iv = CryptoJS.MD5(CryptoJS.SHA256(CryptoJS.enc.Utf8.parse(pwd)));
//these are my attempts to decrypt
//ATTEMPT #1:
//returning object contains a word array but converting it to utf8 string
//causes "Malformed UTF-8 data" error
console.log(CryptoJS.AES.decrypt(ciphertext, pwd).toString(CryptoJS.enc.Utf8));
//ATTEMPT #2
var key = CryptoJS.PBKDF2(
CryptoJS.enc.Utf8.parse(pwd).toString(CryptoJS.enc.Utf8),
CryptoJS.enc.Hex.parse(salt),
{ keySize: 128 / 32, iterations: 1000 }
);
var decrypted = CryptoJS.AES.decrypt(
ciphertext,
key,
{ iv: iv }
);
//returning object contains a word array but converting it to utf8 string
//causes "Malformed UTF-8 data" error
console.log(decrypted.toString(CryptoJS.enc.Utf8));
//ATTEMPT #3
var raw = base64UrlDecode(ciphertext);
var iv1 = CryptoJS.lib.WordArray.create(CryptoJS.MD5(CryptoJS.SHA256(CryptoJS.enc.Utf8.parse(pwd))));;
var salt1 = CryptoJS.lib.WordArray.create(CryptoJS.enc.Hex.parse(salt));
var key1 = generateKey(pwd, salt1);
var ciphertextWords = CryptoJS.lib.WordArray.create(raw.words.slice(128 / 32 + 128 / 32));
var cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: ciphertextWords });
var plaintextArray = CryptoJS.AES.decrypt(
cipherParams,
key1,
{ iv: iv1 }
);
console.log(CryptoJS.enc.Utf8.stringify(plaintextArray));
//returning object contains a word array but converting it to utf8 string
//returns nothing
//ATTEMPT #4
var iv2 = CryptoJS.MD5(CryptoJS.SHA256(CryptoJS.enc.Utf8.parse(pwd)));
var Pass = CryptoJS.enc.Utf8.parse(pwd);
var Salt = CryptoJS.enc.Utf8.parse(salt);
var key256Bits1000Iterations = CryptoJS.PBKDF2(Pass.toString(CryptoJS.enc.Utf8), Salt, { keySize: 256 / 32, iterations: 1000 });
var cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(ciphertext)
});
var decrypted = CryptoJS.AES.decrypt(cipherParams, key256Bits1000Iterations, { mode: CryptoJS.mode.CBC, iv: iv2, padding: CryptoJS.pad.Pkcs7 });
console.log(decrypted.toString(CryptoJS.enc.Utf8));
//returning object contains a word array but converting it to utf8 string
//returns nothing
}
this is the function to generate key for CryptoJs decrypt
function generateKey(secret, salt) {
return CryptoJS.PBKDF2(
secret,
salt,
{
keySize: this.keySize / 32, // size in Words
iterations: 1000,
hasher: CryptoJS.algo.SHA256
}
);
}
this is the conversion function i use
function base64UrlDecode(str) {
return CryptoJS.enc.Base64.parse(str.replace(/\-/g, '+').replace(/\_/g, '/'));
}
even though i control params on the client(cyphertext,password,IV and the salt) are exactly the same with the server,CryptoJS.decrypt returns nothing without any error.i guess i miss converting something to another in order C# to be compatible with CryptoJs.
any advice welcomes.
thanks in advance.
I am trying to solve an encryption issue I am having between php and c#.
I have encrypted data using the following php and openssl operation.
$encrypt_method = "AES-256-CBC";
$secret_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
$secret_iv = 'XXXXXXXXXXXXXXXX';
$key = hash ('sha256', $secret_key);
$iv = substr (hash ('sha256', $secret_iv), 0, 16);
$output = openssl_encrypt ($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode ($output);
I have tried a couple of methods in C# to decrypt but this is what I am trying now.
public string Encrypt_Decrypt(string action, string value) {
string secretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string secretIV = "XXXXXXXXXXXXXXXX";
string key = Hash(secretKey);
string iv = Hash(secretIV).Substring(0,16);
string retValue = "";
if (action == "encrypt") {
retValue = EncryptString(value, key, iv);
}
else if (action == "decrypt") {
retValue = DecryptString(value, key, iv);
}
}
// Hash to match php hash function
public static string Hash(string unhashedString) {
return BitConverter.ToString(new SHA256CryptoServiceProvider().ComputeHash(Encoding.Default.GetBytes(unhashedString))).Replace("-", String.Empty).ToLower();
}
public static string DecryptString(string cipherData, string keyString, string ivString) {
byte[] key = Encoding.UTF8.GetBytes(keyString);
Console.WriteLine(key.Length);
byte[] iv = Encoding.UTF8.GetBytes(ivString);
Console.WriteLine(iv.Length);
byte[] cipherCrypt = Convert.FromBase64String(cipherData);
for (int i = 0; i < cipherCrypt.Length; i++) {
Console.Write(cipherCrypt[i] + " ");
}
try {
RijndaelManaged crypto = new RijndaelManaged();
crypto.Key = key;
crypto.IV = iv;
crypto.Mode = CipherMode.CBC;
crypto.KeySize = 256;
crypto.BlockSize = 128;
crypto.Padding = PaddingMode.None;
ICryptoTransform decryptor = crypto.CreateDecryptor(crypto.Key, crypto.IV);
using (MemoryStream memStream = new MemoryStream(cipherCrypt)) {
using (CryptoStream cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Read)) {
using (StreamReader streamReader = new StreamReader(cryptoStream)) {
return streamReader.ReadToEnd();
}
}
}
}
catch (CryptographicException e) {
Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);
return null;
}
}
I have tried a couple different encoding types when getting the byte[] for the operation.
I keep getting the following error:
Specified key is not a valid size for this algorithm.
Not sure what I am missing. Any help is appreciated.
Also, I already read through this and tried what the solution suggestion recommended. I got the same resulting error.
UPDATE - 01
I have updated the code here to reflect the code I have changed.
The key length is 32,
The iv length is 16,
The data coming in at "cipherData" is length 32,
When "cipherData" goes through "FromBase64String(cipherData)" it comes out as a 24 byte array. This is causing an issue for the decryptor which wants a 32 byte array.
There are obviously problems with the key size. The code between PHP and C# seem to match. The problem seems to be that the code is wrong in both cases.
Let's see how long the key actually is:
Start with a 32 byte key (non-encoded).
Hash the key with SHA-256: 32 bytes (non-encoded).
Encode to hex (integrated into PHP's hash() function by default): 64 bytes.
AES only supports the following key sizes: 16, 24 and 32 bytes. openssl_encrypt() will only use the first 32 bytes of the hex key silently. So, you need to use the first 32 bytes in C#.
Note that openssl_encrypt() takes an options argument which denotes that the output is Base64 when OPENSSL_RAW_DATA is not set. It means that the PHP output was encoded twice with Base64. So you need to decode it twice in C#.
<Original Code From Here>
I came across this thread while googling. However after trying it out and making adjustments, I've come across a hurdle, hopefully someone can help me out a bit.
The code above is fine but since the algorithm isn't really useful without making the IV change everytime, I tried using following code to generate iv but it kept saying "Specified key is not a valid size for this algorithm." in my C# debugger.
I also tried outputting IV from the C# code, after decoding base 64 string the string length varies from 30 31 2X ==> basically just fluctuates for some reason.
I also would like to change the KEY as well but couldn't due to similar reasons as the IV issue, so hopefully someone can help me out with that?
(I've tried the following from http://php.net/manual/en/function.mcrypt-encrypt.php, couldn't get it to work in harmony with C#, maybe once I fix the IV issue I'll be able to fix this as well?
$key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3"); )
PHP========================
<?php
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
//$iv = "45287112549354892144548565456541";
$key = "anjueolkdiwpoida";
$text = "This is my encrypted message";
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC, $iv);
$crypttext = urlencode($crypttext);
$crypttext64=base64_encode($crypttext);
print($crypttext64) . "\n<br/>";
print(base64encode($iv)) . "\n<br/>";
?>
C#========================
string iv = Encoding.UTF8.GetString(Convert.FromBase64String("SOME IV STRING I COPY FROM BROSWER WITH ABOVE PHP LOADED"));
string kyy = "anjueolkdiwpoida";
//ciphertext is also SOME TXT STRING I COPIED FROM BROWSER WITH ABOVE PHP LOADED
string plainText = ValidationControls.DecryptRJ256(cipherText, kyy, iv);
public byte[] Decode(string str)
{
var decbuff = Convert.FromBase64String(str);
return decbuff;
}
static public String DecryptRJ256(byte[] cypher, string KeyString, string IVString)
{
var sRet = "";
var encoding = new UTF8Encoding();
var Key = encoding.GetBytes(KeyString);
var IV = encoding.GetBytes(IVString);
using (var rj = new RijndaelManaged())
{
try
{
rj.Padding = PaddingMode.PKCS7;
rj.Mode = CipherMode.CBC;
rj.KeySize = 256;
rj.BlockSize = 256;
rj.Key = Key;
rj.IV = IV;
var ms = new MemoryStream(cypher);
using (var cs = new CryptoStream(ms, rj.CreateDecryptor(Key, IV), CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
sRet = sr.ReadLine();
}
}
}
catch (Exception exc) { Console.WriteLine(exc.Message); App.Current.Shutdown(); }
finally
{
rj.Clear();
}
}
return sRet;
}
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.
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);
...
}
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.
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.