delphi THashSHA2在巨大的文件上返回错误的SHA256

ar0*_*968 7 delphi amazon-s3

Data.Cloud.CloudAPI.pasclass function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;这回错了SHA 256上的一些文件.

class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
  LBytes : TBytes;
  Hash: THashSHA2;
begin
  LBytes := TBytesStream(Content).Bytes;
  //Hash bytes
  Hash := THashSHA2.Create;
  Hash.Update(LBytes);
  Result := Hash.HashAsString;
end;
Run Code Online (Sandbox Code Playgroud)

AWS S3返回错误:

提供的x-amz-content-sha256标头与计算的不匹配

GetStreamToHashSHA256Hex似乎与亚马逊产生了不同的sha256:

<ClientComputedContentSHA256>f43ee89e2b7758057bb1f33eb8546d4c2c118f2ab932de89dbd74aabc0651053</ClientComputedContentSHA256>
<S3ComputedContentSHA256>3bbf5f864cc139cf6392b4623bd782a69d16929db713bffaa68035f8a5c3c0ce</S3ComputedContentSHA256>
Run Code Online (Sandbox Code Playgroud)

我用myfile.zip做了一些测试(600 MB)...

TIdHashSHA256 Indy的另一种选择返回正确的SHA256(与aws s3相同),例如:

var
  aFileStream: TFileStream;
  aHash:       TIdHashSHA256;
begin
  aFileStream := TFileStream.Create('C:\myfile.zip', fmOpenRead or fmShareDenyWrite);
  aHash       := TIdHashSHA256.Create;

  try
    Result := aHash.HashStreamAsHex(aFileStream).ToLower;
  finally
     aFileStream.Free;
     aHash.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

hash_file() 从PHP返回正确的SHA256(与aws s3相同),例如:

hash_file('sha256', 'C:\myfile.zip');
Run Code Online (Sandbox Code Playgroud)

但THashSHA2返回错误的sha256,例如:

var
  LBytes : TBytes;
  Hash: THashSHA2;
begin
  LBytes := TFile.ReadAllBytes('C:\myfile.zip');
  Hash := THashSHA2.Create;
  Hash.Update(LBytes);
  Result := Hash.HashAsString;
end;
Run Code Online (Sandbox Code Playgroud)

为什么?

UPDATE

这是我的错误修复.将Data.Cloud.CloudAPI.pas导入项目并重写这些函数:

uses IdHash, IdHashSHA, IdSSLOpenSSL;

class function TCloudSHA256Authentication.GetHashSHA256Hex( HashString: string): string;
var
  aHash: TIdHashSHA256;
begin
  LoadOpenSSLLibrary;
  try
    if not(TIdHashSHA256.IsAvailable) then
      raise Exception.Create('HashSHA256 Isn''t available!');

    aHash := TIdHashSHA256.Create;
    try
      Result := aHash.HashStringAsHex(HashString).ToLower;
    finally
      aHash.Free;
    end;
  finally
    UnLoadOpenSSLLibrary;
  end;
end;

class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
  aHash: TIdHashSHA256;
begin
  LoadOpenSSLLibrary;
  try
    if not(TIdHashSHA256.IsAvailable) then
      raise Exception.Create('HashSHA256 Isn''t available!');

    aHash := TIdHashSHA256.Create;
    try
      Result := aHash.HashStreamAsHex(Content).ToLower;
    finally
      aHash.Free;
    end;
  finally
    UnLoadOpenSSLLibrary;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

更新2

我也尝试实施FredS建议,它的工作原理是:

class function TCloudSHA256Authentication.GetHashSHA256Hex( HashString: string): string;
var
  Content: TStringStream;
  Hash: THashSHA2;
  LBytes: TArray<Byte>;
  Buffer: PByte;
  BufLen: Integer;
  Readed: Integer;
begin
  BufLen := 16 * 1024;

  Buffer  := AllocMem(BufLen);
  Hash    := THashSHA2.Create;
  Content := TStringStream.Create(HashString);
  try
    while Content.Position < Content.Size do
    begin
      Readed := Content.Read(Buffer^, BufLen);
      if Readed > 0 then
        Hash.update(Buffer^, Readed);
    end;
  finally
    Content.Free;
    FreeMem(Buffer);
  end;

  Result := Hash.HashAsString;
end;

class function TCloudSHA256Authentication.GetStreamToHashSHA256Hex(const Content: TStream): string;
var
  LBytes : TBytes;
  Hash: THashSHA2;
  Buffer: PByte;
  BufLen: Integer;
  Readed: Integer;
begin
  BufLen := 16 * 1024;

  Buffer := AllocMem(BufLen);
  Hash   := THashSHA2.Create;
  try
      Content.Seek(0, soFromBeginning);

      while Content.Position < Content.Size do
      begin
        Readed := Content.Read(Buffer^, BufLen);
        if Readed > 0 then
          Hash.update(Buffer^, Readed);
      end;

      Content.Seek(0, soFromBeginning);
  finally
      FreeMem(Buffer);
  end;

  Result := Hash.HashAsString;
