Delphi/Pascal/FreePascal中用于短字符串的MD5

3

我正在尝试为短字符串(长度小于64个字节)实现简单的MD5加密。

我使用了维基百科上的算法(链接)

一切都编译成功了,但对于以下字符串,我的结果是:

"hello world" 

是:

BB3BB65ED0EE1EE0BB22CB93C3CD5A8F

应该是这样的:

5EB63BBBE01EEED093CB22BB8F5ACDC3

完整的代码在这里:
program Prog;

uses Classes, SysUtils;

function leftrotate(x, c: Cardinal): Cardinal;
begin
  leftrotate := (x shl c) or (x shr (32-c));
end;

const s: array[0..63] of Cardinal = (
    7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,  7, 12, 17, 22,
    5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,  5,  9, 14, 20,
    4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,  4, 11, 16, 23,
    6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21,  6, 10, 15, 21 );
K: array[0..63] of Cardinal = (
    $d76aa478, $e8c7b756, $242070db, $c1bdceee,
    $f57c0faf, $4787c62a, $a8304613, $fd469501,
    $698098d8, $8b44f7af, $ffff5bb1, $895cd7be,
    $6b901122, $fd987193, $a679438e, $49b40821,
    $f61e2562, $c040b340, $265e5a51, $e9b6c7aa,
    $d62f105d, $02441453, $d8a1e681, $e7d3fbc8,
    $21e1cde6, $c33707d6, $f4d50d87, $455a14ed,
    $a9e3e905, $fcefa3f8, $676f02d9, $8d2a4c8a,
    $fffa3942, $8771f681, $6d9d6122, $fde5380c,
    $a4beea44, $4bdecfa9, $f6bb4b60, $bebfbc70,
    $289b7ec6, $eaa127fa, $d4ef3085, $04881d05,
    $d9d4d039, $e6db99e5, $1fa27cf8, $c4ac5665,
    $f4292244, $432aff97, $ab9423a7, $fc93a039,
    $655b59c3, $8f0ccc92, $ffeff47d, $85845dd1,
    $6fa87e4f, $fe2ce6e0, $a3014314, $4e0811a1,
    $f7537e82, $bd3af235, $2ad7d2bb, $eb86d391 );

var a0,b0,c0,d0, a,b,c,d, f,g,dTemp: Cardinal;
   Len: Integer;
   Msg: array[0..63] of Char;
   M: array[0..15] of Cardinal absolute Msg; //break chunk into sixteen 32-bit words M[j]
   Str: String;
   i: Integer;
   ff: TFileStream;
   wait: Char;
begin
  a0 := $67452301;
  b0 := $efcdab89;
  c0 := $98badcfe;
  d0 := $10325476;

  Str := 'hello world';
  Len := Length(Str);

  FillChar(Msg, 64, 0);

  for i:=1 to Len do Msg[i-1] := Str[i];

//append "1" bit to message
  Msg[Len] := chr(128);

//append original length in bits mod (2 pow 64) to message
  Msg[63-7] := chr(8*Len);  //Update thanks to @MBo

//Process each 512-bit chunk of message- 1 only have 1 chunk

//TEST dump
//  ff := TFileStream.create('test.txt', fmCreate);
//  ff.write(msg, 64);
//  ff.free;

//Initialize hash value for this chunk:
    A := a0;
    B := b0;
    C := c0;
    D := d0;

//Main loop:
    for i := 0 to 63 do begin

        if (i>=0) and (i<=15) then begin
            F := (B and C) or ((not B) and D);
            g := i;
        end
        else if (i>=16) and (i<=31) then begin
            F := (D and B) or ((not D) and C);
            g := (5*i + 1) mod 16;
        end
        else if (i>=32) and (i<=47) then begin
            F := B xor C xor D;
            g := (3*i + 5) mod 16;
        end
        else if (i>=48) and (i<=63) then begin
            F := C xor (B or (not D));
            g := (7*i) mod 16;
        end;

        dTemp := D;
        D := C;
        C := B;
        B := B + leftrotate((A + F + K[i] + M[g]), s[i]);
        A := dTemp;
    end;

