Search code examples
c#phpencryptionrijndaelinitialization-vector

Rijndael PHP vs C# - Invalid KeySize in C# but not in PHP


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.


Solution

  • 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.