【问题标题】:Replace mcrypt MCRYPT_RIJNDAEL_256 with openssl or change it to AES将 mcrypt MCRYPT_RIJNDAEL_256 替换为 openssl 或将其更改为 AES
【发布时间】:2017-06-23 19:52:51
【问题描述】:

我正在构建一个简单的 PHP 脚本,它需要对来自 C# 应用程序的输入进行解码。
我创建了具有以下加密功能的 C# 应用程序(我还包括了我的解密功能):

public static string Encrypt(string input, string key)
{
    var aes = new RijndaelManaged
    {
        KeySize = 256,
        BlockSize = 256,
        Padding = PaddingMode.PKCS7,
        Mode = CipherMode.CBC,
        Key = Encoding.UTF8.GetBytes(key) 
    };

    aes.GenerateIV();
    var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
    byte[] buffer;
    using (var ms = new MemoryStream())
    {
        using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
        {
            byte[] bytes = Encoding.UTF8.GetBytes(input);
            cs.Write(bytes, 0, bytes.Length);
        }
        buffer = ms.ToArray();
    }
    buffer = buffer.Concat(aes.IV).ToArray();
    return Convert.ToBase64String(buffer);
}

private static String Decrypt(string text, string key)
{
    RijndaelManaged aes = new RijndaelManaged
    {
        KeySize = 256,
        BlockSize = 256,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7,
        Key = Encoding.UTF8.GetBytes(key)
    };

    byte[] encoded = Convert.FromBase64String(text);
    byte[] buffer = encoded.Take(encoded.Length - aes.IV.Length).ToArray();
    aes.IV = encoded.Skip(encoded.Length - aes.IV.Length).ToArray();
    var decrypt = aes.CreateDecryptor();
    byte[] xBuff;
    using (var ms = new MemoryStream())
    {
        using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
        {
            cs.Write(buffer, 0, buffer.Length);
        }
        xBuff = ms.ToArray();
    }
    var output = Encoding.UTF8.GetString(xBuff);
    return output;
}

经过几分钟的搜索,我在 PHP 中使用 mcrypt 找到了简单的解密函数:

function strippadding($string)
{
    $slast = ord(substr($string, -1));
    $slastc = chr($slast);
    $pcheck = substr($string, -$slast);
    if(preg_match("/$slastc{".$slast."}/", $string)){
    $string = substr($string, 0, strlen($string)-$slast);
        return $string;
    } else {
        return false;
    }
}

function decrypt($string, $key)
{
    $string = base64_decode($string);
    $iv = substr($string, -32);
    $string = str_replace($iv, "", $string);
    return strippadding(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $string, MCRYPT_MODE_CBC, $iv));
}

这很好用,但是当我在多个网站上阅读时,不再推荐使用 mcrypt,迟早会被删除。

我正在尝试使用 openssl 重新创建相同的功能,但没有任何运气。 我尝试将mcrypt_decrypt 替换为:

openssl_decrypt($string, 'aes-256-cbc', $encryption_key, 0, $iv);

但我发现MCRYPT_RIJNDAEL_256 doesn't mean AES-256. 我一直在尝试不同的密钥大小和块大小,但没有运气。

如何使用 openssl 重新创建 PHP 解密函数?

EDIT1:
我在 C# 代码中将 RijndaelManaged 更改为 AesCryptoServiceProvider

var aes = new AesCryptoServiceProvider()
{
    KeySize = 256,
    BlockSize = 128,
    Padding = PaddingMode.PKCS7,
    Mode = CipherMode.CBC,
    Key = Encoding.UTF8.GetBytes(key) 
};

在 PHP 内部:

define('AES_128_CBC', 'aes-128-cbc');

function decrypt_openssl($string, $pkey)
{
    $key = $pkey;
    $string = base64_decode($string);
    $iv = substr($string, -32);
    $string = str_replace($iv, "", $string);

    $decrypted = openssl_decrypt($string, AES_128_CBC, base64_encode($key), 0, base64_encode($iv));
    return $decrypted;
}

但我仍然无法在 PHP 中解码编码字符串。

我需要一种方法来解密我的 C# 函数的输出,或者同时更改两者以使两种方式的通信正常工作。

EDIT2:
我正在提供我的 C# 类的完整源代码:

