Rijndael PHP vs C# - C# 中的 KeySize 无效,但 PHP 中没有

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

我尝试在 C# 中使用 Rijndael 加密字符串 (json) 并生成一个字符串,我可以将其提供给 PHP Web 服务。此 Web 服务依次使用 IV 和主密钥(它们已知)对字符串进行解码。我必须编写可以与 PHP 服务通信的 C# 代码,我不会 control/own PHP 服务。

加密的PHP代码如下:

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)))

我必须将上面的代码转换为 C#,这样我才能加密我的 JSON 并将其提供给 PHP 网络服务。

有两个问题。第一个是主密钥长度,第二个(可能相关)是加密数据的 rawurlencode(我现在很难测试)。

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;
}

EncryptStringToBytes 做了一些检查并使用了这段代码(摘自互联网上的许多示例):

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();
        }
    }
}

我将得到的错误:

Specified key is not a valid size for this algorithm.

所以,问题简而言之:

1) 为什么 PHP 代码接受 Rijndael 256(CBC 模式)中长度为 29 的密钥,而我的 C# 却不接受?我玩过模式,后来添加了填充,设置了 KeySize(已经是 256 默认值),我只是看不出我在这里做错了什么。

2) 当我使用长度为 32 的密​​钥时,这个密钥被接受并且我的代码有效。我也可以用 C# 解密它(但不能在 PHP 中测试它)。我想解决问题 1,然后继续解决问题 2,但也许有人可以在这里给我一些理解。加密字符串在IV中包含1个'=',在加密后的json中包含2x'=='(最后)。我读过有关填充等的内容,但我想知道为什么在我收到的 PHP 示例中看不到“=”符号。同样,也许在解决问题 1 之后这将不是问题。

非常感谢您的阅读,希望我在这里没有说得太蠢。在昨天尝试了一天之后,我感觉我已经尝试了很多不同的方法,但似乎都没有用。

您使用的是旧版本的 PHP,它很乐意接受长度无效的密钥。 Rijndael 支持 16、24 和 32 字节的密钥大小,不支持介于两者之间的密钥。 PHP 中的 mcrypt 扩展悄悄地用 0x00 字节填充密钥直到下一个有效密钥大小,即 32 字节。你将不得不在 C# 中做同样的事情:

byte[] key = new byte[32];
byte[] password = Encoding.UTF8.GetBytes(encryptionKey);
Array.Copy(password, key, password.Length);
rj.Key = key;

请记住,为了提供一定的安全性,密钥必须具有高熵。密码不是密钥,因此不会提供太多的熵,因为字符集和可能使用的词有限。始终使用具有高成本 factor/iteration 计数和随机盐的 Argon2、scrypt、bcrypt 或 PBKDF2 等可用派生函数从密码派生密钥。

您还应该为您的密文添加身份验证。否则,攻击者可能会在您不知情的情况下更改密文。这可以通过在密文上使用 GCM/EAX 或 运行 HMAC 等身份验证模式来生成身份验证标记来完成。

我只是想对@artjom-b 所说的内容进行一点补充。

首先,它确实有效:-)

但是你还需要改变你的

rj.Padding = PaddingMode.PKCS7

使用

rj.Padding = PaddingMode.Zeros 

此外,从技术上讲,您的两个函数不会返回相同的东西。 PHP returns 二进制数据的两个串联位的 base 64,而 C# returns 是单独的 b64 字符串的串联。返回字符串的后半部分结果会有所不同。

编辑:粗略和准备好的解密例程:

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);
}

注意:来自 Artjom B 的所有注意事项和警告仍然适用。