Browse Source

* System.Hash for Delphi compatibility

Michaël Van Canneyt 1 year ago
parent
commit
fe243540a8

+ 4 - 0
packages/vcl-compat/fpmake.pp

@@ -34,6 +34,8 @@ begin
     P.Dependencies.Add('rtl-objpas'); 
     P.Dependencies.Add('rtl-generics');
     P.Dependencies.Add('fcl-json');
+    P.Dependencies.Add('fcl-hash');
+    P.Dependencies.Add('hash');
     P.SourcePath.Add('src');
     P.IncludePath.Add('src');
 
@@ -57,6 +59,8 @@ begin
     T.ResourceStrings := True;
     T.Dependencies.AddUnit('system.messaging');
     T.Dependencies.AddUnit('system.json');
+    T:=P.Targets.AddUnit('system.hash.pp');
+    T.ResourceStrings := True;
 
 
 {$ifndef ALLPACKAGES}

+ 1164 - 0
packages/vcl-compat/src/system.hash.pp

@@ -0,0 +1,1164 @@
+{
+    This file is part of the Free Component Library (FCL)
+    Copyright (c) 2023 the Free Pascal team
+
+    Delphi-compatible hash unit
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+unit System.Hash;
+
+{$mode objfpc}
+{$modeswitch advancedrecords}
+{$macro on}
+
+interface
+
+uses
+{$IFDEF FPC_DOTTEDUNITS}
+  System.Classes, System.SysUtils, System.Hash.Md5, System.Hash.Sha1, System.Hash.Fnv, System.Hash.Sha256, System.Hash.Sha512;
+{$ELSE}
+  Classes, SysUtils, md5, sha1, fnvhash, fpsha256, fpsha512;
+{$ENDIF}
+
+const
+  HashReadBufferSize = 4096; // Use 4k buffer.
+
+type
+  EHashException = class(Exception);
+
+  { THash }
+
+  THash = record
+    class function DigestAsInteger(const aDigest: TBytes): Integer; static;
+    class function DigestAsString(const aDigest: TBytes; UpperCase : Boolean = false): UnicodeString; static;
+    class function DigestAsStringGUID(const aDigest: TBytes): UnicodeString; static;
+    class function GetRandomString(const aLen: Integer = 10): UnicodeString; static;
+    class function ToBigEndian(aValue: Cardinal): Cardinal; overload; static; inline;
+    class function ToBigEndian(aValue: UInt64): UInt64; overload; static; inline;
+  end;
+
+  { THashMD5 }
+
+  THashMD5 = record
+  Private
+    _MD5 : TMD5Context;
+    _Digest : TMD5Digest;
+    _DidFinal : Boolean;
+  public
+    class function Create: THashMD5; static; inline;
+    class function GetHashBytes(const aData: UnicodeString): TBytes; overload; static;
+    class function GetHashString(const aData: UnicodeString): UnicodeString; overload; static; inline;
+    class function GetHashBytes(const aStream: TStream): TBytes; overload; static;
+    class function GetHashString(const aStream: TStream): UnicodeString; overload; static; inline;
+    class function GetHashBytesFromFile(const aFileName: TFileName): TBytes; static;
+    class function GetHashStringFromFile(const aFileName: TFileName): UnicodeString; static; inline;
+    class function GetHMAC(const aData,aKey: UnicodeString): UnicodeString; static; inline;
+    class function GetHMACAsBytes(const aData,aKey: UnicodeString): TBytes; overload; static;
+    class function GetHMACAsBytes(const aData: UnicodeString; const aKey: TBytes): TBytes; overload; static;
+    class function GetHMACAsBytes(const aData: TBytes; const aKey: UnicodeString): TBytes; overload; static;
+    class function GetHMACAsBytes(const aData,aKey: TBytes): TBytes; overload; static;
+    procedure Reset;
+    procedure Update(var aData; aLength: Cardinal); overload;
+    procedure Update(const aData: TBytes; aLength: Cardinal = 0); overload; {inline;}
+    procedure Update(const aData: UnicodeString); overload; {inline;}
+    function GetDigest: TBytes;
+    function GetBlockSize: Integer; inline;
+    function GetHashSize: Integer; inline;
+    function HashAsBytes: TBytes; inline;
+    function HashAsString: UnicodeString; // inline;
+  end;
+
+  { THashSHA1 }
+
+  THashSHA1 = record
+  private
+    _SHA1 : TSHA1Context;
+    _Digest : TSHA1Digest;
+    _DidFinal : Boolean;
+  public
+    class function Create: THashSHA1; static; // inline;
+    class function GetHashBytes(const aData: UnicodeString): TBytes; overload; static;
+    class function GetHashString(const aData: UnicodeString): UnicodeString; overload; static; // inline;
+    class function GetHashBytes(const aStream: TStream): TBytes; overload; static;
+    class function GetHashString(const aStream: TStream): UnicodeString; overload; static; // inline;
+    class function GetHashBytesFromFile(const aFileName: TFileName): TBytes; static;
+    class function GetHashStringFromFile(const aFileName: TFileName): UnicodeString; static; // inline;
+    class function GetHMAC(const aData, aKey: UnicodeString): UnicodeString; static; // inline;
+    class function GetHMACAsBytes(const aData, aKey: UnicodeString): TBytes; overload;  static;
+    class function GetHMACAsBytes(const aData: UnicodeString; const aKey: TBytes): TBytes; overload; static;
+    class function GetHMACAsBytes(const aData: TBytes; const aKey: UnicodeString): TBytes; overload; static;
+    class function GetHMACAsBytes(const aData, aKey: TBytes): TBytes; overload; static;
+    procedure Reset; inline;
+    procedure Update(var aData; aLength: Cardinal); overload;
+    procedure Update(const aData: TBytes; aLength: Cardinal = 0); overload;
+    procedure Update(const aData: UnicodeString); overload; // inline;
+    function GetDigest: TBytes;
+    function GetBlockSize: Integer; inline;
+    function GetHashSize: Integer; inline;
+    function HashAsBytes: TBytes; inline;
+    function HashAsString: UnicodeString; // inline;
+  end;
+
+  { THashSHA2 }
+
+  THashSHA2 = record
+  public type
+    TSHA2Version = (SHA224, SHA256, SHA384, SHA512, SHA512_224, SHA512_256);
+  public
+    class function Create(aHashVersion: TSHA2Version = TSHA2Version.SHA256): THashSHA2; static; inline;
+    class function GetHashBytes(const aData: UnicodeString; aHashVersion: TSHA2Version = TSHA2Version.SHA256): TBytes; overload; static;
+    class function GetHashString(const aData: UnicodeString; aHashVersion: TSHA2Version = TSHA2Version.SHA256): UnicodeString; overload; static; inline;
+    class function GetHashBytes(const aStream: TStream; aHashVersion: TSHA2Version = TSHA2Version.SHA256): TBytes; overload; static;
+    class function GetHashString(const aStream: TStream; aHashVersion: TSHA2Version = TSHA2Version.SHA256): UnicodeString; overload; static; inline;
+    class function GetHashBytesFromFile(const aFileName: TFileName; aHashVersion: TSHA2Version = TSHA2Version.SHA256): TBytes; static;
+    class function GetHashStringFromFile(const aFileName: TFileName; aHashVersion: TSHA2Version = TSHA2Version.SHA256): UnicodeString; static; inline;
+    class function GetHMAC(const aData, aKey: UnicodeString; aHashVersion: TSHA2Version = TSHA2Version.SHA256): UnicodeString; static; inline;
+    class function GetHMACAsBytes(const aData, aKey: UnicodeString; aHashVersion: TSHA2Version = TSHA2Version.SHA256): TBytes; overload;  static;
+    class function GetHMACAsBytes(const aData: UnicodeString; const aKey: TBytes; aHashVersion: TSHA2Version = TSHA2Version.SHA256): TBytes; overload; static;
+    class function GetHMACAsBytes(const aData: TBytes; const aKey: UnicodeString; aHashVersion: TSHA2Version = TSHA2Version.SHA256): TBytes; overload; static;
+    class function GetHMACAsBytes(const aData, aKey: TBytes; aHashVersion: TSHA2Version = TSHA2Version.SHA256): TBytes; overload; static;
+    procedure Reset; inline;
+    procedure Update(var aData; aLength: Cardinal); overload;
+    procedure Update(const aData : PByte; aLength: Cardinal); overload;
+    procedure Update(const aData: TBytes; aLength: Cardinal = 0); overload;
+    procedure Update(const aData: UnicodeString); overload;
+    procedure Update(const aData: RawByteString); overload;
+    function GetDigest: TBytes;
+    function GetBlockSize: Integer; inline;
+    function GetHashSize: Integer;  inline;
+    function HashAsBytes: TBytes; // inline;
+    function HashAsString: UnicodeString; // inline;
+  Private
+    procedure DoFinal;
+  private
+    FDidFinal : Boolean;
+    case FHashVersion: TSHA2Version of
+      Sha256 : (_S256 : TSHA256);
+      Sha384 : (_S384 : TSHA384);
+      Sha512 : (_S512 : TSHA512);
+  end;
+
+  { THashBobJenkins }
+
+  THashBobJenkins = record
+  Private
+    FCurrent : Cardinal;
+  public
+    class function Create: THashBobJenkins; static;
+    class function GetHashBytes(const aData: UnicodeString): TBytes; static;
+    class function GetHashString(const aData: UnicodeString): UnicodeString; static;
+    class function GetHashString(const aData: RawByteString): UnicodeString; static;
+    class function GetHashValue(const aData: UnicodeString): Integer; overload; static;
+    class function GetHashValue(const aData: RawByteString): Integer; overload; static;
+    class function GetHashValue(var aData; aLength: Integer; aInitialValue: Integer = 0): Integer; overload; static;
+    class function GetHashValue(const aData : PByte; aLength: Integer; aInitialValue: Integer = 0): Integer; overload; static;
+    procedure Reset(aInitialValue: Integer = 0);
+    procedure Update(var aData; aLength: Cardinal); overload;
+    procedure Update(aData : PByte; aLength: Cardinal); overload;
+    procedure Update(const aData: TBytes; aLength: Cardinal = 0); overload;
+    procedure Update(const aData: UnicodeString); overload;
+    procedure Update(const aData: RawByteString); overload;
+    function HashAsBytes: TBytes;
+    function HashAsInteger: Integer;
+    function HashAsString: UnicodeString;
+  end;
+
+  { THashFNV1a32 }
+
+  THashFNV1a32 = record
+  public const
+    FNV_PRIME = FNV_32_PRIME;
+    FNV_SEED  = FNV1_32_INIT;
+  private
+    FCurrent : Fnv32_t;
+  public
+    class function Create: THashFNV1a32; static;
+    class function GetHashBytes(const aData: UnicodeString): TBytes; static;
+    class function GetHashString(const aData: UnicodeString): UnicodeString; overload; static;
+    class function GetHashString(const aData: RawByteString): UnicodeString; overload; static;
+    class function GetHashValue(const aData: UnicodeString): Integer; overload; static; // inline;
+    class function GetHashValue(const aData: RawByteString): Integer; overload; static; // inline;
+    class function GetHashValue(const aData; aLength: Cardinal; aInitialValue: Cardinal = FNV_SEED): Integer; overload; static; // inline;
+    procedure Reset(aInitialValue: Cardinal = FNV_SEED);
+    procedure Update(const aData; aLength: Cardinal); overload; // inline;
+    procedure Update(const aData: TBytes; aLength: Cardinal = 0); overload; // inline;
+    procedure Update(const aData: UnicodeString); overload; // inline;
+    procedure Update(const aData: RawByteString); overload; // inline;
+    function GetDigest : TBytes;
+    function HashAsBytes: TBytes;
+    function HashAsInteger: Integer;
+    function HashAsString: UnicodeString;
+  end;
+
+const
+  SHashCanNotUpdateMD5 = 'MD5: Cannot update a finalized hash';
+  SHashCanNotUpdateSHA1 = 'SHA1: Cannot update a finalized hash';
+  SHashCanNotUpdateSHA2 = 'SHA2: Cannot update a finalized hash';
+  RandomStringChars =  UnicodeString('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-/*_');
+  RandomStringCharCount = Length(RandomStringChars);
+
+
+implementation
+
+Uses
+{$IFDEF FPC_DOTTEDUNITS}
+  System.Types, System.SysConst, System.Generics.Hashes;
+{$ELSE}
+  Types, SysConst, Generics.Hashes;
+{$ENDIF}
+
+Resourcestring
+  SErrDigestSizeMustBe4 = 'Digest size must be 4, got %d instead.';
+
+
+{ THash }
+
+class function THash.ToBigEndian(aValue: Cardinal): Cardinal;
+begin
+  Result:=NtoBE(aValue);
+end;
+
+class function THash.ToBigEndian(aValue: UInt64): UInt64;
+begin
+  Result:=NtoBE(aValue);
+end;
+
+
+class function THash.DigestAsInteger(const aDigest: TBytes): Integer;
+begin
+  if Length(aDigest) <> 4 then
+    raise EHashException.CreateFmt(SErrDigestSizeMustBe4,[Length(aDigest)]);
+  Result:= PLongInt(@ADigest[0])^;
+end;
+
+class function THash.DigestAsString(const aDigest: TBytes; UpperCase: Boolean): UnicodeString;
+
+const
+  HexDigitsWL: array[0..15] of widechar = '0123456789abcdef';
+
+
+var
+  S : UnicodeString;
+  I,Len: Integer;
+  H,Res : PWideChar;
+  PB : PByte;
+  B : Byte;
+
+begin
+  S:='';
+  if Uppercase then
+    H:=@HexDigitsW
+  else
+    H:=@HexDigitsWL;
+  Len:=Length(aDigest);
+  SetLength(S,2*Len);
+  Res:=PWideChar(S);
+  PB:=PByte(aDigest);
+  for I:=1 to Len do
+    begin
+    B:=PB^;
+    Res^:=H[B shr 4];
+    inc(Res);
+    Res^:=H[B and 15];
+    Inc(Res);
+    Inc(PB);
+    end;
+  Result:=S;
+end;
+
+class function THash.DigestAsStringGUID(const aDigest: TBytes): UnicodeString;
+
+
+begin
+  With TGUID.Create(aDigest,TEndian.Little) do
+    begin
+    D1:=ToBigEndian(D1);
+    D2:=Swap(D2);
+    D3:=Swap(D3);
+{$IF SIZEOF(Char)=2}
+    Result:=ToString;
+{$ELSE}
+    Result:=Utf8Decode(ToString);
+{$ENDIF}
+    end;
+end;
+
+class function THash.GetRandomString(const aLen: Integer): UnicodeString;
+
+var
+  I : Integer;
+  Res: PWideChar;
+
+begin
+  Result:='';
+  SetLength(Result,aLen);
+  Res:=PWideChar(Result);
+  for I:=1 to ALen do
+    begin
+    Res^:=RandomStringChars[1+Random(RandomStringCharCount)];
+    Inc(Res);
+    end;
+end;
+
+{ THashMD5 }
+
+class function THashMD5.Create: THashMD5;
+begin
+  Result:=Default(THashMD5);
+  Result.Reset;
+end;
+
+function THashMD5.GetBlockSize: Integer;
+begin
+  Result:=64;
+end;
+
+function THashMD5.GetHashSize: Integer;
+begin
+  Result:=SizeOf(TMD5Digest);
+end;
+
+class function THashMD5.GetHashBytes(const aData: UnicodeString): TBytes;
+
+begin
+  With THashMD5.Create do
+    begin
+    Update(aData);
+    Result:=GetDigest;
+    end;
+end;
+
+class function THashMD5.GetHashString(const aData: UnicodeString): UnicodeString;
+
+begin
+  With THashMD5.Create do
+    begin
+    Update(aData);
+    Result:=HashAsString;
+    end;
+end;
+
+class function THashMD5.GetHashBytes(const aStream: TStream): TBytes;
+
+var
+  Buf: TBytes;
+  Len,Count: Longint;
+
+begin
+  Buf:=Default(TBytes);
+  Len:=HashReadBufferSize;
+  SetLength(Buf,Len);
+  With THashMD5.Create do
+    begin
+    Count:=aStream.Read(Buf,Len);
+    While (Count>0) do
+      begin
+      Update(Buf,Count);
+      Count:=aStream.Read(Buf,Len);
+      end;
+    Result:=GetDigest;
+    end;
+end;
+
+class function THashMD5.GetHashString(const aStream: TStream): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHashBytes(aStream));
+end;
+
+class function THashMD5.GetHashBytesFromFile(const aFileName: TFileName): TBytes;
+
+var
+  F: TFileStream;
+
+begin
+  F:=TFileStream.Create(aFileName,fmOpenRead or fmShareDenyWrite);
+  try
+    Result:=GetHashBytes(F);
+  finally
+    F.Free;
+  end;
+end;
+
+class function THashMD5.GetHashStringFromFile(const aFileName: TFileName): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHashBytesFromFile(aFileName));
+end;
+
+class function THashMD5.GetHMAC(const aData,aKey: UnicodeString): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHMACAsBytes(aData,aKey));
+end;
+
+class function THashMD5.GetHMACAsBytes(const aData, aKey: UnicodeString): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(GetBytes(aData),GetBytes(aKey));
+end;
+
+class function THashMD5.GetHMACAsBytes(const aData: UnicodeString; const aKey: TBytes): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(GetBytes(aData),aKey);
+end;
+
+class function THashMD5.GetHMACAsBytes(const aData: TBytes; const aKey: UnicodeString): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(aData,GetBytes(aKey));
+end;
+
+class function THashMD5.GetHMACAsBytes(const aData, aKey: TBytes): TBytes;
+
+var
+  I: Byte;
+  MD5_BLOCK_SIZE : Integer;
+  VLength: PtrUInt;
+  PKey, POPad, PIPad: PByte;
+  VKey, VOPad, VIPad: TBytes;
+  MD5  : THashMD5;
+
+begin
+  VKey:=Default(TBytes);
+  VOPad:=Default(TBytes);
+  VIPad:=Default(TBytes);
+  MD5:=THashMD5.Create;
+  MD5_BLOCK_SIZE:=MD5.GetBlockSize;
+  VLength:=Length(aKey);
+  if VLength > MD5_BLOCK_SIZE then
+    begin
+    SetLength(VKey,MD5_BLOCK_SIZE);
+    FillChar(VKey[0],MD5_BLOCK_SIZE, #0);
+    MD5.Update(aKey);
+    VKey:=Concat(MD5.GetDigest,VKey);
+    end
+  else
+    begin
+    SetLength(VKey,MD5_BLOCK_SIZE-VLength);
+    FillChar(VKey[0],MD5_BLOCK_SIZE-VLength, #0);
+    VKey:=Concat(aKey,VKey);
+    end;
+  SetLength(VOPad,MD5_BLOCK_SIZE);
+  POPad:=PByte(VOPad);
+  FillChar(POPad^, MD5_BLOCK_SIZE, $5c);
+  SetLength(VIPad, MD5_BLOCK_SIZE);
+  PIPad := PByte(VIPad);
+  FillChar(PIPad^, MD5_BLOCK_SIZE, $36);
+  PKey := PByte(VKey);
+  for I:=1 to VLength do
+    begin
+    POPad^:=(POPad^ xor PKey^);
+    PIPad^:=(PIPad^ xor PKey^);
+    Inc(POPad);
+    Inc(PIPad);
+    Inc(PKey);
+    end;
+  VIPad:=Concat(VIPad,aData);
+  MD5.Reset;
+  MD5.Update(VIPad);
+  Result:=Concat(VOPad,MD5.GetDigest);
+end;
+
+procedure THashMD5.Reset;
+begin
+  MD5Init(_MD5);
+end;
+
+procedure THashMD5.Update(var aData; aLength: Cardinal);
+begin
+  MD5Update(_MD5,aData,aLength);
+end;
+
+procedure THashMD5.Update(const aData: TBytes; aLength: Cardinal);
+begin
+  if aLength=0 then
+    aLength:=Length(aData);
+  MD5Update(_MD5,aData[0],aLength);
+end;
+
+procedure THashMD5.Update(const aData: UnicodeString);
+begin
+  Update(TEncoding.UTF8.GetBytes(aData));
+end;
+
+function THashMD5.GetDigest: TBytes;
+
+begin
+  Result:=[];
+  if not _DidFinal then
+    begin
+    _DidFinal:=True;
+    MD5Final(_MD5,_Digest);
+    end;
+  SetLength(Result,Length(_Digest));
+  Move(_Digest,Result[0],Length(_Digest));
+end;
+
+function THashMD5.HashAsBytes: TBytes;
+begin
+  Result:=GetDigest;
+end;
+
+function THashMD5.HashAsString: UnicodeString;
+begin
+   Result:=THash.DigestAsString(GetDigest);
+end;
+
+{ THashSHA1 }
+
+procedure THashSHA1.Reset;
+begin
+  SHA1Init(_SHA1);
+end;
+
+class function THashSHA1.Create: THashSHA1;
+begin
+  Result:=Default(THashSHA1);
+  Result.Reset;
+end;
+
+procedure THashSHA1.Update(var aData; aLength: Cardinal);
+
+begin
+  SHA1Update(_SHA1,aData,aLength);
+end;
+
+procedure THashSHA1.Update(const aData: TBytes; aLength: Cardinal);
+begin
+  if ALength=0 then
+    ALength:=Length(aData);
+  Update(aData[0],aLength);
+end;
+
+procedure THashSHA1.Update(const aData: UnicodeString);
+begin
+  Update(TEncoding.UTF8.GetBytes(aData));
+end;
+
+function THashSHA1.GetBlockSize: Integer;
+begin
+  Result:=64;
+end;
+
+function THashSHA1.GetHashSize: Integer;
+begin
+  Result:=20;
+end;
+
+function THashSHA1.HashAsBytes: TBytes;
+begin
+  Result:=GetDigest;
+end;
+
+function THashSHA1.HashAsString: UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetDigest);
+end;
+
+class function THashSHA1.GetHashBytes(const aData: UnicodeString): TBytes;
+begin
+  With THashSha1.Create do
+    begin
+    Update(aData);
+    Result:=GetDigest;
+    end;
+end;
+
+class function THashSHA1.GetHashString(const aData: UnicodeString): UnicodeString;
+begin
+  With THashSha1.Create do
+    begin
+    Update(aData);
+    Result:=HashAsString;
+    end;
+end;
+
+class function THashSHA1.GetHashBytes(const aStream: TStream): TBytes;
+
+var
+  Buf: TBytes;
+  Len,Count: Longint;
+
+begin
+  Buf:=Default(TBytes);
+  Len:=HashReadBufferSize;
+  SetLength(Buf,Len);
+  With THashSha1.Create do
+    begin
+    Count:=aStream.Read(Buf,Len);
+    While (Count>0) do
+      begin
+      Update(Buf,Count);
+      Count:=aStream.Read(Buf,Len);
+      end;
+    Result:=GetDigest;
+    end;
+end;
+
+class function THashSHA1.GetHashString(const aStream: TStream): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHashBytes(aStream));
+end;
+
+class function THashSHA1.GetHashBytesFromFile(const aFileName: TFileName): TBytes;
+
+var
+  F: TFileStream;
+
+begin
+  F:=TFileStream.Create(aFileName,fmOpenRead or fmShareDenyWrite);
+  try
+    Result:=GetHashBytes(F);
+  finally
+    F.Free;
+  end;
+end;
+
+class function THashSHA1.GetHashStringFromFile(const aFileName: TFileName): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHashBytesFromFile(aFileName));
+end;
+
+class function THashSHA1.GetHMAC(const aData, aKey: UnicodeString): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHMACAsBytes(aData,aKey));
+end;
+
+class function THashSHA1.GetHMACAsBytes(const aData, aKey: UnicodeString): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(GetBytes(aData),GetBytes(aKey));
+end;
+
+class function THashSHA1.GetHMACAsBytes(const aData: UnicodeString; const aKey: TBytes): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(GetBytes(aData),aKey);
+end;
+
+class function THashSHA1.GetHMACAsBytes(const aData: TBytes; const aKey: UnicodeString): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(aData,GetBytes(aKey));
+end;
+
+class function THashSHA1.GetHMACAsBytes(const aData, aKey: TBytes): TBytes;
+
+var
+  I: Byte;
+  SHA1_BLOCK_SIZE : Integer;
+  VLength: PtrUInt;
+  PKey, POPad, PIPad: PByte;
+  VKey, VOPad, VIPad: TBytes;
+  Sha1  : THashSha1;
+
+begin
+  VKey:=Default(TBytes);
+  VOPad:=Default(TBytes);
+  VIPad:=Default(TBytes);
+  Sha1:=THashSha1.Create;
+  SHA1_BLOCK_SIZE:=Sha1.GetBlockSize;
+  VLength:=Length(aKey);
+  if VLength > SHA1_BLOCK_SIZE then
+    begin
+    SetLength(VKey,SHA1_BLOCK_SIZE);
+    FillChar(VKey[0],SHA1_BLOCK_SIZE, #0);
+    Sha1.Update(aKey);
+    VKey:=Concat(Sha1.GetDigest,VKey);
+    end
+  else
+    begin
+    SetLength(VKey,SHA1_BLOCK_SIZE-VLength);
+    FillChar(VKey[0],SHA1_BLOCK_SIZE-VLength, #0);
+    VKey:=Concat(aKey,VKey);
+    end;
+  SetLength(VOPad,SHA1_BLOCK_SIZE);
+  POPad:=PByte(VOPad);
+  FillChar(POPad^, SHA1_BLOCK_SIZE, $5c);
+  SetLength(VIPad, SHA1_BLOCK_SIZE);
+  PIPad := PByte(VIPad);
+  FillChar(PIPad^, SHA1_BLOCK_SIZE, $36);
+  PKey := PByte(VKey);
+  for I:=1 to VLength do
+    begin
+    POPad^:=(POPad^ xor PKey^);
+    PIPad^:=(PIPad^ xor PKey^);
+    Inc(POPad);
+    Inc(PIPad);
+    Inc(PKey);
+    end;
+  VIPad:=Concat(VIPad,aData);
+  Sha1.Reset;
+  Sha1.Update(VIPad);
+  Result:=Concat(VOPad,Sha1.GetDigest);
+end;
+
+function THashSHA1.GetDigest: TBytes;
+
+begin
+  Result:=[];
+  if not _DidFinal then
+    begin
+    _DidFinal:=True;
+    SHA1Final(_SHA1,_Digest);
+    end;
+  SetLength(Result,Length(_Digest));
+  Move(_Digest,Result[0],Length(_Digest));
+end;
+
+
+{ THashSHA2 }
+
+Procedure NotSupportedVersion(aHashVersion : THashSHA2.TSHA2Version);
+
+var
+  S : String;
+
+begin
+  WriteStr(S,aHashversion);
+  Raise EHashException.CreateFmt('SHA2 - %s not yet supported',[S]);
+end;
+
+class function THashSHA2.Create(aHashVersion: TSHA2Version): THashSHA2;
+
+begin
+  if aHashVersion in [SHA224, SHA512_224, SHA512_256] then
+    NotSupportedVersion(aHashVersion);
+  Result.FHashVersion:=aHashVersion;
+  Result.Reset;
+end;
+
+class function THashSHA2.GetHashBytes(const aData: UnicodeString; aHashVersion: TSHA2Version): TBytes;
+
+var
+  H : THashSHA2;
+
+begin
+  H:=THashSHA2.Create(aHashVersion);
+  H.Update(AData);
+  Result:=H.GetDigest;
+end;
+
+class function THashSHA2.GetHashString(const aData: UnicodeString; aHashVersion: TSHA2Version): UnicodeString;
+
+var
+  H: THashSHA2;
+
+begin
+  H:=THashSHA2.Create(aHashVersion);
+  H.Update(aData);
+  Result:=H.HashAsString;
+end;
+
+class function THashSHA2.GetHashBytes(const aStream: TStream; aHashVersion: TSHA2Version): TBytes;
+
+var
+  Buf: TBytes;
+  Len,Count: Longint;
+
+begin
+  Buf:=Default(TBytes);
+  Len:=HashReadBufferSize;
+  SetLength(Buf,Len);
+  With THashSha2.Create(aHashVersion) do
+    begin
+    Count:=aStream.Read(Buf,Len);
+    While (Count>0) do
+      begin
+      Update(Buf,Count);
+      Count:=aStream.Read(Buf,Len);
+      end;
+    Result:=GetDigest;
+    end;
+end;
+
+class function THashSHA2.GetHashString(const aStream: TStream; aHashVersion: TSHA2Version): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHashBytes(aStream,aHashVersion));
+end;
+
+class function THashSHA2.GetHashBytesFromFile(const aFileName: TFileName; aHashVersion: TSHA2Version): TBytes;
+
+var
+  F: TFileStream;
+
+begin
+  F:=TFileStream.Create(aFileName,fmOpenRead or fmShareDenyWrite);
+  try
+    Result:=GetHashBytes(F,aHashVersion);
+  finally
+    F.Free;
+  end;
+end;
+
+class function THashSHA2.GetHashStringFromFile(const aFileName: TFileName; aHashVersion: TSHA2Version): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHashBytesFromFile(aFileName,aHashVersion));
+end;
+
+class function THashSHA2.GetHMAC(const aData, aKey: UnicodeString; aHashVersion: TSHA2Version): UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetHMACAsBytes(aData,aKey,aHashVersion));
+end;
+
+class function THashSHA2.GetHMACAsBytes(const aData, aKey: UnicodeString; aHashVersion: TSHA2Version): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(GetBytes(aData),GetBytes(aKey),aHashVersion);
+end;
+
+class function THashSHA2.GetHMACAsBytes(const aData: UnicodeString; const aKey: TBytes; aHashVersion: TSHA2Version): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(GetBytes(aData),aKey,aHashVersion);
+end;
+
+class function THashSHA2.GetHMACAsBytes(const aData: TBytes; const aKey: UnicodeString; aHashVersion: TSHA2Version): TBytes;
+begin
+  With TEncoding.UTF8 do
+    Result:=GetHMACAsBytes(aData,GetBytes(aKey),aHashVersion);
+end;
+
+class function THashSHA2.GetHMACAsBytes(const aData, aKey: TBytes; aHashVersion: TSHA2Version): TBytes;
+
+var
+  Count: UInt32;
+  KeySize,DataSize,BufSize : Integer;
+  aDigest,KeyBuffer, PadBuffer: TBytes;
+  SHA2,SHA2_ : THashSHA2;
+
+begin
+  Result:=[];
+  KeySize:=Length(akey);
+  DataSize:=Length(aData);
+  if aKey = nil then
+    Exit;
+  if aData = nil then
+    Exit;
+  SHA2:=THashSHA2.Create(aHashversion);
+  BufSize:=SHA2.GetBlockSize;
+  SetLength(KeyBuffer,BufSize);
+  SetLength(PadBuffer,BufSize);
+  if KeySize>BufSize then
+  begin
+    SHA2.Update(aKey);
+    aDigest:=SHA2.GetDigest;
+    System.Move(aDigest[0],KeyBuffer[0],SHA2.GetHashSize);
+  end else
+    System.Move(aKey[0], KeyBuffer[0], KeySize);
+  // XOR the key buffer with the iPad value
+  for Count := 0 to BufSize do
+    PadBuffer[Count] := KeyBuffer[Count] xor $36;
+  SHA2.Reset;
+  SHA2.Update(PadBuffer);
+  SHA2.Update(aData);
+  aDigest:=SHA2.GetDigest;
+  // XOR the key buffer with the oPad value
+  for Count := 0 to 63 do
+    PadBuffer[Count] := KeyBuffer[Count] xor $5C;
+  // SHA256 the key buffer and the result of the inner SHA256 (Outer)
+  SHA2.Reset;
+  SHA2.Update(PadBuffer);
+  SHA2.Update(aDigest);
+  Result:=SHA2_.GetDigest;
+end;
+
+procedure THashSHA2.Reset;
+begin
+  case FHashVersion of
+    Sha256 : _S256.Init;
+    Sha384 : _S384.Init;
+    Sha512 : _S512.Init;
+  end;
+  FDidFinal:=False;
+end;
+
+procedure THashSHA2.Update(var aData; aLength: Cardinal);
+begin
+  Update(PByte(@aData),aLength);
+end;
+
+procedure THashSHA2.Update(const aData: PByte; aLength: Cardinal);
+begin
+  case FHashVersion of
+    Sha256 : _S256.Update(aData,aLength);
+    Sha384 : _S384.Update(aData,aLength);
+    Sha512 : _S512.Update(aData,aLength);
+  end;
+end;
+
+procedure THashSHA2.Update(const aData: TBytes; aLength: Cardinal);
+begin
+  if aLength=0 then
+    aLength:=Length(aData);
+  Update(PByte(aData),aLength);
+end;
+
+procedure THashSHA2.Update(const aData: UnicodeString);
+begin
+  Update(TEncoding.UTF8.GetBytes(aData));
+end;
+
+procedure THashSHA2.Update(const aData: RawByteString);
+begin
+  Update(PByte(aData),Length(aData)*SizeOf(AnsiChar));
+end;
+
+procedure THashSHA2.DoFinal;
+
+begin
+  case FHashVersion of
+    Sha256 : _S256.Final;
+    Sha384 : _S384.Final;
+    Sha512 : _S512.Final;
+  end;
+  FDidFinal:=True;
+end;
+
+function THashSHA2.GetDigest: TBytes;
+
+Var
+  P : PByte;
+  L : Integer;
+
+
+begin
+  if Not FDidFinal then
+    DoFinal;
+  // These should normally all be the same...
+  case FHashVersion of
+    Sha256 : P:=@_S256.Digest;
+    Sha384 : P:=@_S384.Digest;
+    Sha512 : P:=@_S512.Digest;
+  end;
+  L:=GetHashSize;
+  SetLength(Result,L);
+  Move(P^,Result[0],L);
+end;
+
+function THashSHA2.GetBlockSize: Integer;
+Const
+  Sizes : Array[TSHA2Version] of integer
+        = (64,64,128,128,128,128);
+begin
+  Result:=Sizes[FHashVersion];
+end;
+
+function THashSHA2.GetHashSize: Integer;
+
+Const
+  Sizes : Array[TSHA2Version] of integer
+        = (28,32,48,64,28,32);
+begin
+  Result:=Sizes[FHashVersion];
+end;
+
+function THashSHA2.HashAsBytes: TBytes;
+begin
+  Result:=GetDigest;
+end;
+
+function THashSHA2.HashAsString: UnicodeString;
+begin
+  Result:=THash.DigestAsString(GetDigest);
+end;
+
+{ THashBobJenkins }
+
+class function THashBobJenkins.Create: THashBobJenkins;
+begin
+  Result.Reset;
+end;
+
+class function THashBobJenkins.GetHashBytes(const aData: UnicodeString): TBytes;
+begin
+  Result:=Default(TBytes);
+  SetLength(Result,SizeOf(Cardinal));
+  PCardinal(Result)^:=GetHashValue(aData)
+end;
+
+class function THashBobJenkins.GetHashString(const aData: UnicodeString): UnicodeString;
+begin
+  Result:=HexStr(GetHashValue(aData),8);
+end;
+
+class function THashBobJenkins.GetHashString(const aData: RawByteString): UnicodeString;
+begin
+  Result:=HexStr(GetHashValue(aData),8);
+end;
+
+
+class function THashBobJenkins.GetHashValue(var aData; aLength: Integer; aInitialValue: Integer): Integer;
+begin
+  Result:=Integer(DelphiHashLittle(PByte(@AData),aLength,aInitialValue));
+end;
+
+class function THashBobJenkins.GetHashValue(const aData: PByte; aLength: Integer; aInitialValue: Integer): Integer;
+begin
+  Result:=DelphiHashLittle(AData,aLength,aInitialValue);
+end;
+
+class function THashBobJenkins.GetHashValue(const aData: UnicodeString): Integer;
+begin
+  Result:=GetHashValue(PByte(aData),Length(aData)*SizeOf(UnicodeChar),0);
+end;
+
+class function THashBobJenkins.GetHashValue(const aData: RawByteString): Integer;
+begin
+  Result:=GetHashValue(PByte(aData),Length(aData)*SizeOf(AnsiChar),0);
+end;
+
+procedure THashBobJenkins.Reset(aInitialValue: Integer);
+begin
+  FCurrent:=aInitialValue;
+end;
+
+procedure THashBobJenkins.Update(aData: PByte; aLength: Cardinal);
+begin
+  FCurrent:=DelphiHashLittle(AData,aLength,FCurrent);
+end;
+
+procedure THashBobJenkins.Update(var aData; aLength: Cardinal);
+begin
+  Update(PByte(@AData),aLength);
+end;
+
+procedure THashBobJenkins.Update(const aData: TBytes; aLength: Cardinal);
+begin
+  if aLength=0 then
+    aLength:=Length(aData);
+  Update(PByte(aData),aLength);
+end;
+
+procedure THashBobJenkins.Update(const aData: UnicodeString);
+begin
+  Update(PByte(aData),Length(aData)*SizeOf(UnicodeChar));
+end;
+
+procedure THashBobJenkins.Update(const aData: RawByteString);
+begin
+  Update(PByte(aData),Length(aData)*SizeOf(AnsiChar));
+end;
+
+function THashBobJenkins.HashAsBytes: TBytes;
+begin
+  Result:=[];
+  SetLength(Result,Sizeof(Cardinal));
+  PCardinal(Result)^:=FCurrent;
+end;
+
+function THashBobJenkins.HashAsInteger: Integer;
+begin
+  Result:=FCurrent;
+end;
+
+function THashBobJenkins.HashAsString: UnicodeString;
+begin
+  Result:=HexStr(HashAsInteger,8);
+end;
+
+{ THashFNV1a32 }
+
+class function THashFNV1a32.Create: THashFNV1a32;
+begin
+  Result.Reset;
+end;
+
+class function THashFNV1a32.GetHashBytes(const aData: UnicodeString): TBytes;
+
+var
+  C : Cardinal;
+
+begin
+  Result:=Default(TBytes);
+  SetLength(Result,SizeOf(Fnv32_t));
+  C:=Cardinal(GetHashValue(aData));
+  PFnv32_t(@Result[0])^:=C;
+end;
+
+class function THashFNV1a32.GetHashString(const aData: UnicodeString): UnicodeString;
+begin
+  Result:=HexStr(FNV1_32a(Pointer(aData)^,Length(aData)*SizeOf(UnicodeChar),FNV_SEED),8);
+end;
+
+class function THashFNV1a32.GetHashString(const aData: RawByteString): UnicodeString;
+begin
+  Result:=HexStr(FNV1_32a(Pointer(aData)^,Length(aData),FNV_SEED),8);
+end;
+
+class function THashFNV1a32.GetHashValue(const aData: UnicodeString): Integer;
+
+var
+  C : Cardinal;
+
+begin
+  C:=FNV1_32a(PByte(aData),Length(aData)*SizeOf(UnicodeChar),FNV_SEED);
+  Result:=Integer(C);
+end;
+
+class function THashFNV1a32.GetHashValue(const aData: RawByteString): Integer;
+begin
+  Result:=Integer(FNV1_32a(Pointer(aData)^,Length(aData), FNV_SEED));
+end;
+
+class function THashFNV1a32.GetHashValue(const aData; aLength: Cardinal; aInitialValue: Cardinal): Integer;
+begin
+  Result:=Integer(FNV1_32a(aData,aLength,aInitialValue));
+end;
+
+procedure THashFNV1a32.Reset(aInitialValue: Cardinal);
+begin
+  FCurrent:=aInitialValue;
+end;
+
+procedure THashFNV1a32.Update(const aData; aLength: Cardinal);
+begin
+  FCurrent:=FNV1_32a(aData,aLength,FCurrent);
+end;
+
+procedure THashFNV1a32.Update(const aData: TBytes; aLength: Cardinal);
+begin
+  if aLength=0 then
+    aLength:=Length(aData);
+  Update(aData[0],aLength);
+end;
+
+procedure THashFNV1a32.Update(const aData: UnicodeString);
+begin
+  FCurrent:=FNV1_32a(PByte(aData),Length(aData)*SizeOf(UnicodeChar),FCurrent);
+end;
+
+procedure THashFNV1a32.Update(const aData: RawByteString);
+begin
+  FCurrent:=FNV1_32a(PByte(aData),Length(aData)*SizeOf(AnsiChar),FCurrent);
+end;
+
+function THashFNV1a32.GetDigest: TBytes;
+begin
+  Result:=Default(TBytes);
+  SetLength(Result,SizeOf(Fnv32_t));
+  PFnv32_t(@Result[0])^:=FCurrent;
+end;
+
+function THashFNV1a32.HashAsBytes: TBytes;
+begin
+  Result:=GetDigest;
+end;
+
+function THashFNV1a32.HashAsInteger: Integer;
+begin
+  Result:=Integer(FCurrent);
+end;
+
+function THashFNV1a32.HashAsString: UnicodeString;
+begin
+  Result:=HexStr(FCurrent,8);
+end;
+
+end.