end;
Run Code Online (Sandbox Code Playgroud)

Fre*_*edS 5

我刚刚在柏林使用MS Cyrpto和THashSHA2测试了一个+1.5 GB的文件,它们都返回了相同的哈希,但是像OpenSSL这样的MS Crypto更快。

问题在于文件太大,无法一整块地容纳TBytes。我的记录助手有,TBytes.MaxLen = $F000; {61440}因此您需要使用TFileStream并将文件分块读取到HashSHA2.Update中。

更新: 根据David Heffernan的评论,我重新测试了TBytes.MaxLen,它似乎仅受可用内存的限制。


MS Crypto和Delphi HashSha2之间的实例和速度比较

注意:需要Jedi API

  program SHA2SpeedTest;

  {$APPTYPE CONSOLE}
  {$R *.res}

  uses
    JwaWindows, Winapi.Windows, System.SysUtils, System.Classes, System.Diagnostics, System.Hash;

  const
    SHA256_LEN    = 256 div 8;
    ChunkSize     = $F000;

  type
    TBytesHelper = record helper for TBytes
    public
      function BinToHex: string;
    end;

  function TBytesHelper.BinToHex: string;
  var
    Len : Integer;
  begin
    Len := Length(Self);
    SetLength(Result, Len * 2));
    System.Classes.BinToHex(Self, PChar(Result), Len);
  end;

  procedure DelphiHash256(const AStream: TStream; out Bytes: TBytes);
  var
    HashSHA2: THashSHA2;
    BytesRead: Integer;
  begin
    HashSHA2 := THashSHA2.create;
    SetLength(Bytes, ChunkSize);
    AStream.Position := 0;
    repeat
      BytesRead := AStream.Read(Bytes, ChunkSize);
      if (BytesRead = 0) then Break; // Done
      HashSHA2.Update(Bytes, BytesRead);
    until False;
    Bytes := HashSHA2.HashAsBytes;
  end;


  function CryptoHash256(const AStream: TStream; out Bytes: TBytes): Boolean;
  var
    SigLen   : Cardinal;
    hHash    : HCRYPTHASH;
    hProv    : HCRYPTPROV;
    BytesRead: Integer;
  begin
    hProv  := 0; hHash  := 0;
    Result := False;

    If not CryptAcquireContext(hProv, nil, nil, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) then Exit;
    try
      if not CryptCreateHash(hProv, CALG_SHA_256, 0, 0, hHash) then Exit;
      try
        SetLength(Bytes, ChunkSize);
        AStream.Position := 0;
        repeat
          BytesRead := AStream.Read(Bytes, ChunkSize);
          if (BytesRead = 0) then Break; // Done
          if not CryptHashData(hHash, @Bytes[0], BytesRead, 0) then Exit;
        until False;

        SigLen := SHA256_LEN;
        SetLength(Bytes, SigLen);
        Result := CryptGetHashParam(hHash, HP_HASHVAL, @Bytes[0], SigLen, 0);
      finally
        CryptDestroyHash(hHash);
      end;
    finally
      CryptReleaseContext(hProv, 0);
    end;
  end;

  var
    Stream: TStream;
    Bytes : TBytes;
    sw    : TStopwatch;
    CryptoTicks : int64;
    FileName : string;

    {* CheckFileName *}
    function CheckFileName: boolean;
    begin
      if (FileName='') then FileName := ParamStr(0);
      Result := FileExists(FileName);
      if not Result then Writeln('Invalid File name');
    end;
  begin
    repeat
      Writeln('Please Enter a valid File name, empty for this Executable');
      Readln(FileName);
    until CheckFileName;

    try
      Stream := TFileStream.Create(FileName, fmOpenRead + fmShareDenyNone);
      try
        WriteLn('Crypto - Calculating Checksum');
        sw.Start;
        if not CryptoHash256(Stream, Bytes) then raise Exception.Create('Something Happened :)');
        sw.Stop;
        Writeln(Bytes.BinToHex);
        WriteLn('Elapsed: ' + sw.Elapsed.ToString);
        CryptoTicks := sw.ElapsedTicks;

        WriteLn('Delphi - Calculating Checksum');
        sw.Reset; sw.Start;
        DelphiHash256(Stream, Bytes);
        sw.Stop;
        Writeln(Bytes.BinToHex);
        WriteLn('Elapsed: ' + sw.Elapsed.ToString);

        Writeln(Format('MS Crypto is %d%% faster', [(sw.ElapsedTicks-CryptoTicks) * 100 div CryptoTicks]));
      finally
        Stream.Free;
      end;
      Writeln('Hit <Enter> to exit');
      Readln;
    except
      on E: Exception do Writeln(E.ClassName, ': ', E.Message);
    end;

  end.
Run Code Online (Sandbox Code Playgroud)

  • 该代码的其他问题包括将难以理解的代码强制转换为TBytesStream。对于Emba来说,质量对他们而言至关重要。 (5认同)