要谨慎使用Indy的TIdHashMessageDigest类

本文介绍了一个Delphi实现的Base64编码和解码函数,并通过MD5哈希验证其正确性。作者发现了Indy库中TIdHashMessageDigest5类的一个Bug,该Bug导致对文件部分进行哈希时出现错误。

     前几天,本人写了几个Delphi的Base64转换函数,并写了一篇博客文章《Delphi版的Base64转换函数 》,该文中,本人用Indy中的TIdHashMessageDigest5类,通过MD5的Hash验证Base64编码和解码的正确性。事后,本人想验证文件的Base64编码和解码,并重新写了Base64转换函数,对整个文件流的Base64编、解码无疑也是正确的,见以下代码(其中的Base64Encode和Base64Decode函数是重新写的对流的Base64操作):

 

var
  Source, Dest: TStream;
  md5: TIdHashMessageDigest5;
  Value: T4x4LongWordRecord;
  s1, s2: 
string;
begin
  
if OpenDialog1.Execute then
  begin
    md5 :
= TIdHashMessageDigest5.Create;
    
try
      Source :
= TFileStream.Create(OpenDialog1.FileName, fmOpenRead); // 打开源文件
      Value := md5.HashValue(Source);                                 // 取得源文件Hash
      s1 := md5.AsHex(Value);
      Dest :
= TFileStream.Create('Base64EncodeTest.txt', fmCreate);   // Base64编码文件
      try
        Base64Encode(Source, Dest);
      
finally
        Source.Free;
        Dest.Free;
      end;
      Source :
= TFileStream.Create('Base64EncodeTest.txt', fmOpenRead);// 打开编码文件
      Dest := TFileStream.Create('Base64DecodeTest.txt', fmCreate);    // 还原源文件内容
      try
        Base64Decode(Source, Dest);
        Dest.Position :
= 0;
        Value :
= md5.HashValue(Dest);                                  // 取得还原文件Hash
        s2 := md5.AsHex(Value);
      
finally
        Source.Free;
        Dest.Free;
      end;
    
finally
      md5.Free;
    end;
    
// 显示源文件和还原文件的Hash十六进制字串,如果相等,证明编码和解码过程正确
    ShowMessage(s1 + #10 + s2);
  end;
end;

    为了反复验证,又决定对流的部分内容进行验证,在代码Value := md5.HashValue(Source);前加了Source.Position := 100,代码Base64Encode(Source, Dest)改为Base64Encode(Source, Dest, 100);(该函数有2个默认参数,可定义起始位置和长度)

表示从流的100字节开始MD5Hash和Base64编码,可是无论如何2次Hash不相等,而截取的文件内容和长度无疑是正确的,通过反复调试,最后发现问题在TIdHashMessageDigest5类上,请看Indy的TIdHashMessageDigest5类的源码片断:

 

function TIdHashMessageDigest4.HashValue(AStream: TStream): T4x4LongWordRecord;
Var
  LStartPos: Integer;
  LBitSize,
  LSize: Int64;
  S: String;
  S1: String;
  LFillSize : Integer;
begin
  LStartPos :
= AStream.Position;
  LSize :
= AStream.Size - LStartPos;

  FBuffer :
= MD4_INIT_VALUES;

  
while LSize - AStream.Position >= SizeOf(FCBuffer) do
  begin
    AStream.Read(FCBuffer[
0], SizeOf(FCBuffer));
    MDCoder;
  end;

  
// Ensure S1 has sufficient size to hold a complete 64-byte chunk
  SetLength(S1, SizeOf(FCBuffer));

  
// Read the last set of bytes.
  LStartPos := AStream.Read(S1[1], 64);
  
// Now adjust S1 to only hold the last set of bytes.
  SetLength(S1, LStartPos);
  (后面的代码略)

    问题出在while LSize - AStream.Position >= SizeOf(FCBuffer) do上,函数中,LSize := AStream.Size - LStartPos计算的是流的剩余长度,如果LStartPos=0,则LSize为整个流的长度,这时运行是正确的,否则如果LSartPos<>0则是错误的!我测试时的流长度为1565,AStream.Position=100(即LStartPos=100),LSize=1465,正确的读法应该是每次读64字节(Sizeof(FCBuffer)=64),读22次后,余57字节。而按上面循环条件读21次后,AStream.Position=21*64+100=1444,循环结束,余21字节,这就不正确了,而且当运行LStartPos := AStream.Read(S1[1], 64)时,由于流长度为1565,流的当前位置AStream.Position=1444,剩121字节,所以读出的字节还是64,更是错上加错!

    我又在Delphi2007中看了Indy9和Indy10的源码,虽代码有所改动,但循环原理一样,由于MD5继承Md4类,所以TIdHashMessageDigest4也是错误的,而且TIdHashMessageDigest2也是一样!我将代码稍稍改了一下:LSize := AStream.Size - LStartPos;改为LSize := AStream.Size; LBitSize := (AStream.Size - AStream.Position) * 8,后面的LBitSize计算句删掉,重新运行,结果就正确了!

    另外,即使上面改正正确了,但该类的流操作函数还是有缺陷的,只能定义流的开始位置,不能定义结束位置或者读的长度。Indy10的Md5(md4)函数修改了参数,理应可以定义读的长度,不料看了一下代码,那个参数根本就没有使用。

    所以,在此提醒大家,一般使用Indy的Hash类是没有问题的,特殊操作就不行了,这种BUG,开发者们这些年都没发现,真是吃惊!为了方便和正确起见,我只好着手自己写这些类了。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值