+ 4 - 0
packages/vcl-compat/tests/testcompat.lpi

@@ -64,6 +64,10 @@
         <Filename Value="utcpush.pas"/>
         <IsPartOfProject Value="True"/>
       </Unit>
+      <Unit>
+        <Filename Value="utchash.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
     </Units>
   </ProjectOptions>
   <CompilerOptions>

+ 2 - 1
packages/vcl-compat/tests/testcompat.lpr

@@ -5,7 +5,8 @@ program testcompat;
 uses
   {$IFDEF UNIX}cwstring,{$ENDIF}
   Classes, consoletestrunner, tcnetencoding, tciotuils, 
-  utmessagemanager, utcdevices, utcanalytics, utcimagelist, utcnotifications, utcjson, utcpush;
+  utmessagemanager, utcdevices, utcanalytics, utcimagelist, 
+  utcnotifications, utcjson, utcpush, utchash;
 
 type
 

+ 845 - 0
packages/vcl-compat/tests/utchash.pp

@@ -0,0 +1,845 @@
+unit utchash;
+
+{$mode objfpc}
+{$H+}
+{$modeswitch functionreferences}
+{$modeswitch anonymousfunctions}
+{$macro on}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testutils, testregistry, system.hash, generics.hashes;
+
+type
+
+  { TTestHashBase }
+
+  TTestHashBase = Class(TTestCase)
+  Public
+    class procedure AssertEquals(aMsg: String; aExpected, aActual: TBytes); overload;
+    class function BytesToString(Bytes: TBytes): String;
+  end;
+
+  TTestHash = Class(TTestHashBase)
+  Private
+    FB : TBytes;
+  Protected
+    Procedure Setup; override;
+    Procedure TearDown; override;
+  Published
+    Procedure TestDigestAsInteger;
+    Procedure TestDigestAsString;
+    Procedure TestDigestAsStringGUID;
+    Procedure TestGetRandomString;
+    Procedure TestToBigEndian;
+  end;
+
+  { TTestMD5 }
+
+  TTestMD5 = class(TTestHashBase)
+  private
+    FMD5: THashMD5;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    property MD5 : THashMD5 Read FMD5;
+  published
+    procedure TestHashAsBytes;
+    procedure TestHashAsBytesTwice;
+    // Class functions
+    Procedure TestGetHashBytesString;
+    Procedure TestGetHashStringString;
+    Procedure TestGetHashBytesStream;
+    Procedure TestGetHashStringStream;
+    Procedure TestGetHashBytesFromFile;
+    Procedure TestGetHashStringFromFile;
+  end;
+
+  { TTestSha1 }
+
+  TTestSha1 = Class(TTestHashBase)
+  private
+    FSHA1: THashSha1;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    property SHA1: THashSha1 Read FSHA1;
+  published
+    procedure TestHashAsBytes;
+    procedure TestHashAsBytesTwice;
+    // Class functions
+    Procedure TestGetHashBytesString;
+    Procedure TestGetHashStringString;
+    Procedure TestGetHashBytesStream;
+    Procedure TestGetHashStringStream;
+    Procedure TestGetHashBytesFromFile;
+    Procedure TestGetHashStringFromFile;
+  end;
+
+  { TTestSha2 }
+
+  { Only tests SHA256, on the assumption that the other supported fphash mechanisms are also correct,
+    so we basically test the wrapper layer around the FPC native routines... }
+  TTestSha2 = Class(TTestHashBase)
+  private
+    FSHA2: THashSha2;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    property SHA2: THashSha2 Read FSHA2;
+  published
+    procedure TestHashAsBytes;
+    procedure TestHashAsBytesTwice;
+    // Class functions
+    Procedure TestGetHashBytesString;
+    Procedure TestGetHashStringString;
+    Procedure TestGetHashBytesStream;
+    Procedure TestGetHashStringStream;
+    Procedure TestGetHashBytesFromFile;
+    Procedure TestGetHashStringFromFile;
+  end;
+
+
+  { TTestFNV }
+
+  TTestFNV = Class(TTestHashBase)
+  private
+    FFNV: THashFNV1a32;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    property FNV: THashFNV1a32 Read FFNV;
+  Published
+    Procedure TestHashAsBytes;
+    Procedure TestGetHashBytes;         // (const aData: UnicodeString): TBytes; static;
+    Procedure TestGetHashStringUnicode; // (const aData: UnicodeString): UnicodeString; overload; static;
+    Procedure TestGetHashStringRawByte; // (const aData: RawByteString): UnicodeString; overload; static;
+    Procedure TestGetHashValueUnicode;  // (const aData: UnicodeString): Integer; overload; static; inline;
+    Procedure TestGetHashValueRawByte;  // (const aData: RawByteString): Integer; overload; static; inline;
+    Procedure TestGetHashValue;         // (const aData; aLength: Cardinal; aInitialValue: Cardinal = FNV_SEED): Integer; overload; static; inline;
+  end;
+
+  { TTestFNV }
+
+  { TTestBobJenkins }
+
+  TTestBobJenkins = Class(TTestHashBase)
+  private
+    FBJ: THashBobJenkins;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    property BJ: THashBobJenkins Read FBJ;
+  Published
+    Procedure TestHashAsBytes;
+    Procedure TestGetHashBytes;         // (const aData: UnicodeString): TBytes; static;
+    Procedure TestGetHashStringUnicode; // (const aData: UnicodeString): UnicodeString; overload; static;
+    Procedure TestGetHashStringRawByte; // (const aData: RawByteString): UnicodeString; overload; static;
+    Procedure TestGetHashValueUnicode;  // (const aData: UnicodeString): Integer; overload; static; inline;
+    Procedure TestGetHashValueRawByte;  // (const aData: RawByteString): Integer; overload; static; inline;
+    Procedure TestGetHashValue;         // (const aData; aLength: Cardinal; aInitialValue: Cardinal = FNV_SEED): Integer; overload; static; inline;
+  end;
+
+
+
+implementation
+
+
+Class Function TTestHashBase.BytesToString(Bytes : TBytes) : String;
+
+var
+  I : Integer;
+
+
+begin
+  Result:='[';
+  For I:=0 to Length(Bytes)-1 do
+    begin
+    if I>0 then
+      Result:=Result+', ';
+    Result:=Result+'$'+Format('%x',[Bytes[i]]);
+    end;
+  Result:=Result+']';
+end;
+
+class procedure TTestHashBase.AssertEquals(aMsg: String; aExpected, aActual: TBytes);
+
+var
+  I : Integer;
+
+begin
+  AssertEquals(aMsg+': length',Length(aExpected),Length(aActual));
+  For I:=0 to length(aExpected)-1 do
+    AssertEquals(aMsg+': Byte['+IntTostr(i)+']','$'+IntToHex(aExpected[i]),'$'+IntToHex(aActual[i]));
+end;
+
+{ TTestHash }
+
+procedure TTestHash.Setup;
+begin
+  inherited Setup;
+  SetLength(FB,0);
+end;
+
+procedure TTestHash.TearDown;
+begin
+  SetLength(FB,0);
+  inherited TearDown;
+end;
+
+procedure TTestHash.TestDigestAsInteger;
+
+  Procedure DoDigestInt;
+
+  begin
+    THash.DigestAsInteger(FB);
+  end;
+
+begin
+//  class function DigestAsInteger(const aDigest: TBytes): Integer; static;
+  SetLength(FB,4);
+  FB[0]:=$01;
+  FB[1]:=$02;
+  FB[2]:=$03;
+  FB[3]:=$04;
+  AssertEquals('Integer',NtoBE($01020304),THash.DigestAsInteger(FB));
+  SetLength(FB,5);
+  AssertException('Size must be 4',EHashException,
+  procedure
+  begin
+    THash.DigestAsInteger(FB);
+  end);
+end;
+
+procedure TTestHash.TestDigestAsString;
+begin
+//  class function DigestAsString(const aDigest: TBytes; UpperCase : Boolean = false): UnicodeString; static;
+  SetLength(FB,4);
+  FB[0]:=$01;
+  FB[1]:=$0A;
+  FB[2]:=$03;
+  FB[3]:=$0D;
+  AssertEquals('Lower','010a030d',THash.DigestAsString(FB));
+  AssertEquals('Upper','010A030D',THash.DigestAsString(FB,True));
+end;
+
+
+Operator = (a,b : TGUID) : Boolean;
+var
+  i: integer;
+begin
+  Result:=(a.D1=b.D1)
+          and (a.D2=b.D2)
+          and (a.D3=b.D3)
+          and (a.D3=b.D3);
+  I:=0;
+  While Result and (I<8) do
+    begin
+    Result:=(a.D4[i]=b.D4[i]);
+    Inc(I);
+    end;
+end;
+
+procedure TTestHash.TestDigestAsStringGUID;
+
+var
+  I : integer;
+
+begin
+  SetLength(FB,16);
+  For I:=0 to 15 do
+    FB[i]:=I;
+  AssertEquals('THash.DigestAsStringGUID','{00010203-0405-0607-0809-0A0B0C0D0E0F}',THash.DigestAsStringGUID(FB));
+end;
+
+procedure TTestHash.TestGetRandomString;
+
+var
+  S : UnicodeString;
+  C : UnicodeChar;
+
+begin
+  S:=THash.GetRandomString;
+  AssertEquals('Default length',10,Length(S));
+  For C in S do
+   If not Pos(C,RandomStringChars)>0 then
+     Fail(C+' not in allowed chars');
+end;
+
+procedure TTestHash.TestToBigEndian;
+
+var
+  C : Cardinal;
+  Q : UInt64;
+
+begin
+  C:=$01020304;
+  AssertEquals('Cardinal',NToBE(C),THash.ToBigEndian(C));
+  Q:=UInt64($0102030405060708);
+  AssertEquals('Cardinal',NToBE(Q),THash.ToBigEndian(Q));
+end;
+
+Const
+  ExpectABCMD5 : TBytes = ($90, $1, $50, $98, $3C, $D2, $4F, $B0, $D6, $96, $3F, $7D, $28, $E1, $7F, $72);
+  ExpectABCSha1 : TBytes = ($A9, $99, $3E, $36, $47, $6, $81, $6A, $BA, $3E, $25, $71, $78, $50, $C2, $6C, $9C, $D0, $D8, $9D);
+  ExpectABCSha2 : TBytes = ($BA, $78, $16, $BF, $8F, $1, $CF, $EA, $41, $41, $40, $DE, $5D, $AE, $22, $23, $B0, $3, $61, $A3, $96, $17, $7A, $9C, $B4, $10, $FF, $61, $F2, $0, $15, $AD);
+  ExpectABCFNV = Cardinal($1A47E90B);
+  ExpectABCFNVUnicode = Cardinal($AE1E997D);
+  ExpectABCBJ = 238646833;
+  ExpectABCBJUnicode = Cardinal(4228388320);
+
+
+
+procedure TTestMD5.TestHashAsBytes;
+
+
+var
+  Act : TBytes;
+
+begin
+  FMD5.Update('abc');
+  Act:=FMD5.HashAsBytes;
+  // Writeln(BytesToString(Act));
+  AssertEquals('abc',ExpectAbcMD5,Act);
+end;
+
+procedure TTestMD5.TestHashAsBytesTwice;
+
+var
+  Act : TBytes;
+
+begin
+  FMD5.Update('abc');
+  Act:=FMD5.HashAsBytes;
+  AssertEquals('abc',ExpectAbcMD5,Act);
+  Act:=FMD5.HashAsBytes;
+  AssertEquals('abc 2',ExpectAbcMD5,Act);
+end;
+
+procedure TTestMD5.TestGetHashBytesString;
+begin
+  AssertEquals('abc',ExpectAbcMD5,THashMD5.GetHashBytes('abc'));
+end;
+
+procedure TTestMD5.TestGetHashStringString;
+begin
+  AssertEquals('abc',THash.DigestAsString(ExpectAbcMD5),THashMD5.GetHashString('abc'));
+end;
+
+procedure TTestMD5.TestGetHashBytesStream;
+
+Var
+  S : TStream;
+  A : AnsiString;
+
+begin
+  A:='abc';
+  S:=TStringStream.Create(A);
+  try
+    AssertEquals('GetHashBytes',ExpectAbcMD5,THashMD5.GetHashBytes(S));
+  finally
+    S.Free;
+  end;
+end;
+
+procedure TTestMD5.TestGetHashStringStream;
+
+Var
+  S : TStream;
+  A : AnsiString;
+
+begin
+  A:='abc';
+  S:=TStringStream.Create(A);
+  try
+    AssertEquals('GetHashBytes',THash.DigestAsString(ExpectAbcMD5),THashMD5.GetHashString(S));
+  finally
+    S.Free;
+  end;
+end;
+
+procedure TTestMD5.TestGetHashBytesFromFile;
+
+Var
+  F,S : TStream;
+  Fn,A : AnsiString;
+
+begin
+  A:='abc';
+  F:=nil;
+  S:=TStringStream.Create(A);
+  try
+    FN:=GetTempFileName;
+    F:=TFileStream.Create(FN,fmCreate);
+    F.CopyFrom(S,0);
+    FreeAndNil(F);
+    AssertEquals('GetHashBytes',ExpectAbcMD5,THashMD5.GetHashBytesFromFile(FN));
+  finally
+    S.Free;
+    FreeAndNil(F);
+  end;
+
+end;
+
+procedure TTestMD5.TestGetHashStringFromFile;
+
+Var
+  F,S : TStream;
+  Fn,A : AnsiString;
+
+begin
+  A:='abc';
+  F:=nil;
+  S:=TStringStream.Create(A);
+  try
+    FN:=GetTempFileName;
+    F:=TFileStream.Create(FN,fmCreate);
+    F.CopyFrom(S,0);
+    FreeAndNil(F);
+    AssertEquals('GetHashBytes',THash.DigestAsString(ExpectABCMD5),THashMD5.GetHashStringFromFile(FN));
+  finally
+    S.Free;
+    FreeAndNil(F);
+  end;
+end;
+
+
+
+procedure TTestMD5.SetUp;
+begin
+  FMD5:=THashMD5.Create;
+end;
+
+procedure TTestMD5.TearDown;
+begin
+  //
+end;
+
+{ TTestSha1 }
+
+procedure TTestSha1.SetUp;
+begin
+  inherited SetUp;
+  FSHA1:=THashSha1.Create;
+end;
+
+procedure TTestSha1.TearDown;
+begin
+  inherited TearDown;
+end;
+
+procedure TTestSha1.TestHashAsBytes;
+
+var
+  Act : TBytes;
+
+begin
+  FSHA1.Update('abc');
+  Act:=FSHA1.HashAsBytes;
+  // Writeln(BytesToString(Act));
+  AssertEquals('abc',ExpectAbcSha1,Act);
+end;
+
+procedure TTestSha1.TestHashAsBytesTwice;
+var
+  Act : TBytes;
+
+begin
+  FSHA1.Update('abc');
+  Act:=FSHA1.HashAsBytes;
+  // Writeln(BytesToString(Act));
+  AssertEquals('abc',ExpectAbcSha1,Act);
+  Act:=FSHA1.HashAsBytes;
+  // Writeln(BytesToString(Act));
+  AssertEquals('abc',ExpectAbcSha1,Act);
+end;
+
+procedure TTestSha1.TestGetHashBytesString;
+begin
+  AssertEquals('abc',ExpectAbcSha1,THashSha1.GetHashBytes('abc'));
+end;
+
+procedure TTestSha1.TestGetHashStringString;
+begin
+  AssertEquals('abc',THash.DigestAsString(ExpectAbcSha1),THashSha1.GetHashString('abc'));
+end;
+
+procedure TTestSha1.TestGetHashBytesStream;
+
+Var
+  S : TStream;
+  A : AnsiString;
+
+begin
+  A:='abc';
+  S:=TBytesStream.Create(TEncoding.UTF8.GetAnsiBytes(A));
+  try
+    AssertEquals('GetHashBytes',ExpectAbcSha1,THashSha1.GetHashBytes(S));
+  finally
+    S.Free;
+  end;
+
+end;
+
+procedure TTestSha1.TestGetHashStringStream;
+
+Var
+  S : TStream;
+  A : AnsiString;
+
+begin
+  A:='abc';
+  S:=TStringStream.Create(A);
+  try
+    AssertEquals('GetHashBytes',THash.DigestAsString(ExpectAbcSha1),THashSha1.GetHashString(S));
+  finally
+    S.Free;
+  end;
+end;
+
+procedure TTestSha1.TestGetHashBytesFromFile;
+Var
+  F,S : TStream;
+  Fn,A : AnsiString;
+
+begin
+  A:='abc';
+  F:=nil;
+  S:=TStringStream.Create(A);
+  try
+    FN:=GetTempFileName;
+    F:=TFileStream.Create(FN,fmCreate);
+    F.CopyFrom(S,0);
+    FreeAndNil(F);
+    AssertEquals('GetHashBytes',ExpectAbcSha1,THashSha1.GetHashBytesFromFile(FN));
+  finally
+    S.Free;
+    FreeAndNil(F);
+  end;
+end;
+
+procedure TTestSha1.TestGetHashStringFromFile;
+
+Var
+  F,S : TStream;
+  Fn,A : AnsiString;
+
+begin
+  A:='abc';
+  F:=nil;
+  S:=TStringStream.Create(A);
+  try
+    FN:=GetTempFileName;
+    F:=TFileStream.Create(FN,fmCreate);
+    F.CopyFrom(S,0);
+    FreeAndNil(F);
+    AssertEquals('GetHashBytes',THash.DigestAsString(ExpectABCSha1),THashSha1.GetHashStringFromFile(FN));
+  finally
+    S.Free;
+    FreeAndNil(F);
+  end;
+end;
+
+{ TTestSha2 }
+
+procedure TTestSha2.SetUp;
+begin
+  inherited SetUp;
+  FSHa2:=THashSha2.Create();
+end;
+
+procedure TTestSha2.TearDown;
+begin
+  FSHa2:=Default(THashSha2);
+  inherited TearDown;
+end;
+
+procedure TTestSha2.TestHashAsBytes;
+var
+  Act : TBytes;
+
+begin
+  FSHA2.Update(UNicodeString('abc'));
+  Act:=FSHA2.HashAsBytes;
+  AssertEquals('abc',ExpectAbcSha2,Act);
+end;
+
+procedure TTestSha2.TestHashAsBytesTwice;
+var
+  Act : TBytes;
+
+begin
+  FSHA2.Update(UNicodeString('abc'));
+  Act:=FSHA2.HashAsBytes;
+  AssertEquals('abc',ExpectAbcSha2,Act);
+  Act:=FSHA2.HashAsBytes;
+  AssertEquals('abc',ExpectAbcSha2,Act);
+end;
+
+procedure TTestSha2.TestGetHashBytesString;
+begin
+  AssertEquals('abc',ExpectAbcSha2,THashSha2.GetHashBytes('abc'));
+end;
+
+procedure TTestSha2.TestGetHashStringString;
+begin
+  AssertEquals('abc',THash.DigestAsString(ExpectAbcSha2),THashSha2.GetHashString('abc'));
+end;
+
+procedure TTestSha2.TestGetHashBytesStream;
+Var
+  S : TStream;
+  A : AnsiString;
+
+begin
+  A:='abc';
+  S:=TBytesStream.Create(TEncoding.UTF8.GetAnsiBytes(A));
+  try
+    AssertEquals('GetHashBytes',ExpectAbcSha2,THashSha2.GetHashBytes(S));
+  finally
+    S.Free;
+  end;
+end;
+
+procedure TTestSha2.TestGetHashStringStream;
+Var
+  S : TStream;
+  A : AnsiString;
+
+begin
+  A:='abc';
+  S:=TStringStream.Create(A);
+  try
+    AssertEquals('GetHashBytes',THash.DigestAsString(ExpectAbcSha2),THashSha2.GetHashString(S));
+  finally
+    S.Free;
+  end;
+
+end;
+
+procedure TTestSha2.TestGetHashBytesFromFile;
+Var
+  F,S : TStream;
+  Fn,A : AnsiString;
+
+begin
+  A:='abc';
+  F:=nil;
+  S:=TStringStream.Create(A);
+  try
+    FN:=GetTempFileName;
+    F:=TFileStream.Create(FN,fmCreate);
+    F.CopyFrom(S,0);
+    FreeAndNil(F);
+    AssertEquals('GetHashBytes',ExpectAbcSha2,THashSha2.GetHashBytesFromFile(FN));
+  finally
+    S.Free;
+    FreeAndNil(F);
+  end;
+end;
+
+procedure TTestSha2.TestGetHashStringFromFile;
+Var
+  F,S : TStream;
+  Fn,A : AnsiString;
+
+begin
+  A:='abc';
+  F:=nil;
+  S:=TStringStream.Create(A);
+  try
+    FN:=GetTempFileName;
+    F:=TFileStream.Create(FN,fmCreate);
+    F.CopyFrom(S,0);
+    FreeAndNil(F);
+    AssertEquals('GetHashBytes',THash.DigestAsString(ExpectABCSha2),THashSha2.GetHashStringFromFile(FN));
+  finally
+    S.Free;
+    FreeAndNil(F);
+  end;
+end;
+
+{ TTestFNV }
+
+procedure TTestFNV.SetUp;
+begin
+  inherited SetUp;
+  FFNV:=THashFNV1a32.Create;
+end;
+
+procedure TTestFNV.TearDown;
+begin
+  inherited TearDown;
+end;
+
+procedure TTestFNV.TestHashAsBytes;
+
+var
+  Act : TBytes;
+
+begin
+//  Writeln('HashAsBytes');
+  FFNV.Update(RawByteString('abc'));
+  Act:=FFNV.HashAsBytes;
+  AssertEquals('abc',ExpectABCFNV,THash.DigestAsInteger(Act));
+end;
+
+procedure TTestFNV.TestGetHashBytes;
+
+var
+  Act : TBytes;
+
+begin
+//  Writeln('GetHashBytes');
+  Act:=THashFNV1a32.GetHashBytes('abc');
+//  Writeln(BytesToString(Act));
+  AssertEquals('abc',ExpectABCFNVUnicode,Cardinal(THash.DigestAsInteger(Act)));
+end;
+
+procedure TTestFNV.TestGetHashStringUnicode;
+
+var
+  A : Unicodestring;
+
+begin
+  A:='abc';
+  AssertEquals('Hash',HexStr(ExpectABCFNVUnicode,8),THashFNV1a32.GetHashString(A));
+end;
+
+procedure TTestFNV.TestGetHashStringRawByte;
+
+var
+  A : Ansistring;
+
+begin
+  A:='abc';
+  AssertEquals('Hash',HexStr(ExpectABCFNV,8),THashFNV1a32.GetHashString(A));
+end;
+
+procedure TTestFNV.TestGetHashValueUnicode;
+
+var
+  A : Unicodestring;
+
+begin
+  A:='abc';
+  AssertEquals('Hash',ExpectABCFNVUnicode,Cardinal(THashFNV1a32.GetHashValue(A)));
+end;
+
+procedure TTestFNV.TestGetHashValueRawByte;
+
+var
+  A : Ansistring;
+
+begin
+  A:='abc';
+  AssertEquals('Hash',ExpectABCFNV,THashFNV1a32.GetHashValue(A));
+end;
+
+procedure TTestFNV.TestGetHashValue;
+var
+  A : Ansistring;
+
+begin
+  A:='abc';
+  AssertEquals('Hash',ExpectABCFNV,THashFNV1a32.GetHashValue(PByte(A)^,3));
+end;
+
+{ TTestBobJenkins }
+
+procedure TTestBobJenkins.SetUp;
+begin
+  inherited SetUp;
+  FBJ:=THashBobJenkins.Create;
+end;
+
+procedure TTestBobJenkins.TearDown;
+begin
+  FBJ:=Default(THashBobJenkins);
+  inherited TearDown;
+end;
+
+procedure TTestBobJenkins.TestHashAsBytes;
+
+var
+  Act : TBytes;
+
+begin
+  FBJ.Update(RawByteString('abc'));
+  Act:=FBJ.HashAsBytes;
+  AssertEquals('abc',ExpectABCBJ,THash.DigestAsInteger(Act));
+end;
+
+procedure TTestBobJenkins.TestGetHashBytes;
+
+var
+  Act : TBytes;
+
+begin
+  Act:=THashBobJenkins.GetHashBytes('abc');
+  AssertEquals('abc',ExpectABCBJUnicode,Cardinal(THash.DigestAsInteger(Act)));
+end;
+
+procedure TTestBobJenkins.TestGetHashStringUnicode;
+
+var
+  A : Unicodestring;
+
+begin
+  A:='abc';
+  AssertEquals('Hash',HexStr(ExpectABCBJUnicode,8),THashBobJenkins.GetHashString(A));
+end;
+
+procedure TTestBobJenkins.TestGetHashStringRawByte;
+
+var
+  A : Ansistring;
+
+begin
+  A:='abc';
+  AssertEquals('Hash',HexStr(ExpectABCBJ,8),THashBobJenkins.GetHashString(A));
+end;
+
+procedure TTestBobJenkins.TestGetHashValueUnicode;
+var
+  A : UnicodeString;
+  D : Integer;
+
+
+begin
+  A:='abc';
+  D:=THashBobJenkins.GetHashValue(A);
+  AssertEquals('abc',ExpectABCBJUnicode,Cardinal(D));
+end;
+
+procedure TTestBobJenkins.TestGetHashValueRawByte;
+
+var
+  A : AnsiString;
+  D : Integer;
+
+
+begin
+  A:='abc';
+  D:=Cardinal(THashBobJenkins.GetHashValue(A));
+  AssertEquals('abc',ExpectABCBJ,D);
+end;
+
+procedure TTestBobJenkins.TestGetHashValue;
+var
+  A : AnsiString;
+  D : Integer;
+
+
+begin
+  A:='abc';
+  D:=Cardinal(THashBobJenkins.GetHashValue(PByte(A),3));
+  AssertEquals('abc',ExpectABCBJ,D);
+end;
+
+initialization
+
+  RegisterTests([TTestHash,TTestMD5,TTestSha1,TTestFNV,TTestBobJenkins,TTestSha2]);
+end.
+