Reputation: 403
I try to encrypt a string (json) with Rijndael in C# and come up with a string, which I can offer to a PHP web service. This web service in turn decodes the string using the IV and masterkey (known to them). I have to write the C# code that can talk to the PHP service, I do not control/own the PHP service.
The PHP code for encrypting is as follows:
function encrypt($plaintext) {
$masterkey = 'masterKeyOfLength29Characters';
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $masterkey, $iv);
$crypttext = mcrypt_generic($td, $plaintext);
mcrypt_generic_deinit($td);
return base64_encode($iv.$crypttext);
}
$param = array("key" => "value");
$encryptedString = rawurlencode(encrypt(json_encode($param)))
The code above I'll have to convert to C#, so I can encrypt my JSON and offer it to the PHP web service.
There are two problems. The first was with the masterkey length, the second (might be related) is with the rawurlencode of the encrypted data (hard for me to test at this point).
var masterkey = "masterKeyOfLength29Characters";
var data = EncryptData(json, masterkey);
// Some code to URL Encode the data, I haven't gotten as far to test this
// since I can't encrypt with the key used in PHP, so I can't call the service
// to test the encoded string from my C# code.
data = HttpUtility.UrlEncode(data);
data = data.Replace("+", "%20");
public static string EncryptData(string json, string encryptionKey) {
Rijndael rj = Rijndael.Create();
rj.Mode = CipherMode.CBC;
rj.Padding = PaddingMode.PKCS7;
rj.BlockSize = 256;
rj.KeySize = 256;
rj.Key = Encoding.UTF8.GetBytes(encryptionKey); // ERROR here
rj.GenerateIV();
var encryptedJSON = EncryptStringToBytes(json, rj.Key, rj.IV);
var r1 = Convert.ToBase64String(rj.IV);
var r2 = Convert.ToBase64String(encryptedJSON);
return r1 + r2;
}
The EncryptStringToBytes does some checks and uses this code (plucked from the many examples on the internet):
using (Rijndael rijAlg = Rijndael.Create()) {
// Basically I do the same here as above, and I could also generate
// the IV here, but then I'd had to return it too. I know I can clean this
// code up quite a bit, but I'd rather focus on getting it to work first ;)
rijAlg.Mode = CipherMode.CBC;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.BlockSize = 256;
rijAlg.KeySize = 256;
rijAlg.Key = Key;
rijAlg.IV = IV;
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
The error I'll get:
Specified key is not a valid size for this algorithm.
So, the problems in short:
1) How come the PHP code accepts the key of length 29 in the Rijndael 256 (CBC mode), and my C# doesn't? I've played around with the Mode, added the Padding later, set the KeySize (was 256 default already), and I just can't see what I'm doing wrong here.
2) When I use a key of length 32, this one is accepted and my code works. I can also decrypt it in C# (but can't test this in PHP). I would like to solve problem 1, and then continue on problem 2, but maybe someone can give me some understanding here. The encrypted string contains 1 '=' in the IV, and 2x '==' (at the end) in the encrypted json. I've read about padding and such, but I was wondering why no '=' signs are visible in the PHP examples I received. Again, maybe after fixing problem 1 this won't be an issue.
Many thanks for reading and I hope I'm not being too stupid here. After a day of trying yesterday I kind of get the feeling I've tried many different approaches and non seem to work.
Upvotes: 1
Views: 411
Reputation: 3758
Just thought I'd add a tiny bit to what @artjom-b has said.
Firstly, it does work :-)
But in addition you need to change your
rj.Padding = PaddingMode.PKCS7
to use
rj.Padding = PaddingMode.Zeros
Also, technically, your two functions aren't returning the same thing. The PHP returns base 64 of two concatenated bits of binary data whereas the C# returns a concatenation of separate b64 strings. The result will be different in the second half of the returned string.
EDIT: The rough and ready decryption routine:
public string DecryptRijndael(byte[] cipherText, string password, byte[] iv)
{
var key = new byte[32];
Encoding.UTF8.GetBytes(password).CopyTo(key, 0);
var cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CBC;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 256;
cipher.BlockSize = 256;
cipher.Key = key;
cipher.IV = iv;
byte[] plain;
using (var decryptor = cipher.CreateDecryptor())
{
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
{
cs.Write(cipherText, 0, cipherText.Length);
cs.FlushFinalBlock();
plain = ms.ToArray();
}
}
}
return Encoding.UTF8.GetString(plain);
}
NB: All the caveats and warnings from Artjom B still apply.
Upvotes: 1
Reputation: 61902
You're using an old version of PHP which happily accepts keys that have an invalid length. Rijndael supports key sizes of 16, 24 and 32 bytes and nothing inbetween. The mcrypt extension in PHP silently pads the key with 0x00 bytes up to the next valid key size which is 32 bytes. You will have to do the same thing in C#:
byte[] key = new byte[32];
byte[] password = Encoding.UTF8.GetBytes(encryptionKey);
Array.Copy(password, key, password.Length);
rj.Key = key;
Keep in mind that in order to provide some security a key must have high entropy. A password is not a key and therefore doesn't provide much entropy, because of the limited character set and possible use words. Always derive a key from the password with available derivation functions such as Argon2, scrypt, bcrypt or PBKDF2 with a high cost factor/iteration count and a random salt.
You should also add authentication to your ciphertexts. Otherwise, an attacker might change the ciphertext without you knowing it. This either done by using an authenticated mode like GCM/EAX or running HMAC over the ciphertext to produce the authentication tag.
Upvotes: 1