public static string EncryptRijndael(string input, string key)
{
    var aes = new RijndaelManaged
    {
        KeySize = 256,
        BlockSize = 256,
        Padding = PaddingMode.PKCS7,
        Mode = CipherMode.CBC,
        Key = Encoding.UTF8.GetBytes(key)
    };

    aes.GenerateIV();

    var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

    byte[] buffer;
    using (var ms = new MemoryStream())
    {
        using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
        {
            byte[] bytes = Encoding.UTF8.GetBytes(input);
            cs.Write(bytes, 0, bytes.Length);
        }
        buffer = ms.ToArray();
    }

    buffer = buffer.Concat(aes.IV).ToArray();

    aes.Dispose();
    return Convert.ToBase64String(buffer);
}

public static string DecryptRijndael(string input, string key)
{
    var aes = new RijndaelManaged
    {
        KeySize = 256,
        BlockSize = 256,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7,
        Key = Encoding.UTF8.GetBytes(key)
    };

    byte[] xXml = Convert.FromBase64String(input);
    var buffer = xXml.Take(xXml.Length - aes.IV.Length).ToArray();
    var iv = xXml.Skip(xXml.Length - aes.IV.Length).ToArray();

    aes.IV = iv;
    var decrypt = aes.CreateDecryptor();
    byte[] xBuff;
    using (var ms = new MemoryStream())
    {
        using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
        {
            cs.Write(buffer, 0, buffer.Length);
        }
        xBuff = ms.ToArray();
    }
    aes.Dispose();
    String output = Encoding.UTF8.GetString(xBuff);
    return output;
}


public static string EncryptAes(string input, string key)
{
    var aes = new AesCryptoServiceProvider()
    {
        KeySize = 256,
        BlockSize = 128,
        Padding = PaddingMode.PKCS7,
        Mode = CipherMode.CBC,
        Key = Encoding.UTF8.GetBytes(key) 
    };

    aes.GenerateIV();
    var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
    byte[] buffer;
    using (var ms = new MemoryStream())
    {
        using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
        {
            byte[] bytes = Encoding.UTF8.GetBytes(input);
            cs.Write(bytes, 0, bytes.Length);
        }
        buffer = ms.ToArray();
    }

    buffer = buffer.Concat(aes.IV).ToArray();
    aes.Dispose();
    return Convert.ToBase64String(buffer);
}

public static String DecryptAes(string input, string key)
{
    var aes = new AesCryptoServiceProvider()
    {
        KeySize = 256,
        BlockSize = 128,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7,
        Key = Encoding.UTF8.GetBytes(key) 
    };

    byte[] xXml = Convert.FromBase64String(input);
    var buffer = xXml.Take(xXml.Length - aes.IV.Length).ToArray();
    var iv = xXml.Skip(xXml.Length - aes.IV.Length).ToArray();

    aes.IV = iv;
    var decrypt = aes.CreateDecryptor();
    byte[] xBuff;
    using (var ms = new MemoryStream())
    {
        using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
        {
            cs.Write(buffer, 0, buffer.Length);
        }
        xBuff = ms.ToArray();
    }
    aes.Dispose();
    String output = Encoding.UTF8.GetString(xBuff);
    return output;
}

我的测试密钥是:zjPUcCp9Jn7k8RtEzxTRePjn984LqwyN
我的纯文本测试数据:zażółć geślą jaźń
我的测试数据为base64:emHFvMOzxYLEhyBnZcWbbMSFIGphxbrFhA==
和用于加密功能的输出样本:结果的 Rijndael算法强>:4GD / tt3I3hqYToLnwxI / HJ37EHfXrd1uxchIOjuxSuZl0Kyvxb + S6h4gG3cWKJTbj0wDSH1zvbeSvHd9Wu1VaA ==结果的 AES 强>:B0dKdL4k9J6CeqlAekaXM + EH / zDqd5B4sKK2p6DFsgYNbV56Xdy01XvYPZX8ZXBc P>

IV 在创建 base64 输出之前添加到字节数组的末尾。解密时,我从输入字符串的末尾读取 IV 并使用它来解密。

我需要确保我可以加密/解密 utf-8 字符串。

【问题讨论】:

  • Rijndael 具有 16 字节的块大小和 128、192 或 256 位的密钥大小是 AES。 PHP mcrypt 是个问题,它不支持标准的 PKCS#7 (née PKCS#5) 填充,只支持非标准的空填充。
  • @zaph 我不是 PHP 开发人员,所以如果你能告诉我如何修改我的代码。正如我正确理解的那样,我必须在 PHP 中设置它?还是我必须更改 C# 部分?
  • 最好不要使用mcrypt,它已经废弃了近十年了。因此,它已被弃用,并将在 PHP 7.2 中从核心中移除并进入 PECL。它不支持标准 PKCS#7 (née PKCS#5) 填充,仅支持甚至不能用于二进制数据的非标准空填充。 mcrypt 有许多可追溯到 2003 年的突出错误。请考虑使用 defuseRNCryptor,它们提供了完整的解决方案,正在维护并且是正确的。
  • @zaph 我必须同意 mcrypt 是不可能的,这就是我想使用 openssl 的原因,我可能想避免使用任何外部库。在 C# 部分中,我使用带有 PKCS7 填充的 CBC 模式。
  • @zaph 我可以更改 C# 部分,以便在 PHP 网站上更容易解密。

