我正在尝试将以下 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个不同的值。
关于c# - php 到 C# 使用 Rijindael-256 算法加密并使用 base64 算法编码的 JSON 数据数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65507334/