要謹慎使用Indy的TIdHashMessageDigest類

來源:互聯網
上載者:User

     前幾天,本人寫了幾個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,開發人員們這些年都沒發現,真是吃驚!為了方便和正確起見,我只好著手自己寫這些類了。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.