Files
lazarus-ccr/components/fpspreadsheet/source/crypto/3rdParty/fcl-hash/fpssha256.pp

435 lines
13 KiB
ObjectPascal

{
This file is part of the Free Component Library.
Copyright (c) 2021 by the Free Pascal team.
SHA256 and HMACSha256 routines.
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 fpsSHA256;
{$mode ObjFPC}{$H+}
{$MODESWITCH advancedrecords}
interface
uses
Classes, SysUtils;
Type
TSHA256Digest = packed array[0..31] of Byte;
PSHA256Digest = ^TSHA256Digest;
PSHA256 = ^TSHA256;
TSHA256 = record
Context: array[0..7] of UInt32;
Digest: TSHA256Digest;
HashBuffer: array[0..63] of Byte;
Index: UInt32;
TotalLength: Int64;
procedure Compress;
procedure Final;
procedure Init;
function IsEqual(const ADigest: TSHA256Digest): Boolean;
procedure OutputHexa(out Result: AnsiString);
procedure Update(PBuf: PByte; Size: UInt32); overload;
procedure Update(const Value: TBytes); overload;
// Calculate SHA256, return digest as bytes.
class procedure DigestBytes(const Value: TBytes; out Result: TBytes) ; static;
// Calculate SHA256, return digest as base64(url) string
class procedure DigestBase64(const Value: TBytes; const IsURL: Boolean; out Result: AnsiString); static;
// Calculate SHA256, return digest as HEX encoded string
class procedure DigestHexa(const Value: TBytes; out Result: AnsiString); static;
// HMAC using SHA256 as hash
Class function HMAC(Key: PByte; KeySize: UInt32; Data: PByte; DataSize: UInt32; var aDigest: TSHA256Digest): Boolean; overload; static;
class function HMAC(Key: PByte; KeySize: UInt32; Data: PByte; DataSize: UInt32; Data2: PByte; DataSize2: UInt32; Data3: PByte; DataSize3: UInt32; var aDigest: TSHA256Digest): Boolean; overload; static;
// Calculate HMacSHA256, return digest as hex string.
class function HMACHexa(const Key, Data: TBytes; out SignatureHexa: AnsiString): Boolean; overload; static;
// Calculate SHA256 from a stream, return digest.
class procedure Stream(aStream: TStream; out aDigest: TSHA256Digest); static; overload;
class function Stream(aStream: TStream): TSHA256Digest; static; overload;
// Digest Stream, result as HexaDecimal string.
class procedure StreamHexa(aStream: TStream; out Result: AnsiString); static; overload;
class Function StreamHexa(aStream: TStream): AnsiString; static overload;
// Digest Stream, result as Base64-encoded string
class procedure StreamBase64(aStream: TStream; isURL : Boolean; out Result: AnsiString); static; overload;
class Function StreamBase64(aStream: TStream; isURL : Boolean): AnsiString; static; overload;
// HKDF : Derive key of desired length from a salt,input key and info (RF5869, using HMACSHA256) .
class function HKDF(const Salt, IKM, Info: TBytes; var Output: TBytes; const DesiredLen: Integer): Boolean; static;
end;
Const
SHA256_DIGEST_SIZE = SizeOf(TSHA256Digest); // 32
implementation
uses fpsHashUtils;
//------------------------------------------------------------------------------
// SHA256
//------------------------------------------------------------------------------
procedure TSHA256.Init;
begin
Self.Index := 0;
Self.TotalLength := 0;
FillChar(Self.HashBuffer, Sizeof(Self.HashBuffer), 0);
Self.Context[0] := $6a09e667;
Self.Context[1] := $bb67ae85;
Self.Context[2] := $3c6ef372;
Self.Context[3] := $a54ff53a;
Self.Context[4] := $510e527f;
Self.Context[5] := $9b05688c;
Self.Context[6] := $1f83d9ab;
Self.Context[7] := $5be0cd19;
end;
procedure TSHA256.Compress;
// Actual hashing function
const
K: array[0..63] of UInt32 = (
$428a2f98, $71374491, $b5c0fbcf, $e9b5dba5, $3956c25b, $59f111f1,
$923f82a4, $ab1c5ed5, $d807aa98, $12835b01, $243185be, $550c7dc3,
$72be5d74, $80deb1fe, $9bdc06a7, $c19bf174, $e49b69c1, $efbe4786,
$0fc19dc6, $240ca1cc, $2de92c6f, $4a7484aa, $5cb0a9dc, $76f988da,
$983e5152, $a831c66d, $b00327c8, $bf597fc7, $c6e00bf3, $d5a79147,
$06ca6351, $14292967, $27b70a85, $2e1b2138, $4d2c6dfc, $53380d13,
$650a7354, $766a0abb, $81c2c92e, $92722c85, $a2bfe8a1, $a81a664b,
$c24b8b70, $c76c51a3, $d192e819, $d6990624, $f40e3585, $106aa070,
$19a4c116, $1e376c08, $2748774c, $34b0bcb5, $391c0cb3, $4ed8aa4a,
$5b9cca4f, $682e6ff3, $748f82ee, $78a5636f, $84c87814, $8cc70208,
$90befffa, $a4506ceb, $bef9a3f7, $c67178f2);
Type
TBuf64 = array[0..63] of UInt32;
var
A, B, C, D, E, F, G, H: UInt32;
W: TBuf64;
I: UInt32;
t1, t2: UInt32;
begin
w:=Default(TBuf64);
// Calculate "expanded message blocks"
Move(HashBuffer, W, Sizeof(HashBuffer));
for I := 0 to 15 do
W[I] := SwapEndian(W[I]);
for I := 16 to 63 do
W[I] := (((W[I-2] shr 17) or(W[I-2] shl 15)) xor ((W[I-2] shr 19) or (W[I-2] shl 13))
xor (W[I-2] shr 10))+W[I-7]+(((W[I-15] shr 7) or (W[I-15] shl 25))
xor ((W[I-15] shr 18) or (W[I-15] shl 14)) xor (W[I-15] shr 3))+W[I-16];
A := Context[0]; B := Context[1]; C := Context[2]; D := Context[3]; E := Context[4]; F := Context[5]; G := Context[6]; H := Context[7];
for I := 0 to High(W) do
begin
t1 := H+(((E shr 6) or (E shl 26)) xor ((E shr 11) or (E shl 21)) xor ((E shr 25) or (E shl 7)))+((E and F) xor (not E and G))+K[I]+W[I];
t2 := (((A shr 2) or (A shl 30)) xor ((A shr 13) or (A shl 19)) xor ((A shr 22) xor (A shl 10)))+((A and B) xor (A and C) xor (B and C));
H := G; G := F; F := E; E := D+t1;
D := C; C := B; B := A; A := t1+t2;
end;
Inc(Context[0], A);
Inc(Context[1], B);
Inc(Context[2], C);
Inc(Context[3], D);
Inc(Context[4], E);
Inc(Context[5], F);
Inc(Context[6], G);
Inc(Context[7], H);
end;
type
TInt64Rec = packed record
case Integer of
0: (Lo, Hi: UInt32);
1: (QuadPart: Int64);
end;
procedure TSHA256.Final;
begin
// 1. append bit '1' after Buffer
HashBuffer[Self.Index] := $80;
FillChar(HashBuffer[Self.Index+1], SizeOf(HashBuffer)-Self.Index-1, 0);
// 2. Compress if more than 448 bits, (no room for 64 bit length)
if Self.Index >= 56 then
begin
Compress;
FillChar(HashBuffer, SizeOf(HashBuffer), 0);
end;
// Write 64 bit Buffer length into the last bits of the last block
// (in big endian format) and do a final compress
PUInt32(@HashBuffer[56])^ := SwapEndian(TInt64Rec(TotalLength).Hi);
PUInt32(@HashBuffer[60])^ := SwapEndian(TInt64Rec(TotalLength).Lo);
Compress;
Context[0] := SwapEndian(Context[0]);
Context[1] := SwapEndian(Context[1]);
Context[2] := SwapEndian(Context[2]);
Context[3] := SwapEndian(Context[3]);
Context[4] := SwapEndian(Context[4]);
Context[5] := SwapEndian(Context[5]);
Context[6] := SwapEndian(Context[6]);
Context[7] := SwapEndian(Context[7]);
Move(Context, Digest, Sizeof(Context));
// Self.Init; // uncomment if you need security protection against memory inspection
end;
function TSHA256.IsEqual(const ADigest: TSHA256Digest): Boolean;
var
Left, Right: TBytes;
begin
Left:=BytesFromVar(@ADigest, SizeOf(ADigest));
Right:=BytesFromVar(@Self.Digest, SizeOf(Self.Digest));
Result:=CompareMem(Pointer(Left), Pointer(Right),Length(Left));
end;
procedure TSHA256.Update(PBuf: PByte; Size: UInt32);
var
Len: UInt32;
begin
Inc(TotalLength, Int64(UInt32(Size)) * 8);
while Size > 0 do
begin
if (Sizeof(HashBuffer)-Self.Index) <= UInt32(Size) then
begin
Len := Sizeof(HashBuffer)-Self.Index;
Move(PBuf^, HashBuffer[Self.Index], Len);
Dec(Size, Len);
Inc(PBuf, Len);
Compress;
Self.Index := 0;
end else
begin
Move(PBuf^, HashBuffer[Self.Index], Size);
Inc(Self.Index, Size);
Size := 0;
end;
end;
end;
procedure TSHA256.Update(const Value: TBytes);
begin
Update(PByte(Value), System.Length(Value));
end;
// @Result[64]
// 'abc' -> 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
procedure TSHA256.OutputHexa(out Result: AnsiString);
begin
BytesToHexStr(Result,PByte(@Self.Digest),SizeOf(Self.Digest));
end;
// @Result[32]
class procedure TSHA256.DigestBytes(const Value: TBytes; out Result: TBytes);
var
lSHA256: TSHA256;
begin
lSHA256.Init;
lSHA256.Update(Value);
lSHA256.Final;
BytesFromVar(Result, @lSHA256.Digest[0], SizeOf(lSHA256.Digest));
end;
class procedure TSHA256.DigestBase64(const Value: TBytes; const IsURL: Boolean; out Result: AnsiString);
var
S : TBytes;
lSHA256: TSHA256;
begin
lSHA256.Init;
lSHA256.Update(Value);
lSHA256.Final;
BytesFromVar(S, @lSHA256.Digest[0], SizeOf(lSHA256.Digest));
BytesEncodeBase64(S, Result, IsURL, False, False);
end;
// @Result[64]
Class procedure TSHA256.DigestHexa(const Value: TBytes; out Result: AnsiString);
var
SHA256: TSHA256;
begin
SHA256.Init;
SHA256.Update(Value);
SHA256.Final;
SHA256.OutputHexa(Result);
end;
class function TSHA256.HMAC(Key: PByte; KeySize: UInt32; Data: PByte; DataSize: UInt32; var aDigest: TSHA256Digest): Boolean;
begin
Result := HMAC(Key, KeySize, Data, DataSize, nil, 0, nil, 0, aDigest);
end;
{Generate a SHA256 HMAC (Hashed Message Authentication Code) using the Key and Data
The SHA256 HMAC algorithm is:
SHA256(Key xor oPad, SHA256(Key xor iPad, Data))
Where iPad is the byte $36 repeated 64 times
oPad is the byte $5c repeated 64 times
If Key is more than 64 bytes it will be hashed to Key = SHA256(Key) instead
If Key is less than 64 bytes it will be padded with zeros }
class function TSHA256.HMAC(Key: PByte; KeySize: UInt32; Data: PByte; DataSize: UInt32; Data2: PByte; DataSize2: UInt32; Data3: PByte; DataSize3: UInt32; var aDigest: TSHA256Digest): Boolean;
Type
TBuf64 = array[0..63] of Byte;
var
Count: UInt32;
KeyBuffer, PadBuffer: TBuf64;
SHA256, SHA256_: TSHA256;
begin
Result:=False;
if Key = nil then
Exit;
if Data = nil then
Exit;
KeyBuffer:=Default(TBuf64);
SHA256.Init;
if KeySize > 64 then
begin
SHA256.Update(Key, KeySize);
SHA256.Final;
System.Move(SHA256.Digest[0], KeyBuffer[0], SizeOf(SHA256.Digest));
end else
System.Move(Key^, KeyBuffer[0], KeySize);
// XOR the key buffer with the iPad value
for Count := 0 to 63 do
PadBuffer[Count] := KeyBuffer[Count] xor $36;
SHA256.Init;
SHA256.Update(@PadBuffer, SizeOf(PadBuffer));
SHA256.Update(Data, DataSize);
if Data2 <> nil then
SHA256.Update(Data2, DataSize2);
if Data3 <> nil then
SHA256.Update(Data3, DataSize3);
SHA256.Final;
// 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)
SHA256_.Init;
SHA256_.Update(@PadBuffer, SizeOf(PadBuffer));
SHA256_.Update(@SHA256.Digest, SizeOf(SHA256.Digest));
SHA256_.Final;
System.Move(SHA256_.Digest, aDigest, SizeOf(aDigest));
Result:=True;
end;
// @Result[64]
class function TSHA256.HMACHexa(const Key, Data: TBytes; out SignatureHexa: AnsiString): Boolean; overload;
var
aDigest: TSHA256Digest;
S: TBytes;
begin
aDigest:=Default(TSHA256Digest);
Result := HMAC(PByte(Key),Length(Key), PByte(Data), Length(Data), aDigest);
BytesFromVar(S, @aDigest[0], SizeOf(aDigest));
BytesToHexStr(SignatureHexa,S);
end;
class procedure TSHA256.Stream(aStream: TStream; out aDigest: TSHA256Digest);
const
BUFFER_SIZE = 64*1024;
var
aLen : LongInt;
Buffer: TBytes;
SHA256: TSHA256;
begin
Buffer:=Nil;
SHA256.Init;
SetLength(Buffer,BUFFER_SIZE);
repeat
aLen:=aStream.Read(Buffer, BUFFER_SIZE);
if aLen = 0 then
Break;
SHA256.Update(PByte(Buffer),aLen);
until aLen=0;
SHA256.Final;
aDigest:=SHA256.Digest;
end;
class function TSHA256.Stream(aStream: TStream): TSHA256Digest;
begin
Stream(aStream,Result);
end;
class procedure TSHA256.StreamHexa(aStream: TStream; out Result: AnsiString);
Var
B : TBytes;
aDigest : TSHA256Digest;
begin
Stream(aStream,aDigest);
BytesFromVar(B,@aDigest,SizeOf(TSHA256Digest));
BytesToHexStr(Result,B);
end;
class function TSHA256.StreamHexa(aStream: TStream): AnsiString;
begin
Result:='';
StreamHexa(aStream,Result);
end;
class procedure TSHA256.StreamBase64(aStream: TStream; isURL : Boolean; out Result: AnsiString);
Var
B : TBytes;
aDigest : TSHA256Digest;
begin
Stream(aStream,aDigest);
BytesFromVar(B,@aDigest,SizeOf(TSHA256Digest));
BytesEncodeBase64(B,Result,isUrl,False,False);
end;
class Function TSHA256.StreamBase64(aStream: TStream; isURL : Boolean): AnsiString;
begin
Result:='';
StreamBase64(aStream,isURL,Result);
end;
class function TSHA256.HKDF(const Salt, IKM, Info: TBytes; var Output: TBytes; const DesiredLen: Integer): Boolean;
var
PRK, T: TSHA256Digest;
Round: Byte;
begin
PRK:=Default(TSHA256Digest);
T:=Default(TSHA256Digest);
Result := HMAC(PByte(Salt), Length(Salt), PByte(IKM), Length(IKM), PRK);
if not Result then
Exit;
Round := 1;
while Length(Output) < DesiredLen do
begin
if Length(Output) = 0 then
Result := HMAC(@PRK, SizeOf(PRK), PByte(Info), Length(Info), @Round, SizeOf(Round), nil, 0, T)
else
Result := HMAC(@PRK, SizeOf(PRK), @T, SizeOf(T), PByte(Info), Length(Info), @Round, SizeOf(Round), T);
if not Result then
Exit;
Inc(Round);
Output:=Concat(OutPut,BytesFromVar(@T,SizeOf(T)));
if Length(Output) >= DesiredLen then
Break;
end;
SetLength(Output,DesiredLen);
end;
end.