Delphi 2009中的MD5哈希处理

7

在Borland Delphi 7甚至Delphi 2007中,一切都正常工作,但在Delphi 2009中它只返回错误的哈希值!

我使用wcrypt2脚本(http://pastebin.com/m2f015cfd

看一下:

字符串:"123456"

哈希:

Delphi 7:"e10adc3949ba59abbe56e057f20f883e" - 真实哈希值。
Delphi 2007:"e10adc3949ba59abbe56e057f20f883e" - 真实哈希值。
而... Delphi 2009:"5fa285e1bebe0a6623e33afc04a1fbd5" - 神奇??

我尝试了很多MD5脚本,但Delphi 2009对它们所有的脚本都是一样的。有什么帮助吗?谢谢。

7个回答

31

您的库不支持Unicode。仅仅传递一个 AnsiString 并不足够,因为它可能在内部使用字符串来存储数据。

您可以尝试更新该库,等待作者更新,或者只需使用随 Delphi 2009 发送的 MessageDigest_5.pas。它位于 source\Win32\soap\wsdlimporter 文件夹中,您需要将其添加到路径中或显式地包含在您的项目中。

以下是在 Delphi 2009 中使用它的一些示例代码:

uses Types, MessageDigest_5;

procedure TForm16.Edit1Change(Sender: TObject);
var
  MD5: IMD5;
begin
  MD5 := GetMD5;
  MD5.Init;
  MD5.Update(TByteDynArray(RawByteString(Edit1.Text)), Length(Edit1.Text));
  Edit2.Text := LowerCase(MD5.AsString);
end;

现在您可以开展业务了:

MD5(123456) = e10adc3949ba59abbe56e057f20f883e

如果需要,您可以将其封装为一个简单的函数调用。重要的是在强制转换为RawByteString之前先强制转换为TByteDynArray,因为RawByteString强制转换会删除所有额外的Unicode字符。但如果编辑包含Unicode字符,则可能会导致出现错误的数据。

请记住,GetMD5返回一个接口,因此它是引用计数的。

圣诞快乐!


1
这里的转换完全是虚假的。你不能合理地将UnicodeString强制转换为RawByteString。而且你绝对不能将RawByteString转换为字节数组。这个答案非常错误,投票也是错误的,应该直接删除。正确的解决方案是使用TEncoding.GetBytes。 - David Heffernan

5

在评论哈希算法之前,最好先了解底层概念和原则。目前为止,所有关注无尽类型转换的回复都是过度杀伤力的,更糟糕的是,如果哈希Unicode字符串,将会导致不可靠的结果。

首先,你需要了解的是哈希和加密算法是以字节级别操作的。这意味着它们不关心你要哈希或加密的内容。你可以哈希整数、字符、纯ASCII、完整的Unicode、字节、长字等等。算法不关心。

在处理字符串时,你唯一需要确保的是哈希库的内部函数在输出结果哈希的函数中返回AnsiString。就是这样。这是唯一重要的事情。

你实际项目的代码可以(也应该)基于普通字符串输入,这映射到Delphi 2009中的unicodestring。你不应该将任何东西强制转换为ansistring或rawbytestring。这么做,如果用户尝试哈希超出ANSI字符集范围的任何内容,你将立即创建一个错误的哈希。在哈希世界中,错误的哈希既不可靠又不安全。


谢谢你的理性发言。+1。 - mghie

1

你检查过你的库是否已经正确更新到D2009和Unicode吗?我有点怀疑同样的代码能够在D7/D2007和D2009上处理这种事情。


1

显然你的库不支持Unicode。

通过声明temp AnsiString并将您的Unicode字符串分配给它,将您的字符串转换为AnsiString或RawByteString或UTF8String。

请注意,如果您正在使用无法转换为单个代码页的Unicode特定字符,则应将字符串转换为UTF8。

然后调用MD5(PAnsiChar(YourTempString))。

检查您的库是否具有PWideChar或UNICODE声明,以跳过此步骤。


1
如果您有 wcrypt2.pas,请使用此函数。
function md5ansi(const Input: AnsiString): AnsiString;
var
  hCryptProvider: HCRYPTPROV;
  hHash: HCRYPTHASH;
  bHash: array[0..$7f] of Byte;
  dwHashBytes: Cardinal;
  pbContent: PByte;
  i: Integer;
begin
  dwHashBytes := 16;
  pbContent := Pointer(PAnsiChar(Input));
  Result := '';

  if CryptAcquireContext(@hCryptProvider, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT or CRYPT_MACHINE_KEYSET) then
  begin
    if CryptCreateHash(hCryptProvider, CALG_MD5, 0, 0, @hHash) then
    begin
      if CryptHashData(hHash, pbContent, Length(Input) * sizeof(AnsiChar), 0) then
      begin
        if CryptGetHashParam(hHash, HP_HASHVAL, @bHash[0], @dwHashBytes, 0) then
        begin
          for i := 0 to dwHashBytes - 1 do
          begin
            Result := Result + AnsiString(Format('%.2x', [bHash[i]]));
          end;
        end;
      end;
      CryptDestroyHash(hHash);
    end;
    CryptReleaseContext(hCryptProvider, 0);
  end;

  Result := AnsiString(AnsiLowerCase(String(Result)));
end;

@JW Kim,欢迎来到StackOverflow。请花点时间阅读faq。如果您以前有论坛或新闻组的经验,您会注意到该网站的工作方式有所不同。您正在回答一个相当旧的问题,且它已被采纳,因此虽然有效,但不再使用。要发布源代码,您必须将其缩进4个空格字符以使其看起来像源代码。 :) - jachguate

0
你是否偶然将通用字符串(在Delphi 2009中为UnicodeString)转换为PAnsiChar并将其传递到哈希函数中?这样是行不通的。你必须先将字符串转换为AnsiString,然后再将其转换为PAnsiChar,如下所示:
PAnsiChar(AnsiString('123456'))

此外,尝试使用RawByteString而不是AnsiString,就像dmajkic建议的那样。避免使用UTF8String,因为它不是一个AnsiString,任何ASCII范围(0..127)之外的字符可能会被重新解释成多字节字符。

-3
在Jim的回答中:
如果我们将
MD5.Update(TByteDynArray(RawByteString(Edit1.Text)), Length(Edit1.Text));
改为
MD5.Update(TByteDynArray(RawByteString(Edit1.Text)), Length(RawByteString(Edit1.Text)));
将会更好地支持中文字符。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接