feat(stdlib): add SHA-256, MD5, HMAC-SHA256, ConstantTimeEqual to Security.Crypto

Expand the crypto primitives unit with four new capabilities:

- SHA-256 / Sha256Hex (FIPS 180-4, Rotr32 helper, 64-round K table)
- MD5 / Md5Hex (RFC 1321, little-endian padding and output, T/S tables)
- HMAC-SHA256 / HmacSha256Hex (RFC 2104, key>block-size hashing)
- ConstantTimeEqual (timing-safe XOR-accumulate comparison)

Also refactors Sha1Hex to use a shared DigestToHex private helper,
adds Rotr32 alongside Rotl32, removes unused `uses Classes` import,
and adds MD5 deprecation notice to the unit header.

Tests: 12 crypto tests covering NIST CAVP (SHA-256), RFC 1321 all 7
vectors (MD5), RFC 4231 TC1-3/TC5-6 (HMAC-SHA256 incl. long-key),
and 8 ConstantTimeEqual assertions. E2E smoke test for Sha256Hex
on both backends (cp.test.e2e.crypto.pas).
This commit is contained in:
Graeme Geldenhuys 2026-07-01 01:06:30 +01:00
parent ac6e0b0d88
commit bfd43f9147
3 changed files with 575 additions and 16 deletions

View 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.

View file

@ -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.

View file

@ -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);