PHP 等效于 C# SHA1 Unicode 哈希

PHP equivalent of C# SHA1 Unicode hashing

我有一个带有用户身份验证的 运行 C# 应用程序。我使用 Encoding.Unicode 使用 SHA1Managed class 加密密码。现在,我们正在开发一个新的 PHP 网络应用程序,PHP sha1 方法似乎使用 ASCII,因此加密后的密码略有不同。

我无法更改我的 C# 代码,因为它在许多不同的服务器中 运行 并且所有用户密码都已加密。这无法更改,因为这意味着向所有用户询问他们的密码(这是不可行的)。

我已经阅读了以下解决方案,但是我无法用它们解决我的问题:

  1. C# SHA-1 vs. PHP SHA-1...Different Results? 在此他们建议使用 ASCIIEncoding。但是,正如我之前所说,这是无法更改的。
  2. and Generate a PHP UTF-16 SHA1 hash to match C# method这些使用base64编码,与我的C#编码不兼容(我认为)
  3. php equivalent to following sha1 encryption c# code这个也用ASCIIEncoding.

这是我无法更改的 C# 代码:

byte[] msgBytes = Encoding.Unicode.GetBytes(data);
SHA1Managed sha1 = new SHA1Managed();
byte[] hashBytes = sha1.ComputeHash(msgBytes);

StringBuilder hashRet = new StringBuilder("");
foreach (byte b in hashBytes)
    hashRet.AppendFormat("{0:X}", b);

return hashRet.ToString();

这是目前实现的 PHP 代码:

$str=mb_convert_encoding($password.$SALT, 'UTF-16LE');
$result=strtoupper(sha1($str));

C#中示例字符串“1981”得到的结果 D4CEDCADB729F37C30EBF41BC8F2929AF526AD3

在 PHP 我得到: D4CEDCADB729F37C30EBF41BC8F29209AF526AD3

可以看出,每个之间的唯一区别是 0(零)字符。这发生在所有字符串中,但 0 出现在不同的位置。

编辑

https://whosebug.com/users/1839439/dharman 所述,结果无法完全重现,因为我的字符串有一个 SALT。但是,即使没有 SALT,结果也应该不同。

解决方案 一位用户提出了解决我问题的建议。不幸的是,他在我 post 认为它有效之前就删除了答案。当我在 StringBuilder 中将字节解析为字符串时,问题出在 C# 中。格式为 {0:X} 并且字节中的任何前导 0 都将被忽略。例如 01 将被解析为 1,0D 将被解析为 D。

因此,我不得不迭代 PHP 中获得的成对结果,并删除其中的前导零。

PHP中的最终代码如下:

$str=mb_convert_encoding($password.$SALT, 'UTF-16LE');
$result=strtoupper(sha1($str));
$array = str_split($result, 2);
foreach ($array as &$valor) {
    $valor = ltrim($valor, '0');
}
unset($valor);
$result = implode($array);

你不会喜欢我的回答,但就是这样。
首先,问题不在于 PHP,PHP 很简单,而且工作正常。你的 C# 代码中有一个严重的错误。它需要尽快解决一个或另一个问题。

This cannot be changed as it would imply asking all users their passwords (which is not viable).

不幸的是,这是必须发生的事情。不过,您可以在不引起大规模恐慌的情况下做到这一点。我假设您的数据库有某种方式知道密码是否已被管理员重置;如果不是,那么您需要添加这样的列(最好是时间戳类型)。下次用户登录时,他们必须提供他们的密码,因为你已经重置了所有密码,所以获取该密码并正确地重新散列并将新散列存储在数据库中。为新散列使用一个新列是明智的,或者至少有一种识别更正散列的方法。该列应该至少是 VARCHAR(60),但最好是 VARCHAR(255),它应该容纳所有流行的哈希。

当然,即使使用盐,SHA1 也不是一种合适的密码哈希方法。由于您无论如何都需要重新散列所有密码,因此最好切换到 bcrypt 或类似的安全散列函数。

由于您的原始应用程序引入了此错误,您需要一些解决方法。在新应用程序中支持错误行为似乎不是一个好主意,所以我建议不要在 PHP 中寻找解决方法。您当然可以尝试按照问题中建议的方式修改密码,但这只会将相同的错误拖到新应用程序中而根本不修复它。

在开始做任何事情之前,您应该分析数据库中有多少密码已损坏。正确的 SHA1 散列长度应该是 40 个字符。如果你能找到少于 40 个的密码,你就会知道需要修复哪些密码和多少密码。

修复密码会很困难,但绝对值得。

字符编码注意事项:
PHP 大多数时候使用 UTF-8 编码。它是网络上最常用的编码,我建议将其用于您的字符串。当涉及到散列时,字符串的编码并不重要,因为散列是按字节计算的。这就是您使用 Encoding.Unicode.GetBytes(data) 将 C# 字符串转换为 UTF-16 字节的原因。