标签: c# php encryption openssl


【解决方案1】:

由于必要的格式和长度,这不是答案,而是作为答案添加:

由 OP 提供:

加密输入:
测试密钥为:zjPUcCp9Jn7k8RtEzxTRePjn984LqwyN
纯文本测试数据:zażółć geślą jaźń(未使用,见下文)
测试数据为 base64:emHFvMOzxYLEhyBnZcWbbMSFIGphxbrFhA==(已使用)
两种加密函数的示例输出:
Rijndael:4gD/tt3I3hqYToLnwxI/HJ37EHfXrd1uxchIOjuxSuZl0Kyvxb+S6h4gG3cWKJTbj0wDSH1zvbeSvHd9Wu1VaA==
AES:B0dKdL4k9J6CeqlAekaXM+eh/zDqd5B4sKK2p6DFsgYNbV56Xdy01XvYPZX8ZXBc

提供的数据显示为十六进制以保持一致性,添加空格以提高可读性:

Rijndael 预期:e200ffb6 ddc8de1a 984e82e7 c3123f1c 9dfb1077 d7addd6e c5c8483a 3bb14ae6 65d0acaf c5bf92ea 1e201b77 162894db 8f4c0348 7d73bdb7 92bc777d 5aed5568
AES 预期:07474a74 be24f49e 827aa940 7a469733 e7a1ff30 ea779078 b0a2b6a7 a0c5b206 0d6d5e7a 5ddcb4d5 7bd83d95 fc65705c

测试数据:7a61c5bc c3b3c582 c4872067 65c59b6c c485206a 61c5bac5 84
测试密钥:7a6a5055 63437039 4a6e376b 38527445 7a785452 65506a6e 3938344c 7177794e
testIV:8f4c0348 7d73bdb7 92bc777d 5aed5568(来自 Rijndael 预期的尾随字节)。

以上是人们对问题的期望。

假设:PKCS#7 填充,CBC 模式。
基于数据 7 字节的填充,因此加密数据应该是 32 字节。

RijndaelExpected 输出为 64 字节,附加 IV 的减 16 字节为 48 字节,这是不正确的。

AESExpected 输出为 48 字节。如果附加了 IV,则它与 RijndaelExpected 不匹配,如果未附加 IV,则它的长度错误。

我计算出来的没有附加 IV 的 AES 输出是(注意 IV 通常在加密数据前加前缀):

CryptData:1a6ec05d 00a6e61b 8196e7f2 879e2f59 25d3b7e2 c103f7e6 41c8c93f 70b32de5
这不符合预期的输出。

另见online AES calculator 请注意,已手动添加 PKCS#7 填充。

为了完整起见,我的测试代码:

NSData *testData = [[NSData alloc] initWithBase64EncodedString:@"emHFvMOzxYLEhyBnZcWbbMSFIGphxbrFhA==" options:0];
NSData *testKey  = [@"zjPUcCp9Jn7k8RtEzxTRePjn984LqwyN" dataUsingEncoding:NSUTF8StringEncoding];
NSData *testIV   = [[NSData alloc] initWithBase64EncodedString:@"j0wDSH1zvbeSvHd9Wu1VaA==" options:0];

size_t movedBytes = 0;
NSMutableData *cryptData = [NSMutableData dataWithLength: testData.length + kCCBlockSizeAES128];
CCCrypt(kCCEncrypt, kCCAlgorithmAES128,
        kCCOptionPKCS7Padding, // CBC  mode is the default
        testKey.bytes, kCCKeySizeAES256,
        testIV.bytes,
        testData.bytes, testData.length,
        cryptData.mutableBytes, cryptData.length,
        &movedBytes);
cryptData.length = movedBytes;
Display(@"CryptData: %@", cryptData);

【讨论】:

    猜你喜欢
    • 2019-07-23
    • 2017-10-15
    • 2020-05-16
    • 2018-12-12
    • 2016-11-16
    • 2012-04-17
    • 2018-11-10
    • 2018-03-02
    • 2020-07-17
    相关资源
    最近更新 更多