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

Data.Cloud.CloudAPI.pas具有class 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;

AWS S3返回错误:

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

GetStreamToHashSHA256Hex似乎从亚马逊产生了一个不同的sha256:

<ClientComputedContentSHA256>f43ee89e2b7758057bb1f33eb8546d4c2c118f2ab932de89dbd74aabc0651053</ClientComputedContentSHA256>
<S3ComputedContentSHA256>3bbf5f864cc139cf6392b4623bd782a69d16929db713bffaa68035f8a5c3c0ce</S3ComputedContentSHA256>

我用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;

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

hash_file('sha256', 'C:myfile.zip');

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

var
  LBytes : TBytes;
  Hash: THashSHA2;
begin
  LBytes := TFile.ReadAllBytes('C:myfile.zip');
  Hash := THashSHA2.Create;
  Hash.Update(LBytes);
  Result := Hash.HashAsString;
end;

为什么?

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;

更新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;

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

问题在于该文件太大而无法在一个块中保存TBytes。 我的记录帮手有TBytes.MaxLen = $F000; {61440} 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.
链接地址: http://www.djcxy.com/p/96025.html

上一篇: delphi THashSHA2 return a wrong SHA256 on huge file

下一篇: passing environment variables to angular2 library