You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8907 8e941d3f-bd1b-0410-a28a-d453659cc2b4
435 lines
13 KiB
ObjectPascal
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.
|
|
|