c# - php 到 C# 使用 Rijindael-256 算法加密并使用 base64 算法编码的 JSON 数据数组

标签 c# php api encryption aes

我正在尝试将以下 php 代码转换为 C#:

 $m_params = urlencode(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256,$key, json_encode($arParams), MCRYPT_MODE_ECB)));

文档内容:

m_params : A JSON array of data of additional parameters encrypted using the Rijindael-256 algorithm and encoded using a base64 algorithm.

我的假设是什么?

第 1 步:创建参数数组,即 $arParams 对于 php,其声明如下:

$arParams = array(
'success_url' => 'http://google.com/new_success_url',
'fail_url' => 'http://google.com/new_fail_url',
'status_url' => 'http://google.com/new_status_url',
);

对于 C#,我这样声明:

 var additional_params = new object[]
                        {
                            new {"http://google.com/new_success_url"},
                            new {"http://google.com/new_fail_url"},
                            new {"http://google.com/new_status_url"},
                        };

第 2 步:编码为 JSON 字符串,我使用过 JsonConvert.SerializeObject(additional_params);

第 3 步:使用 RIJNDAEL-256 算法使用 ECB 加密结果(我也使用过 CBC)

第 4 步:使用 base64 对结果进行编码。我用过Convert.ToBase64String(encrypted);

第 5 步: 对结果进行 URL 编码。我用过HttpUtility.UrlEncode(base64String, Encoding.UTF8);

第 6 步:将结果保存在 m_params

我当前的代码如下所示:

                var additional_params = new object[]
                    {
                        new {"http://google.com/new_success_url"},
                        new {"http://google.com/new_fail_url"},
                        new {"http://google.com/new_status_url"},
                    };
                string m_params ="";
                //converting to Json object additional params
                var jsonEncoded = JsonConvert.SerializeObject(additional_params);
                try
                {

                    string original = jsonEncoded;

                    // Create a new instance of the RijndaelManaged
                    // class.  This generates a new key and initialization
                    // vector (IV).
                    using (RijndaelManaged myRijndael = new RijndaelManaged())
                    {
                        var final_Key = CreateMD5(payeer.m_key + payeer.m_orderid);
                        var rfc = CreateKey(final_Key);
                        
                        // Encrypt the string to an array of bytes.
                        byte[] encrypted = EncryptStringToBytes(original, rfc[0], rfc[1]);
                        var base64String = Convert.ToBase64String(encrypted);
                        m_params = HttpUtility.UrlEncode(base64String, Encoding.UTF8);
                        // Decrypt the bytes to a string.
                        string roundtrip = DecryptStringFromBytes(encrypted, rfc[0], rfc[1]);

                        //Display the original data and the decrypted data.
                        Console.WriteLine("Original:   {0}", original);
                        Console.WriteLine("Round Trip: {0}", roundtrip);
                    }

         static byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
           {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encrypted;
            // Create an RijndaelManaged object
            // with the specified key and IV.
            using (RijndaelManaged rijAlg = new RijndaelManaged())
            {
                rijAlg.Key = Key;
                rijAlg.IV = IV;
                rijAlg.Mode = CipherMode.ECB;
              //  rijAlg.KeySize = 256;
                rijAlg.BlockSize = 256;
                rijAlg.Padding = PaddingMode.PKCS7;
                // Create an encryptor to perform the stream transform.
                ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {

                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }

            // Return the encrypted bytes from the memory stream.
            return encrypted;
        }


public static string CreateMD5(string input)
        {
            // Use input string to calculate MD5 hash
            using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
            {
                byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
                byte[] hashBytes = md5.ComputeHash(inputBytes);

                // Convert the byte array to hexadecimal string
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < hashBytes.Length; i++)
                {
                    sb.Append(hashBytes[i].ToString("X2"));
                }
                return sb.ToString();
            }
        }
        public static dynamic CreateKey(string password)
        {
            var salt = new byte[] { 1, 2, 23, 234, 37, 48, 134, 63, 248, 4 };

            const int Iterations = 9872;
            using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, Iterations))
            {
                var key = rfc2898DeriveBytes.GetBytes(32);
                var IV = rfc2898DeriveBytes.GetBytes(16);
                dynamic[] arr = new dynamic[2];
                arr[0] = key;
                arr[1] = IV;
                return arr;
            }
                
        }

它没有给出相同的输出。我错过了什么吗?

最佳答案