//Add this chunk's hash to result so far:
  a0 := a0 + A;
  b0 := b0 + B;
  c0 := c0 + C;
  d0 := d0 + D;

  //This should give 5EB63BBBE01EEED093CB22BB8F5ACDC3
  Writeln( IntToHex(a0,8) + IntToHex(b0,8) + IntToHex(c0,8)  +IntToHex(d0,8) );

  Readln(wait);
end.

您可以在此在线尝试代码: http://ideone.com/qdYQ6q 这是我的准备工作块的转储,就在主循环之前(test.txt):

当然,你应该将这个放入一个函数中以便重复使用。而且你真的把输入当作文本来处理了吗?你必须将输入视为二进制。 - David Heffernan
@DavidHeffernan 我把它放成一个程序,这样任何人都可以轻松测试,并且可以在ideaone.com上运行,但它将是一个函数。 是的,它必须使用文本(字符串),因为我将用它来哈希电子邮件、登录和密码-仅短字符串。 - Tom
你理解错了。哈希算法是基于二进制操作的。首先选择一个文本编码,然后使用该编码将文本编码为二进制。例如,你可以选择UTF-8编码。 - David Heffernan
@DavidHeffernan 二进制编码已经存在: M: array[0..15] of Cardinal absolute Msg; 而输入字符串为 ANSI。 - Tom
2
现在你有的代码只能处理 ANSI 文本,这相当受限。你不能为文件或流计算哈希值等等。而且你还在重新发明轮子。市面上已经有很多优秀的哈希实现了。 - David Heffernan
显示剩余4条评论
3个回答

5
最后一步是错误的:
  a0 := a0 + A;
  b0 := b0 + B;
  c0 := c0 + C;
  d0 := d0 + D;

这应该改变字节序:

  a0 := Swap32(a0 + A);
  b0 := Swap32(b0 + B);
  c0 := Swap32(c0 + C);
  d0 := Swap32(d0 + D);

function Swap32(ALong: Cardinal): Cardinal; Assembler; 
asm 
  BSWAP eax 
end;

然后就好了。


1
一个小提示... 在维基百科的伪代码中有一行 //Note: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating,所以这些 Longint 不应该改为 DWord 吗? - Rik
@Rik 在这里我们主要使用二进制运算,并且它与 Longints 很好地配合。但感谢您的提示,我会更改代码以使用 Cardinals 来确保它是正确的。 - Tom
@Rik DWord不是Delphi数据类型,而是Winapi的一种。在Delphi下,Uint32可以是Cardinal或Longword。我曾经在某个地方读到过这些都是别名,并且在其他平台上永远不会改变。 - Tom
2
可以相当肯定地认为Cardinal将永远在所有地方都是32位的。然而,如果你对此感到不安,那么请使用LongWord - David Heffernan
1
相比使用DWord、Longword或Cardinal,建议使用明确的UInt32。我期望它能够映射到适当的类型,无论在哪个平台上。我不确定Cardinal是否总是32位。毕竟,在旧时代,它只有16位。 - Rudy Velthuis
显示剩余4条评论

3
你可能会考虑使用第三方实现,而不是创建自己的实现。例如,Indy的TIdHashMessageDigest5类可以产生正确的值,例如:
uses
  ..., IdHashMessageDigest;

var
  S: string;
begin
  with TIdHashMessageDigest5.Create do
  try
    S := HashStringAsHex('hello world'); // returns '5EB63BBBE01EEED093CB22BB8F5ACDC3'
  finally
    Free;
  end;
end;

2
同样地... FPC有一个单元MD5,你可以使用它。(这个问题附有freepascal标签) - Rik
在Delphi中也有一个名为MessageDigest_5的单元。 - LU RD

2
这些步骤怎么样呢:
append "0" bit until message length in bits ≡ 448 (mod 512)

(56个字节,64+56等)

将原始长度以位为单位追加到消息中 mod (2 pow 64)

但您只是将长度以字节为单位追加到了消息中

P.S. 我已经用 Delphi 检查了您的最后一个版本。我将 char 类型更改为 AnsiChar,结果与预期一致。 请注意,正确的二进制结果不需要字节交换。这只有助于从 Int32 值构造十六进制字符串。

在 Intel 硬件上,Int32 已经使用小端序,因此 BB3BB65E(十六进制表示)对应的字节序列为 5E B6 3B BB 等。


1
BitsLen必须在第56个字节中。 - MBo

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