【问题标题】:AES-256-CBC Decrypt Error Stating Padding is invalid and cannot be removedAES-256-CBC 解密错误说明填充无效且无法删除
【发布时间】:2017-03-31 13:54:51
【问题描述】:

我有一个 PHP 应用程序使用 openSSL 将带有 Base64 编码的 AES-256-CBC 加密字符串发送到 .NET 应用程序。我启动了解密方法,但是当我尝试实际解密数据时总是出错。错误状态为“填充无效且无法删除”。我在这里做错了什么?

PHP函数生成的加密Base64值是: p07cNwcvcYLxvYHCUsmZqKYr40IXXYjEHr7r+JdgXiJT5/wpDSDmr48JLOXyNEL7

关键是: M2AZULUALPHA

盐是: TripBuilder2017x

PHP函数是:

function encrypt($text) {
$key = "M2AZULUALPHA";
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$padding = $block - (strlen($text) % $block);
$text .= str_repeat(chr($padding), $padding);

$crypttext = openssl_encrypt($text,'aes-256-cbc', $key,  OPENSSL_RAW_DATA, 'TripBuilder2017x');
return base64_encode($crypttext);

}

.NET 解密函数:

 private string Decrypt(string cipherText)
        {
            string EncryptionKey = "M2AZULUALPHA";
            byte[] saltArray = Encoding.ASCII.GetBytes("TripBuilder2017x");


            byte[] cipherBytes = Convert.FromBase64String(cipherText);
            using (Aes encryptor = Aes.Create())
            {
                Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, saltArray);
                encryptor.Mode = CipherMode.CBC;
                encryptor.BlockSize = 128;
                encryptor.KeySize = 256;
                encryptor.Padding = PaddingMode.PKCS7;
                encryptor.Key = pdb.GetBytes(32);
                encryptor.IV = pdb.GetBytes(16);

                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(cipherBytes, 0, cipherBytes.Length);
                        cs.Close();
                    }
                    cipherText = Encoding.Unicode.GetString(ms.ToArray());
                }


            }
            return cipherText;
        }

更新 我使用了一个在线 openSSL 解密工具,输入了我的 IV、Key 和 base64 文本。它生成了正确的字符串,但字符串中有一些奇怪的块。

更新 2 这是更新的 PHP 代码。我也将密钥大小增加到 16 个字节。我仍然收到同样的错误。

$key = "M2AZULUALPHAECHO";
$salt = "TripBuilder2017x";
$mode = "aes-256-cbc";
$text = "BrassMonkey";

function encrypt($text,$key,$salt,$mode) {

    return base64_encode(encryptplain($text,$key,$salt,$mode));
}

function encryptplain($text,$key,$salt,$mode) {


    $block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
    $padding = $block - (strlen($text) % $block);
    $text .= str_repeat(chr($padding), $padding);

    $crypttext = openssl_encrypt($text, $mode, $key, 1, $salt);
    return ($crypttext);
}

更新 3:我的 .NET 代码

private string Decrypt(string cipherText)
        {
            string EncryptionKey = "M2AZULUALPHAECHO";
            byte[] saltArray = Encoding.ASCII.GetBytes("TripBuilder2017x");

            byte[] cipherBytes = Convert.FromBase64String(cipherText);
            using (Aes encryptor = Aes.Create())
            {

                encryptor.Mode = CipherMode.CBC;
                encryptor.BlockSize = 128;
                encryptor.KeySize = 256;
                encryptor.Padding = PaddingMode.PKCS7;
                encryptor.Key = Encoding.ASCII.GetBytes(EncryptionKey);
                encryptor.IV = saltArray;

                using (MemoryStream ms = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
                    {
                        cs.Write(cipherBytes, 0, cipherBytes.Length);
                        cs.Close();
                    }
                    cipherText = Encoding.Unicode.GetString(ms.ToArray());
                }


            }
            return cipherText;
        }

