将C#哈希代码迁移到PHP

时间:2012-02-05 12:18:21

标签: c# php hash

我知道SO上已经有类似的问题,但似乎没有一个问题可以解决这个问题。我继承了以下用于在遗留.net应用程序中创建密码哈希的c#代码,由于各种原因,C#实现现在正在迁移到php:

string input = "fred";
SHA256CryptoServiceProvider provider = new SHA256CryptoServiceProvider();
byte[] hashedValue = provider.ComputeHash(Encoding.ASCII.GetBytes(input));
string output = "";
string asciiString = ASCIIEncoding.ASCII.GetString(hashedValue);
foreach ( char c in asciiString ) {
   int tmp = c;
   output += String.Format("{0:x2}", 
             (uint)System.Convert.ToUInt32(tmp.ToString()));
}
return output;

我的PHP代码非常简单,但对于相同的输入,“fred”不会产生相同的结果:

$output = hash('sha256', "fred");

我已将问题追溯到编码问题 - 如果我在C#代码中更改此行:

string asciiString = ASCIIEncoding.ASCII.GetString(hashedValue);

string asciiString = ASCIIEncoding.UTF7.GetString(hashedValue);

然后php和C#输出匹配(它产生d0cfc2e5319b82cdc71a33873e826c93d7ee11363f8ac91c4fa3a2cfcd2286e5)。

由于我无法更改.net代码,我需要弄清楚如何在php中复制结果。

提前感谢您的帮助,

3 个答案:

答案 0 :(得分:3)

我不太了解PHP以回答你的问题;但是,我必须指出你的C#代码已被破坏。尝试生成这两个输入的哈希:"âèí""çñÿ"。你会发现他们的哈希冲突:

3f3b221c6c6e3f71223f51695d456d52223f243f3f363949443f3f763b483615

第一个错误在于此操作:

Encoding.ASCII.GetBytes(input)

这假设input中的所有字符都是US-ASCII。任何非ASCII字符都会导致编码器回退到?字符的字节值,从而产生(不需要的)哈希冲突,如上所示。尽管如此,如果您的输入仅限于允许US-ASCII字符,则不会出现问题。

另一个(更严重的)错误在于以下操作:

ASCIIEncoding.ASCII.GetString(hashedValue)

ASCII仅定义值0-127的映射。由于hashedValue字节数组的元素可能包含任何字节值(0-255),因此将它们编码为ASCII会导致数据在遇到大于127的值时丢失。这可能导致进一步的“不需要的”(读取:可能恶意生成)哈希冲突,即使您的原始输入是US-ASCII。

从统计上看,构成哈希值的一半字节大于127,那么你的至少哈希算法强度的一半。如果黑客获得了对存储的哈希的访问权限,那么他们很可能会通过利用这个加密弱点来设法攻击以产生哈希冲突。

编辑:尽管我的帖子和Jon提到了这些考虑因素,但这里的PHP代码却屈服于相同的弱点 - 可以说 - 作为您的C#代码,从而提供相同的哈希:

$output = hash('sha256', $input, true);

for ($i = 0; $i < strlen($output); $i++)
   if ($output[$i] > chr(127))
       $output[$i] = '?';

$output = bin2hex($output);

答案 1 :(得分:1)

您是否可以使用mb_convert_encoding(请参阅http://php.net/manual/en/function.mb-convert-encoding.php - 该页面还有指向支持编码列表的链接)将PHP字符串从UTF7转换为ASCII?

答案 2 :(得分:1)

  

我已将问题追溯到编码问题

是。您正在尝试将任意二进制数据视为有效的文本编码数据。 不是。您应该 在这里使用任何Encoding

如果您希望结果为十六进制,最简单的方法是使用BitConverter.ToString

string text = BitConverter.ToString(hashedValue).Replace("-", "").ToLower();

是的,正如其他地方所指出的那样,你可能不应该在散列过程开始时使用ASCII将文本转换为二进制文件。我可能会使用UTF-8。

非常重要的是你在这里理解这个问题,否则你也会在其他地方遇到它。当您真正获得编码的文本数据时,您应使用ASCII,UTF-8等编码(在任何平台上)。您不应该将它们用于图像,加密结果,散列结果等。

编辑:好的,你说你不能改变C#代码......目前尚不清楚这是否意味着你有遗留的数据,或者你是否需要继续使用C#代码不管。您应该绝对不要运行此代码一秒钟。

但是在PHP中,可能发现只需用0x3F(即“问号”的ASCII)替换哈希值为&gt; = 0x80的每个字节即可。如果你查看数据,你可能会发现那里有3F字节的批次

如果你可以让它运行起来,我强烈建议您迁移到 true MD5哈希,而不会丢失这样的信息。无论你在哪里存储哈希,都要存储两个:遗留的(现在就是你所拥有的)和重新存储的一个。每当要求您验证密码是否正确时,您应该:

  • 检查您是否有“新”的;如果是这样,只使用它 - 忽略遗留的那个。
  • 如果你只有一个遗产:
    • 以破碎的方式哈希密码以检查密码是否正确
    • 如果是,请再次哈希正确并将结果存储在“新”位置。

然后当每个人都正确登录一次后,您就可以消除遗留的哈希值。