Merge branch 'feature/stdlib-hashing'
feat(stdlib): add SHA-256, MD5, HMAC-SHA256, ConstantTimeEqual to Security.Crypto
This commit is contained in:
commit
77197739de
55
compiler/src/test/pascal/cp.test.e2e.crypto.pas
Normal file
55
compiler/src/test/pascal/cp.test.e2e.crypto.pas
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
Blaise - An Object Pascal Compiler
|
||||
Copyright (c) 2026 Graeme Geldenhuys
|
||||
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
||||
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
||||
See LICENSE file in the project root for full license terms.
|
||||
}
|
||||
|
||||
unit cp.test.e2e.crypto;
|
||||
|
||||
{ E2E smoke test for the Security.Crypto stdlib unit.
|
||||
Guards against native-backend miscompilation of the hash
|
||||
implementations (the Ord(S[I]) precedent). }
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
blaise.testing, cp.test.e2e.base;
|
||||
|
||||
type
|
||||
[Threaded]
|
||||
TE2ECryptoTests = class(TE2ETestCase)
|
||||
protected
|
||||
procedure SetUp; override;
|
||||
published
|
||||
procedure TestRun_Sha256Hex_Empty;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
procedure TE2ECryptoTests.SetUp;
|
||||
begin
|
||||
inherited SetUp();
|
||||
SetUpScratch('compiler/target/test-e2e-crypto');
|
||||
end;
|
||||
|
||||
procedure TE2ECryptoTests.TestRun_Sha256Hex_Empty;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertRTLRunsOnAll(
|
||||
'''
|
||||
program P;
|
||||
uses Security.Crypto;
|
||||
begin
|
||||
WriteLn(Sha256Hex(''));
|
||||
end.
|
||||
''',
|
||||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + LineEnding,
|
||||
0);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TE2ECryptoTests);
|
||||
|
||||
end.
|
||||
|
|
@ -16,6 +16,10 @@
|
|||
decisions. It remains required for interop where a protocol mandates it
|
||||
(e.g. the WebSocket opening handshake, Git object ids).
|
||||
|
||||
NB: MD5 is cryptographically broken and must not be used for new security
|
||||
decisions. It remains required for legacy interop where a protocol or
|
||||
existing system mandates it (e.g. content checksums, legacy API signatures).
|
||||
|
||||
Base64 lives in Encoding.Base64, not here: it is a text encoding, not crypto.
|
||||
Compose them at the call site, e.g. Base64Encode(Sha1(Key + GUID)).
|
||||
|
||||
|
|
@ -33,10 +37,33 @@ function Sha1(const AData: string): string;
|
|||
{ SHA-1 digest as a 40-character lower-case hex string. }
|
||||
function Sha1Hex(const AData: string): string;
|
||||
|
||||
{ Raw 32-byte SHA-256 digest of AData (treated as raw bytes). }
|
||||
function Sha256(const AData: string): string;
|
||||
|
||||
{ SHA-256 digest as a 64-character lower-case hex string. }
|
||||
function Sha256Hex(const AData: string): string;
|
||||
|
||||
{ Raw 16-byte MD5 digest of AData (treated as raw bytes). }
|
||||
function Md5(const AData: string): string;
|
||||
|
||||
{ MD5 digest as a 32-character lower-case hex string. }
|
||||
function Md5Hex(const AData: string): string;
|
||||
|
||||
{ HMAC-SHA256 (RFC 2104 / RFC 4231). Returns raw 32-byte MAC.
|
||||
Keys longer than 64 bytes (SHA-256 block size) are hashed first. }
|
||||
function HmacSha256(const AKey, AData: string): string;
|
||||
|
||||
{ HMAC-SHA256 as a 64-character lower-case hex string. }
|
||||
function HmacSha256Hex(const AKey, AData: string): string;
|
||||
|
||||
{ Timing-safe comparison. XOR-accumulates all bytes up to the shorter
|
||||
length, then ORs in a length-mismatch flag. Never short-circuits. }
|
||||
function ConstantTimeEqual(const A, B: string): Boolean;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
Classes, StrUtils;
|
||||
StrUtils;
|
||||
|
||||
const
|
||||
MASK32 = $FFFFFFFF;
|
||||
|
|
@ -46,6 +73,29 @@ begin
|
|||
Result := ((V shl ABits) or (V shr (32 - ABits))) and MASK32;
|
||||
end;
|
||||
|
||||
function Rotr32(V: UInt32; ABits: Integer): UInt32;
|
||||
begin
|
||||
Result := ((V shr ABits) or (V shl (32 - ABits))) and MASK32;
|
||||
end;
|
||||
|
||||
function DigestToHex(const ARaw: string): string;
|
||||
var
|
||||
SB: TStringBuilder;
|
||||
I, B: Integer;
|
||||
const
|
||||
Hex = '0123456789abcdef';
|
||||
begin
|
||||
SB := TStringBuilder.Create();
|
||||
for I := 0 to Length(ARaw) - 1 do
|
||||
begin
|
||||
B := Byte(ARaw[I]);
|
||||
SB.AppendByte(Byte(Hex[B div 16]));
|
||||
SB.AppendByte(Byte(Hex[B mod 16]));
|
||||
end;
|
||||
Result := SB.ToString();
|
||||
SB.Free();
|
||||
end;
|
||||
|
||||
function Sha1(const AData: string): string;
|
||||
var
|
||||
H0, H1, H2, H3, H4: UInt32;
|
||||
|
|
@ -155,23 +205,326 @@ begin
|
|||
end;
|
||||
|
||||
function Sha1Hex(const AData: string): string;
|
||||
var
|
||||
Raw: string;
|
||||
SB: TStringBuilder;
|
||||
I, B: Integer;
|
||||
const
|
||||
Hex = '0123456789abcdef';
|
||||
begin
|
||||
Raw := Sha1(AData);
|
||||
Result := DigestToHex(Sha1(AData));
|
||||
end;
|
||||
|
||||
function Sha256(const AData: string): string;
|
||||
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
|
||||
);
|
||||
var
|
||||
H0, H1, H2, H3, H4, H5, H6, H7: UInt32;
|
||||
MsgLen, TotalBits: Int64;
|
||||
PadLen, I, T, ChunkStart, NumChunks, C: Integer;
|
||||
Msg: array[0..63] of Byte;
|
||||
W: array[0..63] of UInt32;
|
||||
A, B, Cc, D, E, F, G, Hh: UInt32;
|
||||
S0, S1, Ch, Maj, Temp1, Temp2: UInt32;
|
||||
PData: string;
|
||||
SB, OutSB: TStringBuilder;
|
||||
begin
|
||||
MsgLen := Length(AData);
|
||||
TotalBits := MsgLen * 8;
|
||||
|
||||
PadLen := 56 - ((MsgLen + 1) mod 64);
|
||||
if PadLen < 0 then
|
||||
PadLen := PadLen + 64;
|
||||
|
||||
SB := TStringBuilder.Create();
|
||||
for I := 0 to Length(Raw) - 1 do
|
||||
begin
|
||||
B := Byte(Raw[I]);
|
||||
SB.AppendByte(Byte(Hex[B div 16])); { Hex is 0-based in Blaise }
|
||||
SB.AppendByte(Byte(Hex[B mod 16]));
|
||||
end;
|
||||
Result := SB.ToString();
|
||||
SB.Append(AData);
|
||||
SB.AppendByte(128);
|
||||
for I := 1 to PadLen do
|
||||
SB.AppendByte(0);
|
||||
for I := 7 downto 0 do
|
||||
SB.AppendByte((TotalBits shr (I * 8)) and $FF);
|
||||
PData := SB.ToString();
|
||||
SB.Free();
|
||||
|
||||
H0 := $6a09e667;
|
||||
H1 := $bb67ae85;
|
||||
H2 := $3c6ef372;
|
||||
H3 := $a54ff53a;
|
||||
H4 := $510e527f;
|
||||
H5 := $9b05688c;
|
||||
H6 := $1f83d9ab;
|
||||
H7 := $5be0cd19;
|
||||
|
||||
NumChunks := Length(PData) div 64;
|
||||
for C := 0 to NumChunks - 1 do
|
||||
begin
|
||||
ChunkStart := C * 64;
|
||||
for I := 0 to 63 do
|
||||
Msg[I] := Byte(PData[ChunkStart + I]);
|
||||
|
||||
for T := 0 to 15 do
|
||||
W[T] := ((UInt32(Msg[T * 4]) shl 24) or
|
||||
(UInt32(Msg[T * 4 + 1]) shl 16) or
|
||||
(UInt32(Msg[T * 4 + 2]) shl 8) or
|
||||
UInt32(Msg[T * 4 + 3])) and MASK32;
|
||||
|
||||
for T := 16 to 63 do
|
||||
begin
|
||||
S0 := (Rotr32(W[T-15], 7) xor Rotr32(W[T-15], 18) xor (W[T-15] shr 3)) and MASK32;
|
||||
S1 := (Rotr32(W[T-2], 17) xor Rotr32(W[T-2], 19) xor (W[T-2] shr 10)) and MASK32;
|
||||
W[T] := (W[T-16] + S0 + W[T-7] + S1) and MASK32;
|
||||
end;
|
||||
|
||||
A := H0; B := H1; Cc := H2; D := H3;
|
||||
E := H4; F := H5; G := H6; Hh := H7;
|
||||
|
||||
for T := 0 to 63 do
|
||||
begin
|
||||
S1 := (Rotr32(E, 6) xor Rotr32(E, 11) xor Rotr32(E, 25)) and MASK32;
|
||||
Ch := ((E and F) xor (((not E) and MASK32) and G)) and MASK32;
|
||||
Temp1 := (Hh + S1 + Ch + K[T] + W[T]) and MASK32;
|
||||
S0 := (Rotr32(A, 2) xor Rotr32(A, 13) xor Rotr32(A, 22)) and MASK32;
|
||||
Maj := ((A and B) xor (A and Cc) xor (B and Cc)) and MASK32;
|
||||
Temp2 := (S0 + Maj) and MASK32;
|
||||
|
||||
Hh := G;
|
||||
G := F;
|
||||
F := E;
|
||||
E := (D + Temp1) and MASK32;
|
||||
D := Cc;
|
||||
Cc := B;
|
||||
B := A;
|
||||
A := (Temp1 + Temp2) and MASK32;
|
||||
end;
|
||||
|
||||
H0 := (H0 + A) and MASK32;
|
||||
H1 := (H1 + B) and MASK32;
|
||||
H2 := (H2 + Cc) and MASK32;
|
||||
H3 := (H3 + D) and MASK32;
|
||||
H4 := (H4 + E) and MASK32;
|
||||
H5 := (H5 + F) and MASK32;
|
||||
H6 := (H6 + G) and MASK32;
|
||||
H7 := (H7 + Hh) and MASK32;
|
||||
end;
|
||||
|
||||
OutSB := TStringBuilder.Create();
|
||||
OutSB.AppendByte((H0 shr 24) and $FF); OutSB.AppendByte((H0 shr 16) and $FF);
|
||||
OutSB.AppendByte((H0 shr 8) and $FF); OutSB.AppendByte(H0 and $FF);
|
||||
OutSB.AppendByte((H1 shr 24) and $FF); OutSB.AppendByte((H1 shr 16) and $FF);
|
||||
OutSB.AppendByte((H1 shr 8) and $FF); OutSB.AppendByte(H1 and $FF);
|
||||
OutSB.AppendByte((H2 shr 24) and $FF); OutSB.AppendByte((H2 shr 16) and $FF);
|
||||
OutSB.AppendByte((H2 shr 8) and $FF); OutSB.AppendByte(H2 and $FF);
|
||||
OutSB.AppendByte((H3 shr 24) and $FF); OutSB.AppendByte((H3 shr 16) and $FF);
|
||||
OutSB.AppendByte((H3 shr 8) and $FF); OutSB.AppendByte(H3 and $FF);
|
||||
OutSB.AppendByte((H4 shr 24) and $FF); OutSB.AppendByte((H4 shr 16) and $FF);
|
||||
OutSB.AppendByte((H4 shr 8) and $FF); OutSB.AppendByte(H4 and $FF);
|
||||
OutSB.AppendByte((H5 shr 24) and $FF); OutSB.AppendByte((H5 shr 16) and $FF);
|
||||
OutSB.AppendByte((H5 shr 8) and $FF); OutSB.AppendByte(H5 and $FF);
|
||||
OutSB.AppendByte((H6 shr 24) and $FF); OutSB.AppendByte((H6 shr 16) and $FF);
|
||||
OutSB.AppendByte((H6 shr 8) and $FF); OutSB.AppendByte(H6 and $FF);
|
||||
OutSB.AppendByte((H7 shr 24) and $FF); OutSB.AppendByte((H7 shr 16) and $FF);
|
||||
OutSB.AppendByte((H7 shr 8) and $FF); OutSB.AppendByte(H7 and $FF);
|
||||
Result := OutSB.ToString();
|
||||
OutSB.Free();
|
||||
end;
|
||||
|
||||
function Sha256Hex(const AData: string): string;
|
||||
begin
|
||||
Result := DigestToHex(Sha256(AData));
|
||||
end;
|
||||
|
||||
function Md5(const AData: string): string;
|
||||
const
|
||||
T: array[0..63] of UInt32 = (
|
||||
$d76aa478, $e8c7b756, $242070db, $c1bdceee,
|
||||
$f57c0faf, $4787c62a, $a8304613, $fd469501,
|
||||
$698098d8, $8b44f7af, $ffff5bb1, $895cd7be,
|
||||
$6b901122, $fd987193, $a679438e, $49b40821,
|
||||
$f61e2562, $c040b340, $265e5a51, $e9b6c7aa,
|
||||
$d62f105d, $02441453, $d8a1e681, $e7d3fbc8,
|
||||
$21e1cde6, $c33707d6, $f4d50d87, $455a14ed,
|
||||
$a9e3e905, $fcefa3f8, $676f02d9, $8d2a4c8a,
|
||||
$fffa3942, $8771f681, $6d9d6122, $fde5380c,
|
||||
$a4beea44, $4bdecfa9, $f6bb4b60, $bebfbc70,
|
||||
$289b7ec6, $eaa127fa, $d4ef3085, $04881d05,
|
||||
$d9d4d039, $e6db99e5, $1fa27cf8, $c4ac5665,
|
||||
$f4292244, $432aff97, $ab9423a7, $fc93a039,
|
||||
$655b59c3, $8f0ccc92, $ffeff47d, $85845dd1,
|
||||
$6fa87e4f, $fe2ce6e0, $a3014314, $4e0811a1,
|
||||
$f7537e82, $bd3af235, $2ad7d2bb, $eb86d391
|
||||
);
|
||||
S: array[0..63] of Integer = (
|
||||
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
|
||||
);
|
||||
var
|
||||
H0, H1, H2, H3: UInt32;
|
||||
MsgLen, TotalBits: Int64;
|
||||
PadLen, I, J, Chunk, ChunkStart, NumChunks, G: Integer;
|
||||
M: array[0..15] of UInt32;
|
||||
A, B, Cc, D, F, Tmp: UInt32;
|
||||
PData: string;
|
||||
SB, OutSB: TStringBuilder;
|
||||
begin
|
||||
MsgLen := Length(AData);
|
||||
TotalBits := MsgLen * 8;
|
||||
|
||||
PadLen := 56 - ((MsgLen + 1) mod 64);
|
||||
if PadLen < 0 then
|
||||
PadLen := PadLen + 64;
|
||||
|
||||
SB := TStringBuilder.Create();
|
||||
SB.Append(AData);
|
||||
SB.AppendByte(128);
|
||||
for I := 1 to PadLen do
|
||||
SB.AppendByte(0);
|
||||
{ MD5 uses LITTLE-ENDIAN 64-bit bit length. }
|
||||
for I := 0 to 7 do
|
||||
SB.AppendByte((TotalBits shr (I * 8)) and $FF);
|
||||
PData := SB.ToString();
|
||||
SB.Free();
|
||||
|
||||
H0 := $67452301;
|
||||
H1 := $efcdab89;
|
||||
H2 := $98badcfe;
|
||||
H3 := $10325476;
|
||||
|
||||
NumChunks := Length(PData) div 64;
|
||||
for Chunk := 0 to NumChunks - 1 do
|
||||
begin
|
||||
ChunkStart := Chunk * 64;
|
||||
{ Read 16 words in LITTLE-ENDIAN order. }
|
||||
for J := 0 to 15 do
|
||||
M[J] := (UInt32(Byte(PData[ChunkStart + J * 4 + 3])) shl 24) or
|
||||
(UInt32(Byte(PData[ChunkStart + J * 4 + 2])) shl 16) or
|
||||
(UInt32(Byte(PData[ChunkStart + J * 4 + 1])) shl 8) or
|
||||
UInt32(Byte(PData[ChunkStart + J * 4]));
|
||||
|
||||
A := H0; B := H1; Cc := H2; D := H3;
|
||||
|
||||
for I := 0 to 63 do
|
||||
begin
|
||||
if I < 16 then
|
||||
begin
|
||||
F := (B and Cc) or (((not B) and MASK32) and D);
|
||||
G := I;
|
||||
end
|
||||
else if I < 32 then
|
||||
begin
|
||||
F := (D and B) or (((not D) and MASK32) and Cc);
|
||||
G := (5 * I + 1) mod 16;
|
||||
end
|
||||
else if I < 48 then
|
||||
begin
|
||||
F := B xor Cc xor D;
|
||||
G := (3 * I + 5) mod 16;
|
||||
end
|
||||
else
|
||||
begin
|
||||
F := (Cc xor (B or ((not D) and MASK32))) and MASK32;
|
||||
G := (7 * I) mod 16;
|
||||
end;
|
||||
F := F and MASK32;
|
||||
Tmp := D;
|
||||
D := Cc;
|
||||
Cc := B;
|
||||
B := (B + Rotl32((A + F + T[I] + M[G]) and MASK32, S[I])) and MASK32;
|
||||
A := Tmp;
|
||||
end;
|
||||
|
||||
H0 := (H0 + A) and MASK32;
|
||||
H1 := (H1 + B) and MASK32;
|
||||
H2 := (H2 + Cc) and MASK32;
|
||||
H3 := (H3 + D) and MASK32;
|
||||
end;
|
||||
|
||||
{ Emit 16 raw bytes, LITTLE-ENDIAN per word. }
|
||||
OutSB := TStringBuilder.Create();
|
||||
OutSB.AppendByte(H0 and $FF); OutSB.AppendByte((H0 shr 8) and $FF);
|
||||
OutSB.AppendByte((H0 shr 16) and $FF); OutSB.AppendByte((H0 shr 24) and $FF);
|
||||
OutSB.AppendByte(H1 and $FF); OutSB.AppendByte((H1 shr 8) and $FF);
|
||||
OutSB.AppendByte((H1 shr 16) and $FF); OutSB.AppendByte((H1 shr 24) and $FF);
|
||||
OutSB.AppendByte(H2 and $FF); OutSB.AppendByte((H2 shr 8) and $FF);
|
||||
OutSB.AppendByte((H2 shr 16) and $FF); OutSB.AppendByte((H2 shr 24) and $FF);
|
||||
OutSB.AppendByte(H3 and $FF); OutSB.AppendByte((H3 shr 8) and $FF);
|
||||
OutSB.AppendByte((H3 shr 16) and $FF); OutSB.AppendByte((H3 shr 24) and $FF);
|
||||
Result := OutSB.ToString();
|
||||
OutSB.Free();
|
||||
end;
|
||||
|
||||
function Md5Hex(const AData: string): string;
|
||||
begin
|
||||
Result := DigestToHex(Md5(AData));
|
||||
end;
|
||||
|
||||
function HmacSha256(const AKey, AData: string): string;
|
||||
const
|
||||
BLOCK_SIZE = 64; { SHA-256 block size; SHA-512 uses 128 }
|
||||
var
|
||||
KeyBlock: string;
|
||||
IPad, OPad: TStringBuilder;
|
||||
I: Integer;
|
||||
InnerHash: string;
|
||||
begin
|
||||
if Length(AKey) > BLOCK_SIZE then
|
||||
KeyBlock := Sha256(AKey)
|
||||
else
|
||||
KeyBlock := AKey;
|
||||
|
||||
IPad := TStringBuilder.Create();
|
||||
OPad := TStringBuilder.Create();
|
||||
for I := 0 to BLOCK_SIZE - 1 do
|
||||
begin
|
||||
if I < Length(KeyBlock) then
|
||||
begin
|
||||
IPad.AppendByte(Byte(KeyBlock[I]) xor $36);
|
||||
OPad.AppendByte(Byte(KeyBlock[I]) xor $5C);
|
||||
end
|
||||
else
|
||||
begin
|
||||
IPad.AppendByte($36);
|
||||
OPad.AppendByte($5C);
|
||||
end;
|
||||
end;
|
||||
|
||||
IPad.Append(AData);
|
||||
InnerHash := Sha256(IPad.ToString());
|
||||
IPad.Free();
|
||||
|
||||
OPad.Append(InnerHash);
|
||||
Result := Sha256(OPad.ToString());
|
||||
OPad.Free();
|
||||
end;
|
||||
|
||||
function HmacSha256Hex(const AKey, AData: string): string;
|
||||
begin
|
||||
Result := DigestToHex(HmacSha256(AKey, AData));
|
||||
end;
|
||||
|
||||
function ConstantTimeEqual(const A, B: string): Boolean;
|
||||
var
|
||||
Diff, I, MinLen: Integer;
|
||||
begin
|
||||
Diff := Length(A) xor Length(B);
|
||||
MinLen := Length(A);
|
||||
if Length(B) < MinLen then
|
||||
MinLen := Length(B);
|
||||
for I := 0 to MinLen - 1 do
|
||||
Diff := Diff or (Byte(A[I]) xor Byte(B[I]));
|
||||
Result := Diff = 0;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ unit Crypto.Tests;
|
|||
interface
|
||||
|
||||
uses
|
||||
blaise.testing, Security.Crypto, Encoding.Base64;
|
||||
blaise.testing, Security.Crypto, Encoding.Base64, StrUtils;
|
||||
|
||||
type
|
||||
TCryptoTests = class(TTestCase)
|
||||
|
|
@ -22,6 +22,15 @@ type
|
|||
procedure TestSha1Hex_KnownVectors;
|
||||
procedure TestSha1_DigestLength;
|
||||
procedure TestSha1Base64_WebSocketHandshake;
|
||||
procedure TestSha256Hex_KnownVectors;
|
||||
procedure TestSha256_DigestLength;
|
||||
procedure TestSha256Hex_BoundaryVectors;
|
||||
procedure TestMd5Hex_KnownVectors;
|
||||
procedure TestMd5_DigestLength;
|
||||
procedure TestHmacSha256Hex_KnownVectors;
|
||||
procedure TestHmacSha256_DigestLength;
|
||||
procedure TestHmacSha256Hex_LongKey;
|
||||
procedure TestConstantTimeEqual;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -51,6 +60,148 @@ begin
|
|||
Base64Encode(Sha1('dGhlIHNhbXBsZSBub25jZQ==' + GUID)));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestSha256Hex_KnownVectors;
|
||||
begin
|
||||
{ NIST CAVP / FIPS 180-4 SHA-256 test vectors. }
|
||||
AssertEquals('empty',
|
||||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
||||
Sha256Hex(''));
|
||||
AssertEquals('abc',
|
||||
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
|
||||
Sha256Hex('abc'));
|
||||
AssertEquals('448-bit (56 bytes, 2-block)',
|
||||
'248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1',
|
||||
Sha256Hex('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'));
|
||||
AssertEquals('896-bit (112 bytes, multi-block)',
|
||||
'cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1',
|
||||
Sha256Hex('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu'));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestSha256_DigestLength;
|
||||
begin
|
||||
AssertEquals('raw digest is 32 bytes', 32, Length(Sha256('anything')));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestSha256Hex_BoundaryVectors;
|
||||
begin
|
||||
{ 55 bytes: padding (0x80 + 8-byte length = 9 bytes) fits in one 64-byte block. }
|
||||
AssertEquals('55-byte single-block boundary',
|
||||
'9f4390f8d30c2dd92ec9f095b65e2b9ae9b0a925a5258e241c9f1e910f734318',
|
||||
Sha256Hex('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestMd5Hex_KnownVectors;
|
||||
begin
|
||||
{ RFC 1321 Appendix A.5 — all 7 test vectors. }
|
||||
AssertEquals('empty',
|
||||
'd41d8cd98f00b204e9800998ecf8427e', Md5Hex(''));
|
||||
AssertEquals('a',
|
||||
'0cc175b9c0f1b6a831c399e269772661', Md5Hex('a'));
|
||||
AssertEquals('abc',
|
||||
'900150983cd24fb0d6963f7d28e17f72', Md5Hex('abc'));
|
||||
AssertEquals('message digest',
|
||||
'f96b697d7cb7938d525a2f31aaf161d0', Md5Hex('message digest'));
|
||||
AssertEquals('a..z',
|
||||
'c3fcd3d76192e4007dfb496cca67e13b', Md5Hex('abcdefghijklmnopqrstuvwxyz'));
|
||||
AssertEquals('A..Za..z0..9',
|
||||
'd174ab98d277d9f5a5611c2c9f419d9f',
|
||||
Md5Hex('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));
|
||||
AssertEquals('8x 1234567890',
|
||||
'57edf4a22be3c955ac49da2e2107b67a',
|
||||
Md5Hex('12345678901234567890123456789012345678901234567890123456789012345678901234567890'));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestMd5_DigestLength;
|
||||
begin
|
||||
AssertEquals('raw digest is 16 bytes', 16, Length(Md5('anything')));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestHmacSha256Hex_KnownVectors;
|
||||
var
|
||||
KeySB, DataSB: TStringBuilder;
|
||||
I: Integer;
|
||||
Key1, Data1, Key3, Data3: string;
|
||||
begin
|
||||
{ RFC 4231 Test Case 1: 20-byte key of 0x0b, data = "Hi There" }
|
||||
KeySB := TStringBuilder.Create();
|
||||
for I := 0 to 19 do
|
||||
KeySB.AppendByte($0b);
|
||||
Key1 := KeySB.ToString();
|
||||
KeySB.Free();
|
||||
AssertEquals('TC1',
|
||||
'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7',
|
||||
HmacSha256Hex(Key1, 'Hi There'));
|
||||
|
||||
{ RFC 4231 Test Case 2: key = "Jefe", data = "what do ya want for nothing?" }
|
||||
AssertEquals('TC2',
|
||||
'5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843',
|
||||
HmacSha256Hex('Jefe', 'what do ya want for nothing?'));
|
||||
|
||||
{ RFC 4231 Test Case 3: 20-byte key of 0xaa, 50-byte data of 0xdd }
|
||||
KeySB := TStringBuilder.Create();
|
||||
for I := 0 to 19 do
|
||||
KeySB.AppendByte($aa);
|
||||
Key3 := KeySB.ToString();
|
||||
KeySB.Free();
|
||||
DataSB := TStringBuilder.Create();
|
||||
for I := 0 to 49 do
|
||||
DataSB.AppendByte($dd);
|
||||
Data3 := DataSB.ToString();
|
||||
DataSB.Free();
|
||||
AssertEquals('TC3',
|
||||
'773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe',
|
||||
HmacSha256Hex(Key3, Data3));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestHmacSha256_DigestLength;
|
||||
begin
|
||||
AssertEquals('raw HMAC is 32 bytes', 32,
|
||||
Length(HmacSha256('key', 'data')));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestHmacSha256Hex_LongKey;
|
||||
var
|
||||
KeySB, DataSB: TStringBuilder;
|
||||
I: Integer;
|
||||
LongKey, Data: string;
|
||||
begin
|
||||
{ RFC 4231 Test Case 5: 131-byte key of 0xaa (> 64-byte block size),
|
||||
data = "Test Using Larger Than Block-Size Key - Hash Key First" }
|
||||
KeySB := TStringBuilder.Create();
|
||||
for I := 0 to 130 do
|
||||
KeySB.AppendByte($aa);
|
||||
LongKey := KeySB.ToString();
|
||||
KeySB.Free();
|
||||
AssertEquals('TC5 long key',
|
||||
'60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54',
|
||||
HmacSha256Hex(LongKey, 'Test Using Larger Than Block-Size Key - Hash Key First'));
|
||||
|
||||
{ RFC 4231 Test Case 6: same 131-byte key,
|
||||
data = "This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm." }
|
||||
AssertEquals('TC6 long key+data',
|
||||
'9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2',
|
||||
HmacSha256Hex(LongKey,
|
||||
'This is a test using a larger than block-size key and a ' +
|
||||
'larger than block-size data. The key needs to be hashed ' +
|
||||
'before being used by the HMAC algorithm.'));
|
||||
end;
|
||||
|
||||
procedure TCryptoTests.TestConstantTimeEqual;
|
||||
begin
|
||||
AssertTrue('equal strings', ConstantTimeEqual('abc', 'abc'));
|
||||
AssertTrue('both empty', ConstantTimeEqual('', ''));
|
||||
AssertFalse('different content', ConstantTimeEqual('abc', 'abd'));
|
||||
AssertFalse('different lengths', ConstantTimeEqual('abc', 'ab'));
|
||||
AssertFalse('one empty', ConstantTimeEqual('abc', ''));
|
||||
AssertFalse('other empty', ConstantTimeEqual('', 'abc'));
|
||||
AssertTrue('long equal', ConstantTimeEqual(
|
||||
'The quick brown fox jumps over the lazy dog',
|
||||
'The quick brown fox jumps over the lazy dog'));
|
||||
AssertFalse('long differ last byte', ConstantTimeEqual(
|
||||
'The quick brown fox jumps over the lazy dog',
|
||||
'The quick brown fox jumps over the lazy doh'));
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TCryptoTests);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue