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.
Related
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.
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);
I have a problem. I have a method to encrypt a password in php and in C# but i cannot get the same results with both algorithms. Someone can help me?
PhP
<?php
$password = 'MySecretPass';
$secret = '65rgt85k89xrDAr3';
$iv = 'AAAAAAAAAAAAAAAA';
$td = mcrypt_module_open('rijndael-128', '', 'cfb','');
mcrypt_generic_init($td, $secret, $iv);
$password = mcrypt_generic($td, $password);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$password=base64_encode($password);
echo $password;
?>
C#
var password = padString("MySecretPass");
txtEncrypt.Text = Convert.ToBase64String(EncryptStringToBytes(password,
Encoding.UTF8.GetBytes("65rgt85k89xrDAr3"),
Encoding.UTF8.GetBytes("AAAAAAAAAAAAAAAA"), PaddingMode.None));
txtEncrypt.Text = txtEncrypt.Text;
static byte[] EncryptStringToBytes(string plainText, byte[] key, byte[] iv, PaddingMode mode)
{
byte[] encrypted;
using (var rijAlg = new RijndaelManaged { Mode = CipherMode.CFB, BlockSize = 128, Padding = mode })
{
rijAlg.Key = key;
rijAlg.IV = iv;
var encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
return encrypted;
}
private static String padString(String source)
{
char paddingChar = ' ';
int size = 16;
int x = source.Length % size;
int padLength = size - x;
for (int i = 0; i < padLength; i++)
{
source += paddingChar;
}
return source;
}
The results for PhP is "/KNlzi/fZOERWL79", but for c# is /J643dvAR4/Gh0aYHdshNw==. I don't know why my results are different. In addition, I have wrote the code in Java and I get the same result than in C#.
Thanks in advance.
It's probably that Encoding.GetBytes(string) returns "Unicode encoding". That's an encoding that actually does not exist, but it returns UTF-16. On the other hand you treat your key and IV as ASCII characters.
So whichever encoding PHP is using at the time, there will be a mismatch. You should explicitly define which encoding should be used when converting textual strings to bytes, both in the PHP code as in your C# code.
I am attempting to integrate Desk.com's Multipass SSO into my website, and am having trouble generating the correct HMAC-SHA1 signature (so say the error logs). Here are the directions from Desk.com's website:
Build a SHA1 HMAC using your multipass API key and your finished multipass token.
Base64 encode the resulting HMAC.
According to the logs, my multipass token appears to be correct. First, the code in PHP that works perfectly:
// Build an HMAC-SHA1 signature using the multipass string and your API key
$signature = hash_hmac("sha1", $multipass, $api_key, true);
// Base64 encode the signature
$signature = base64_encode($signature);
^ note that hash_hmac's 'true' value is outputting information in raw binary - I'm not sure if this is the case in my C# code
Next, my C# code that is not working correctly:
protected string getSignature(string multipass)
{
string api_key = "my_key_goes_here";
HMACSHA1 hmac = new HMACSHA1(Encoding.ASCII.GetBytes(api_key));
hmac.Initialize();
byte[] buffer = Encoding.ASCII.GetBytes(multipass);
string signature = BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();
return Convert.ToBase64String(Encoding.ASCII.GetBytes(signature));
}
This is the result of (literally) hours of searching and trying multiple different ways. I would be very grateful if I could get this figured out.
If you need a reference, check out this page by Desk.com: http://dev.desk.com/docs/portal/multipass. It has code examples and outlines the instructions for completing the code.
Edit: here is my multipass generation code.
protected string getMultipass(UserData user_data)
{
// Encode the data into a JSON object
JavaScriptSerializer s = new JavaScriptSerializer();
string json_data = s.Serialize(user_data);
// Acquire the Web.config appSettings
string site_key = "my_site_here";
string api_key = "my_key_here";
string iv = "OpenSSL for Ruby";
// Using byte arrays now instead of strings
byte[] encrypted = null;
byte[] bIV = Encoding.ASCII.GetBytes(iv);
byte[] data = Encoding.ASCII.GetBytes(json_data);
// XOR the first block (16 bytes)
// once before the full XOR
// so it gets double XORed
for (var i = 0; i < 16; i++)
data[i] = (byte)(data[i] ^ bIV[i]);
// Pad using block size of 16 bytes
int pad = 16 - (data.Length % 16);
Array.Resize(ref data, data.Length + pad);
for (var i = 0; i < pad; i++)
data[data.Length - pad + i] = (byte)pad;
// Use the AesManaged object to do the encryption
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.IV = bIV;
aesAlg.KeySize = 128;
// Create the 16-byte salted hash
SHA1 sha1 = SHA1.Create();
byte[] saltedHash = sha1.ComputeHash(Encoding.UTF8.GetBytes(api_key + site_key), 0, (api_key + site_key).Length);
Array.Resize(ref saltedHash, 16);
aesAlg.Key = saltedHash;
// Encrypt using the AES managed object
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(data, 0, data.Length);
csEncrypt.FlushFinalBlock();
}
encrypted = msEncrypt.ToArray();
}
}
// Return the Base64-encoded encrypted data
return Convert.ToBase64String(encrypted, Base64FormattingOptions.None)
.TrimEnd("=".ToCharArray()) // Remove trailing "=" characters
.Replace("+", "-") // Change "+" to "-"
.Replace("/", "_"); // Change "/" to "_"
}
You can see the following code that works:
static string create(string userDetails) {
string accountKey = "YOUR_ACCOUNT_KEY";
string apiKey = "YOUR_API_KEY";
string initVector = "OpenSSL for Ruby"; // DO NOT CHANGE
byte[] initVectorBytes = Encoding.UTF8.GetBytes(initVector);
byte[] keyBytesLong;
using( SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider() ) {
keyBytesLong = sha.ComputeHash( Encoding.UTF8.GetBytes( apiKey + accountKey ) );
}
byte[] keyBytes = new byte[16];
Array.Copy(keyBytesLong, keyBytes, 16);
byte[] textBytes = Encoding.UTF8.GetBytes(userDetails);
for (int i = 0; i < 16; i++) {
textBytes[i] ^= initVectorBytes[i];
}
// Encrypt the string to an array of bytes
byte[] encrypted = encryptStringToBytes_AES(textBytes, keyBytes, initVectorBytes);
string encoded = Convert.ToBase64String(encrypted);
return HttpUtility.UrlEncode(encoded);
}
static byte[] encryptStringToBytes_AES(byte[] textBytes, byte[] Key, byte[] IV) {
// Declare the stream used to encrypt to an in memory
// array of bytes and the RijndaelManaged object
// used to encrypt the data.
using( MemoryStream msEncrypt = new MemoryStream() )
using( RijndaelManaged aesAlg = new RijndaelManaged() )
{
// Provide the RijndaelManaged object with the specified key and IV.
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.KeySize = 128;
aesAlg.BlockSize = 128;
aesAlg.Key = Key;
aesAlg.IV = IV;
// Create an encrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor();
// Create the streams used for encryption.
using( CryptoStream csEncrypt = new CryptoStream( msEncrypt, encryptor, CryptoStreamMode.Write ) ) {
csEncrypt.Write( textBytes, 0, textBytes.Length );
csEncrypt.FlushFinalBlock();
}
byte[] encrypted = msEncrypt.ToArray();
// Return the encrypted bytes from the memory stream.
return encrypted;
}
}
I hope it works for you.
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);