正如 James K. Polk 总裁在评论中提到的, block 大小为 256 位的 Rijndael 仅在 .NET Framework 中受支持,在 .NET Core 中不受支持。您没有指定您正在运行的版本,但由于您在发布的代码中使用了 256 位的 block 大小 (rijAlg.BlockSize = 256;),我假设您正在运行 .NET Framework(否则,您需要应用支持 block 大小为256位的Rijndael的第三方库,例如BouncyCaSTLe/C#)。

两个代码都使用不同的填充。 mcrypt 默认情况下应用零填充,C# 代码显式使用 PKCS7 填充(这也是 C# 默认值)。为了使 C# 代码提供与 PHP 代码相同的结果,需要在 C# 代码中切换到零填充(需要注意的是,零填充是不可靠的,与 PKCS7 填充不同)。

additional_params被实例化时(顺便说一句,它不能在我的机器上编译),变量名丢失了,因此它们在序列化中也丢失了。可以使用匿名类型来代替。另请注意,json_encode() 默认情况下会转义斜杠 (/),即将其转换为 \/,这是必须完成的手动在 C# 代码中,例如与替换(“/”,“\\/”)。 JSON 序列化的一种可能实现是:

using Newtonsoft.Json;
...
var additionalParams = new
{
    success_url = "http://google.com/new_success_url",
    fail_url = "http://google.com/new_fail_url",
    status_url = "http://google.com/new_status_url"
};
string jsonEncoded = JsonConvert.SerializeObject(additionalParams).Replace("/", "\\/");

在 PHP 代码中, key 是使用 MD5 摘要从密码派生的。默认情况下,md5()以十六进制字符串形式返回结果,该字符串将 16 字节哈希转换为用作 key 的 32 字节值,以便使用 AES-256。 PHP用小写字母表示十六进制数字,在C#代码中也必须相应实现,例如:

using System;
using System.Text;
using System.Security.Cryptography;
...
MD5 md5 = MD5.Create();
string password = "My password"; // test password 
byte[] passwordHash = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
string passwordHashHex = BitConverter.ToString(passwordHash).Replace("-", "").ToLower();  // convert byte array to lowercase hex string as in PHP
byte[] key = Encoding.UTF8.GetBytes(passwordHashHex);

其中字节数组到十六进制字符串的转换是使用 BitConverter 完成的,请参阅 here .

加密的可能实现是:

using System;
using System.IO;
using System.Web;
using System.Text;
using System.Security.Cryptography;
...
byte[] encrypted = null;
using (RijndaelManaged rijndael = new RijndaelManaged())
{
    rijndael.Key = key;
    rijndael.Mode = CipherMode.ECB;           // default: CBC
    rijndael.BlockSize = 256;                 // default: 128
    rijndael.Padding = PaddingMode.Zeros;     // default: PKCS7

    ICryptoTransform encryptor = rijndael.CreateEncryptor(rijndael.Key, null);
    using (MemoryStream msEncrypt = new MemoryStream())
    {
        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
        {
            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
            {
                swEncrypt.Write(jsonEncoded);
            }
            encrypted = msEncrypt.ToArray();
        }
    }
}
string base64String = Convert.ToBase64String(encrypted);
string m_params = HttpUtility.UrlEncode(base64String, Encoding.UTF8);
Console.WriteLine(m_params);

其中带有所使用的测试密码的代码给出以下结果:

C3pldgsLDSqfG28cbt%2fv0uiBNQT6cWn86iRwg%2bv2blTzR7Lsnra%2b2Ok35Ex9f9UbG%2bjhKgITUQ8kO3DrIrWUQWirzYzwGBucHNRThADf60rGUIBDdjZ2kOIhDVXUzlMsZtBvYIgFoIqFJXCbhZq9GGnKtABUOa5pcmIYeUn%2b%2fqG1mdtJenP5vt8n0eTxsAd6CFc1%2bguR0wZx%2fEZAMsBBRw%3d%3d

根据以下 PHP 代码的结果:

$key = md5('My password'); // test password
$arParams = array(
    'success_url' => 'http://google.com/new_success_url',
    'fail_url' => 'http://google.com/new_fail_url',
    'status_url' => 'http://google.com/new_status_url',
);
$m_params = urlencode(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256,$key, json_encode($arParams), MCRYPT_MODE_ECB)));
print($m_params . "\n");

请注意,C# 使用小写字母进行 url 编码,而 PHP 使用大写字母,表示相同 url 编码,参见 RFC 3986, sec. 2.1 。如果 C# 代码还应应用大写字母进行 url 编码,则可以使用正则表达式轻松实现,请参见例如here .


关于安全的一些评论:

PHP代码采用了不安全的ECB模式。出于安全原因,应使用带有 IV 的模式,例如CBC 或 GCM。后者提供隐式认证加密。 IV 是为每次加密随机生成的,不是 secret 的,并且与密文(通常在前面)一起发送给接收者。

MD5 作为 key 派生函数 (KDF) 也是不安全的。这里,应该使用可靠的 KDF,例如PBKDF2。

此外,使用十六进制字符串作为 key 也会削弱相同的效果,因为每个字节都会减少为十六进制数字系统的 16 个值。更安全的是使用KDF生成的二进制数据,这样每个字节可以取256个不同的值。

mcrypt已弃用。可能的替代方案是 openssl .

关于c# - php 到 C# 使用 Rijindael-256 算法加密并使用 base64 算法编码的 JSON 数据数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65507334/

相关文章:

c# - 如何查看系统音频是否静音?

c# - PointerPressed 在左键单击时不起作用

php - 关于单选按钮/PHP/MySQL 的问题

php - 使用 GWT 优于 PHP 的优势

REST API 心跳标准

ruby-on-rails - Rails 2.3.9 缺少 POST 负载

c# - "Pin"控制ToolstripMenu中的项目

PHP - 从数据库中分配值,表中的 html 链接

ios - setScaleX/setScaleY (Cocos2D)

c# - 从 DNS 获取 IP