使用 PHP 使用 RijndaelManaged class 解密在 C# 中加密的字符串
Decrypting string encrypted in C# with RijndaelManaged class using PHP
这是一些 C# 代码(我对其进行了一些修改以修改其中的一些硬编码值):
public static string Decrypt(string InputFile)
{
string outstr = null;
if ((InputFile != null))
{
if (File.Exists(InputFile))
{
FileStream fsIn = null;
CryptoStream cstream = null;
try
{
byte[] _b = { 94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101 };
fsIn = File.Open(InputFile, FileMode.Open, System.IO.FileAccess.Read);
SymmetricAlgorithm symm = new RijndaelManaged();
PasswordDeriveBytes Key = new PasswordDeriveBytes(System.Environment.MachineName, System.Text.Encoding.Default.GetBytes("G:MFX62rlABW:IUYAX(i"));
ICryptoTransform transform = symm.CreateDecryptor(Key.GetBytes(24), _b);
cstream = new CryptoStream(fsIn, transform, CryptoStreamMode.Read);
StreamReader sr = new StreamReader(cstream);
char[] buff = new char[1000];
sr.Read(buff, 0, 1000);
outstr = new string(buff);
}
finally
{
if (cstream != null)
{
cstream.Close();
}
if (fsIn != null)
{
fsIn.Close();
}
}
}
}
return outstr;
}
我需要想出一个函数来在 PHP 中做同样的事情。请记住,我没有编写 C# 代码,也无法修改它,所以即使它很糟糕,我也坚持使用它。我到处搜索,发现了一些零碎的东西,但到目前为止没有任何效果。我发现的所有示例都使用 mcrypt,这几天似乎不受欢迎,但我可能一直在使用它。接下来,我发现以下 post 其中有一些有用的信息: Rewrite Rijndael 256 C# Encryption Code in PHP
所以看起来 PasswordDeriveBytes class 是关键。我创建了以下 PHP 代码来尝试解密:
function PBKDF1($pass,$salt,$count,$dklen) {
$t = $pass.$salt;
//echo 'S||P: '.bin2hex($t).'<br/>';
$t = sha1($t, true);
//echo 'T1:' . bin2hex($t) . '<br/>';
for($i=2; $i <= $count; $i++) {
$t = sha1($t, true);
//echo 'T'.$i.':' . bin2hex($t) . '<br/>';
}
$t = substr($t,0,$dklen);
return $t;
}
$input = 'Ry5WdjGS8rpA9eA+iQ3aPw==';
$key = "win7x64";
$salt = implode(unpack('C*', "G:MFX62rlABW:IUYAX(i"));
$salt = pack("H*", $salt);
$it = 1000;
$keyLen = 16;
$key = PBKDF1($key, $salt, $it, $keyLen);
$key = bin2hex(substr($key, 0, 8));
$iv = bin2hex(substr($key, 8, 8));
echo trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($input), MCRYPT_MODE_CBC, $iv));
你会注意到,对于我认为的 System.Environment.MachineName,我现在输入了一个固定值,这是我所在机器的计算机名称,所以应该等同于C#代码正在做。除此之外,我注意到使用 MCRYPT_RIJNDAEL_256 不起作用,它会抛出错误 "The IV parameter must be as long as the blocksize"。如果我使用 MCRYPT_RIJNDAEL_128,我不会得到那个错误,但解密仍然失败。我假设我遗漏了 CreateDecryptor 函数使用的字节数组 _b 的片段,我不知道它应该放在哪里。感谢任何帮助。
更新
这是解决方案,通过标记为正确的答案成为可能。请注意,PBKDF1 函数的代码不是我的,它已链接到答案中。
function PBKDF1($pass, $salt, $count, $cb) {
static $base;
static $extra;
static $extracount= 0;
static $hashno;
static $state = 0;
if ($state == 0)
{
$hashno = 0;
$state = 1;
$key = $pass . $salt;
$base = sha1($key, true);
for($i = 2; $i < $count; $i++)
{
$base = sha1($base, true);
}
}
$result = "";
if ($extracount > 0)
{
$rlen = strlen($extra) - $extracount;
if ($rlen >= $cb)
{
$result = substr($extra, $extracount, $cb);
if ($rlen > $cb)
{
$extracount += $cb;
}
else
{
$extra = null;
$extracount = 0;
}
return $result;
}
$result = substr($extra, $rlen, $rlen);
}
$current = "";
$clen = 0;
$remain = $cb - strlen($result);
while ($remain > $clen)
{
if ($hashno == 0)
{
$current = sha1($base, true);
}
else if ($hashno < 1000)
{
$n = sprintf("%d", $hashno);
$tmp = $n . $base;
$current .= sha1($tmp, true);
}
$hashno++;
$clen = strlen($current);
}
// $current now holds at least as many bytes as we need
$result .= substr($current, 0, $remain);
// Save any left over bytes for any future requests
if ($clen > $remain)
{
$extra = $current;
$extracount = $remain;
}
return $result;
}
$input = 'base 64 encoded string to decrypt here';
$key = strtoupper(gethostname());
$salt = 'G:MFX62rlABW:IUYAX(i';
$it = 100;
$keyLen = 24;
$key = PBKDF1($key, $salt, $it, $keyLen);
$iv = implode(array_map('chr', [94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101]));
_b
是用作 IV 的静态值(CreateDecryptor
采用键和 IV 参数)。由于它有 16 个字节长,这意味着您使用的是 Rijndael-128 或更广为人知的 AES。
Key.GetBytes(24)
表明派生的是 24 字节密钥而不是 16 字节密钥。
确保
System.Text.Encoding.Default
等同于implode(unpack('C*', ...
,
PasswordDeriveBytes
的迭代默认值为 1000,
PasswordDeriveBytes
的哈希默认值为 SHA-1
安全问题:
- PBKDF1 已过时,PBKDF2 也好不了多少。使用最新的密钥派生算法,如 Argon2 或 scrypt。
- 必须随机选择 IV 才能实现语义安全。它不必是秘密的,所以它可以与密文一起发送。
- 通过将密钥编码为十六进制来扩展密钥不提供任何安全性(不要使用
bin2hex
)。
- 密文未经验证,这意味着您无法检测到对加密消息的(恶意)操纵。使用 encrypt-then-MAC.
这是一些 C# 代码(我对其进行了一些修改以修改其中的一些硬编码值):
public static string Decrypt(string InputFile)
{
string outstr = null;
if ((InputFile != null))
{
if (File.Exists(InputFile))
{
FileStream fsIn = null;
CryptoStream cstream = null;
try
{
byte[] _b = { 94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101 };
fsIn = File.Open(InputFile, FileMode.Open, System.IO.FileAccess.Read);
SymmetricAlgorithm symm = new RijndaelManaged();
PasswordDeriveBytes Key = new PasswordDeriveBytes(System.Environment.MachineName, System.Text.Encoding.Default.GetBytes("G:MFX62rlABW:IUYAX(i"));
ICryptoTransform transform = symm.CreateDecryptor(Key.GetBytes(24), _b);
cstream = new CryptoStream(fsIn, transform, CryptoStreamMode.Read);
StreamReader sr = new StreamReader(cstream);
char[] buff = new char[1000];
sr.Read(buff, 0, 1000);
outstr = new string(buff);
}
finally
{
if (cstream != null)
{
cstream.Close();
}
if (fsIn != null)
{
fsIn.Close();
}
}
}
}
return outstr;
}
我需要想出一个函数来在 PHP 中做同样的事情。请记住,我没有编写 C# 代码,也无法修改它,所以即使它很糟糕,我也坚持使用它。我到处搜索,发现了一些零碎的东西,但到目前为止没有任何效果。我发现的所有示例都使用 mcrypt,这几天似乎不受欢迎,但我可能一直在使用它。接下来,我发现以下 post 其中有一些有用的信息: Rewrite Rijndael 256 C# Encryption Code in PHP
所以看起来 PasswordDeriveBytes class 是关键。我创建了以下 PHP 代码来尝试解密:
function PBKDF1($pass,$salt,$count,$dklen) {
$t = $pass.$salt;
//echo 'S||P: '.bin2hex($t).'<br/>';
$t = sha1($t, true);
//echo 'T1:' . bin2hex($t) . '<br/>';
for($i=2; $i <= $count; $i++) {
$t = sha1($t, true);
//echo 'T'.$i.':' . bin2hex($t) . '<br/>';
}
$t = substr($t,0,$dklen);
return $t;
}
$input = 'Ry5WdjGS8rpA9eA+iQ3aPw==';
$key = "win7x64";
$salt = implode(unpack('C*', "G:MFX62rlABW:IUYAX(i"));
$salt = pack("H*", $salt);
$it = 1000;
$keyLen = 16;
$key = PBKDF1($key, $salt, $it, $keyLen);
$key = bin2hex(substr($key, 0, 8));
$iv = bin2hex(substr($key, 8, 8));
echo trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($input), MCRYPT_MODE_CBC, $iv));
你会注意到,对于我认为的 System.Environment.MachineName,我现在输入了一个固定值,这是我所在机器的计算机名称,所以应该等同于C#代码正在做。除此之外,我注意到使用 MCRYPT_RIJNDAEL_256 不起作用,它会抛出错误 "The IV parameter must be as long as the blocksize"。如果我使用 MCRYPT_RIJNDAEL_128,我不会得到那个错误,但解密仍然失败。我假设我遗漏了 CreateDecryptor 函数使用的字节数组 _b 的片段,我不知道它应该放在哪里。感谢任何帮助。
更新
这是解决方案,通过标记为正确的答案成为可能。请注意,PBKDF1 函数的代码不是我的,它已链接到答案中。
function PBKDF1($pass, $salt, $count, $cb) {
static $base;
static $extra;
static $extracount= 0;
static $hashno;
static $state = 0;
if ($state == 0)
{
$hashno = 0;
$state = 1;
$key = $pass . $salt;
$base = sha1($key, true);
for($i = 2; $i < $count; $i++)
{
$base = sha1($base, true);
}
}
$result = "";
if ($extracount > 0)
{
$rlen = strlen($extra) - $extracount;
if ($rlen >= $cb)
{
$result = substr($extra, $extracount, $cb);
if ($rlen > $cb)
{
$extracount += $cb;
}
else
{
$extra = null;
$extracount = 0;
}
return $result;
}
$result = substr($extra, $rlen, $rlen);
}
$current = "";
$clen = 0;
$remain = $cb - strlen($result);
while ($remain > $clen)
{
if ($hashno == 0)
{
$current = sha1($base, true);
}
else if ($hashno < 1000)
{
$n = sprintf("%d", $hashno);
$tmp = $n . $base;
$current .= sha1($tmp, true);
}
$hashno++;
$clen = strlen($current);
}
// $current now holds at least as many bytes as we need
$result .= substr($current, 0, $remain);
// Save any left over bytes for any future requests
if ($clen > $remain)
{
$extra = $current;
$extracount = $remain;
}
return $result;
}
$input = 'base 64 encoded string to decrypt here';
$key = strtoupper(gethostname());
$salt = 'G:MFX62rlABW:IUYAX(i';
$it = 100;
$keyLen = 24;
$key = PBKDF1($key, $salt, $it, $keyLen);
$iv = implode(array_map('chr', [94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101]));
_b
是用作 IV 的静态值(CreateDecryptor
采用键和 IV 参数)。由于它有 16 个字节长,这意味着您使用的是 Rijndael-128 或更广为人知的 AES。
Key.GetBytes(24)
表明派生的是 24 字节密钥而不是 16 字节密钥。
确保
System.Text.Encoding.Default
等同于implode(unpack('C*', ...
,PasswordDeriveBytes
的迭代默认值为 1000,PasswordDeriveBytes
的哈希默认值为 SHA-1
安全问题:
- PBKDF1 已过时,PBKDF2 也好不了多少。使用最新的密钥派生算法,如 Argon2 或 scrypt。
- 必须随机选择 IV 才能实现语义安全。它不必是秘密的,所以它可以与密文一起发送。
- 通过将密钥编码为十六进制来扩展密钥不提供任何安全性(不要使用
bin2hex
)。 - 密文未经验证,这意味着您无法检测到对加密消息的(恶意)操纵。使用 encrypt-then-MAC.