【问题标题】:Decrypting string encrypted in C# with RijndaelManaged class using PHP使用 PHP 使用 RijndaelManaged 类解密在 C# 中加密的字符串
【发布时间】:2016-04-09 01:31:39
【问题描述】:

这里是一些 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,这些天似乎不赞成使用它,但我可能一直在使用它。接下来,我发现了以下帖子,其中包含一些有用的信息:Rewrite Rijndael 256 C# Encryption Code in PHP

所以看起来 PasswordDeriveBytes 类是解决这个问题的关键。我创建了以下 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 不起作用,它会引发错误“IV 参数必须与块大小一样长”。如果我使用 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]));

【问题讨论】:

    标签: php encryption cryptography encryption-symmetric rijndaelmanaged


    【解决方案1】:

    _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)。
    • 密文未经过身份验证,这意味着您无法检测到对加密消息的(恶意)操纵。使用先加密后 MAC。

    【讨论】:

    • 好的,我想我现在有 $iv。我的问题是关键。我认为密钥是 24 字节的事实是一个问题,基于这里所说的:stackoverflow.com/questions/3505453/…“PasswordDeriveBytes 仅在 - 且仅当 - 请求的长度为 20 或更少字节时才与 PBKDF1 兼容,最大输出使用 SHA1 的 PBKDF1。PasswordDeriveBytes 使用专有方案,这是不安全的,甚至不一致(例如,要求 32 和 16 字节会产生与要求 48 字节不同的结果)"
    • 我只认为你被搞砸了,你必须在 PHP 中实现 PasswordDeriveBytes,这不应该花很长时间,因为 source is freely available。或者你可以在你最喜欢的搜索引擎中深入挖掘
    • 别再看了。 This snippet 按预期工作。 C# 代码:ideone.com/h5lMj1 和 PHP 代码:ideone.com/o0YYm0 请记住,默认 IterationCount 似乎是 100 而不是 1000
    • 你是地球上最伟大的人类。应该写颂歌和十四行诗来赞美你的伟大。非常感谢,我会将此标记为答案。
    • 不要使用 PBKDF1
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-13
    • 1970-01-01
    • 2021-05-01
    • 2019-04-08
    • 2010-09-18
    • 1970-01-01
    • 2018-01-24
    相关资源
    最近更新 更多