【问题讨论】:

  • 我遇到了类似的问题,我的结论是 openssl 不会返回任何二进制数据 :(,我改用 mcrypt
  • 我在使用 MCrypt 时遇到了同样的问题,这就是我尝试 OpenSSL 的原因。由于我遇到了同样的错误,我觉得这与我分配值的属性有关。
  • 我不推荐使用 mcrypt,因为这是当前 PHP 版本中已弃用的函数。在线查看器中的块可能是您的浏览器/站点使用的字符集中不可用的字符的原因。
  • mcrypt_get_block_size 以字节为单位返回块大小,而 Aes 块大小需要位。我猜这可能是你的不匹配。
  • @DempseyLawrence 我们甚至需要获取块大小吗?OpenSSL 中是否有等效的方法?

标签: c# php encryption cryptography


【解决方案1】:

我可以看到几个直接的问题:

  1. 您的 C# 解密代码使用 PBKDF2 从密码和盐 派生 AES 密钥和 IV,而您的 PHP 加密代码使用密码和盐 直接 作为AES 密钥和 IV。如果你想让它正常工作,你需要在这两个地方使用相同的逻辑。
  2. 在计算块长度和填充输入时,您在 PHP 代码中指定了错误的加密算法 - 各种版本的 Rijndael 根据其 长度命名,而所有版本的 AES具有 128 位块长度(即 Rijndael-128)并使用不同的 key 长度。因此,如果您想要正确的填充,您需要指定 MCRYPT_RIJNDAEL_128。
  3. 除非您还指定 OPENSSL_ZERO_PADDING 标志,否则您不应在加密期间自行执行填充 - 如果您未能指定此标志,OpenSSL 会将其自己的 PKCS#7 填充添加到输入文本中,扩展它由另一个块,您的解密逻辑只会剥离该部分,忽略您手动添加的填充(并导致您使用在线 OpenSSL 解密工具观察到的“字符串中的奇怪块”)。
  4. 您需要确保您指定的 AES 密钥大小实际上与您指定的算法相匹配 - PHP 的 openssl_encrypt() 函数扩展了您的密钥大小(带有空字节)以匹配您指定的算法,而 C# 将自动调整 算法以匹配您提供的密钥大小。在这种情况下,您的 C# 代码实际上是在执行 AES-128 解密,这与您在 PHP 代码中执行的 AES-256 加密不兼容。

其他说明:

  1. 如果您想要从 openssl_encrypt() 获得 Base64 编码的结果,您可以省略 OPENSSL_RAW_DATA 标志,它将返回 Base64 编码的密文。
  2. 问题 #1 的正确解决方案应该是在两个地方都执行密钥派生,或者使用 二进制 密钥和 IV 值 - 明文密钥的熵要小得多,而且安全性要低得多。
  3. 您不应在代码中硬编码密钥和 IV - 密钥应作为参数提供,IV 应随机生成并随后与密文相关联。

【讨论】:

  • 感谢您的建议。我将键值更改为使用 Encoding.ASCII.GetBytes(EncryptionKey);和盐数组使用我之前创建的字节数组。 encryptor.IV = saltArray;.. 当我运行此代码只是作为测试而不进行其他更改时,它说我的密钥不够长,所以我添加了更多字符使其变为 16 字节,然后我能够继续并得到提示具有相同的填充错误。我现在要实施 PHP 更改。密钥也更新了,我原来的测试有“x”。我不确定我是如何在复制和粘贴中丢失的。
  • 谢谢。我做了你建议的更改,不包括备注下列出的更改,我得到了同样的错误。见上面的PHP代码更新。
  • 我更新了我的 C# 代码。是什么让我的解密方法完全成为 AES128 解密?抱歉,这是我第一次搞乱代码加密。
  • 如果您手动指定 encryptor.Key,则 encryptor.KeySize 的值似乎被忽略(如果您保留 Key null,它将改为调用 GenerateKey())。由于您的密钥长度为 16 个字节(即 128 位),因此它使用 AES-128。
  • 那么出于测试目的,如果我将密钥的长度加倍,它应该可以工作吗?我知道我没有使用最佳实践来解决其中的一些问题,但我只是想更多地了解我的错误,让它发挥作用,然后从那里倒退。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-02
  • 2016-07-18
  • 1970-01-01
  • 1970-01-01
  • 2018-07-17
  • 1970-01-01
相关资源
最近更新 更多