Files
comet/cometsha.pas
Ken Johnson bade0eb593 Comet 1.00 - Initial commit: complete standalone FidoNet TCP mailer
Standalone FidoNet mailer daemon implementing the Comet protocol
(TCP variant of the Nova protocol family) with BinkP/1.1 fallback.
Written in Free Pascal for DOS/Win/OS2/Linux/FreeBSD.

15 source files, ~10K lines:
- Protocol: length-prefixed frames, SHA-256/384/512, CRC-32,
  bidirectional transfer with sliding window, adaptive block sizing
- Session: own TCP handshake with BinkP auto-detection on port 26638
- Outbound: BSO (Binkley), FrontDoor, D'Bridge format support
- Daemon: multi-session with thread pool, outbound scanner
- Paths: DOS<->Linux bridge with case-insensitive lookup, drive mapping
- Config: INI-style with heavily documented sample (COMET.SAM)

All 17 Nova interop bug fixes baked in from the start.
18/18 tests passing (CRC-32, SHA-256/384/512, frame encode/decode).
2026-03-29 20:02:37 -07:00

804 lines
23 KiB
ObjectPascal

{
Comet - Direct TCP File Transfer for FidoNet
cometsha.pas - SHA-256 and SHA-384 message digests (FIPS 180-4)
SHA-256: 32-bit state, 256-bit (32 byte) digest.
Used for file content verification (same as Nova/Xenia/Fimail).
SHA-384: 64-bit state, 384-bit (48 byte) digest.
Available for stronger hashing where needed.
Both produce big-endian digest output per FIPS standard.
SHA-256 output is byte-identical to Xenia and Fimail.
SHA-256 test vectors:
SHA256("abc") = ba7816bf 8f01cfea 414140de 5dae2223
b00361a3 96177a9c b410ff61 f20015ad
SHA-384 test vectors:
SHA384("abc") = cb00753f45a35e8b b5a03d699ac65007
272c32ab0eded163 1a8b605a43ff5bed
8086072ba1e7cc23 58baeca134c825a7
Copyright (C) 2026 Ken Johnson
License: GPL-2.0
}
unit cometsha;
{$mode objfpc}{$H+}
interface
type
TSHA256Digest = array[0..31] of Byte;
TSHA256Context = record
State: array[0..7] of LongWord;
Count: array[0..7] of Byte; { 64-bit bit count, big-endian }
Done: Boolean;
DataBuf: array[0..63] of Byte;
DataLen: Byte;
end;
{ Incremental API }
procedure SHA256Init(var Ctx: TSHA256Context);
procedure SHA256Update(var Ctx: TSHA256Context; const Buf; Len: LongWord);
procedure SHA256Final(var Ctx: TSHA256Context; out Digest: TSHA256Digest);
{ One-shot convenience }
function SHA256Buffer(const Buf; Len: LongWord): TSHA256Digest;
function SHA256String(const S: string): TSHA256Digest;
{ Digest to lowercase hex string }
function SHA256DigestToHex(const Digest: TSHA256Digest): string;
{ Compare two digests, returns True if identical }
function SHA256DigestEqual(const A, B: TSHA256Digest): Boolean;
{ ---- SHA-512 family (SHA-384 and SHA-512 share the same core) ---- }
type
TSHA384Digest = array[0..47] of Byte;
TSHA512Digest = array[0..63] of Byte;
{ Shared context for SHA-384 and SHA-512 (same 64-bit core, 128-byte blocks) }
TSHA512Context = record
State: array[0..7] of QWord; { 64-bit state words }
Count: array[0..15] of Byte; { 128-bit bit count, big-endian }
Done: Boolean;
DataBuf: array[0..127] of Byte; { 128-byte block buffer }
DataLen: Byte;
end;
{ SHA-384 uses the same context type }
TSHA384Context = TSHA512Context;
{ ---- SHA-384 ---- }
procedure SHA384Init(var Ctx: TSHA384Context);
procedure SHA384Update(var Ctx: TSHA384Context; const Buf; Len: LongWord);
procedure SHA384Final(var Ctx: TSHA384Context; out Digest: TSHA384Digest);
function SHA384Buffer(const Buf; Len: LongWord): TSHA384Digest;
function SHA384String(const S: string): TSHA384Digest;
function SHA384DigestToHex(const Digest: TSHA384Digest): string;
function SHA384DigestEqual(const A, B: TSHA384Digest): Boolean;
{ ---- SHA-512 ---- }
procedure SHA512Init(var Ctx: TSHA512Context);
procedure SHA512Update(var Ctx: TSHA512Context; const Buf; Len: LongWord);
procedure SHA512Final(var Ctx: TSHA512Context; out Digest: TSHA512Digest);
function SHA512Buffer(const Buf; Len: LongWord): TSHA512Digest;
function SHA512String(const S: string): TSHA512Digest;
function SHA512DigestToHex(const Digest: TSHA512Digest): string;
function SHA512DigestEqual(const A, B: TSHA512Digest): Boolean;
implementation
const
{ Initial hash values (FIPS 180-4 section 5.3.3) }
H0 = LongWord($6a09e667); H1 = LongWord($bb67ae85);
H2 = LongWord($3c6ef372); H3 = LongWord($a54ff53a);
H4 = LongWord($510e527f); H5 = LongWord($9b05688c);
H6 = LongWord($1f83d9ab); H7 = LongWord($5be0cd19);
{ Round constants (FIPS 180-4 section 4.2.2) }
K: array[0..63] of LongWord = (
$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
);
function ROTR(X: LongWord; N: Byte): LongWord; inline;
begin
Result := (X shr N) or (X shl (32 - N));
end;
function CH(X, Y, Z: LongWord): LongWord; inline;
begin
Result := (X and Y) xor ((not X) and Z);
end;
function MAJ(X, Y, Z: LongWord): LongWord; inline;
begin
Result := (X and Y) xor (X and Z) xor (Y and Z);
end;
function BSIG0(X: LongWord): LongWord; inline;
begin
Result := ROTR(X, 2) xor ROTR(X, 13) xor ROTR(X, 22);
end;
function BSIG1(X: LongWord): LongWord; inline;
begin
Result := ROTR(X, 6) xor ROTR(X, 11) xor ROTR(X, 25);
end;
function SSIG0(X: LongWord): LongWord; inline;
begin
Result := ROTR(X, 7) xor ROTR(X, 18) xor (X shr 3);
end;
function SSIG1(X: LongWord): LongWord; inline;
begin
Result := ROTR(X, 17) xor ROTR(X, 19) xor (X shr 10);
end;
procedure SHA256Block(var Ctx: TSHA256Context; Data: PByte);
var
W: array[0..63] of LongWord;
A, B, C, D, E, F, G, HH: LongWord;
T1, T2: LongWord;
T: Integer;
begin
{ Prepare message schedule (big-endian byte order) }
for T := 0 to 15 do
W[T] := (LongWord(Data[T * 4]) shl 24) or
(LongWord(Data[T * 4 + 1]) shl 16) or
(LongWord(Data[T * 4 + 2]) shl 8) or
LongWord(Data[T * 4 + 3]);
for T := 16 to 63 do
W[T] := SSIG1(W[T - 2]) + W[T - 7] + SSIG0(W[T - 15]) + W[T - 16];
{ Initialize working variables }
A := Ctx.State[0]; B := Ctx.State[1];
C := Ctx.State[2]; D := Ctx.State[3];
E := Ctx.State[4]; F := Ctx.State[5];
G := Ctx.State[6]; HH := Ctx.State[7];
{ 64 rounds }
for T := 0 to 63 do
begin
T1 := HH + BSIG1(E) + CH(E, F, G) + K[T] + W[T];
T2 := BSIG0(A) + MAJ(A, B, C);
HH := G; G := F; F := E; E := D + T1;
D := C; C := B; B := A; A := T1 + T2;
end;
{ Add to running hash }
Ctx.State[0] := Ctx.State[0] + A;
Ctx.State[1] := Ctx.State[1] + B;
Ctx.State[2] := Ctx.State[2] + C;
Ctx.State[3] := Ctx.State[3] + D;
Ctx.State[4] := Ctx.State[4] + E;
Ctx.State[5] := Ctx.State[5] + F;
Ctx.State[6] := Ctx.State[6] + G;
Ctx.State[7] := Ctx.State[7] + HH;
end;
procedure SHA256Init(var Ctx: TSHA256Context);
begin
Ctx.State[0] := H0; Ctx.State[1] := H1;
Ctx.State[2] := H2; Ctx.State[3] := H3;
Ctx.State[4] := H4; Ctx.State[5] := H5;
Ctx.State[6] := H6; Ctx.State[7] := H7;
FillChar(Ctx.Count, 8, 0);
Ctx.Done := False;
Ctx.DataLen := 0;
end;
procedure SHA256Update(var Ctx: TSHA256Context; const Buf; Len: LongWord);
var
P: PByte;
I: LongWord;
BitLenLo, BitLenHi: LongWord;
begin
if Ctx.Done or (Len = 0) then Exit;
P := @Buf;
{ Update 64-bit bit count (big-endian in Count[0..7]) }
BitLenLo := (LongWord(Ctx.Count[4]) shl 24) or
(LongWord(Ctx.Count[5]) shl 16) or
(LongWord(Ctx.Count[6]) shl 8) or
LongWord(Ctx.Count[7]);
BitLenHi := (LongWord(Ctx.Count[0]) shl 24) or
(LongWord(Ctx.Count[1]) shl 16) or
(LongWord(Ctx.Count[2]) shl 8) or
LongWord(Ctx.Count[3]);
BitLenLo := BitLenLo + (Len shl 3);
if BitLenLo < (Len shl 3) then Inc(BitLenHi);
BitLenHi := BitLenHi + (Len shr 29);
Ctx.Count[0] := Byte(BitLenHi shr 24);
Ctx.Count[1] := Byte(BitLenHi shr 16);
Ctx.Count[2] := Byte(BitLenHi shr 8);
Ctx.Count[3] := Byte(BitLenHi);
Ctx.Count[4] := Byte(BitLenLo shr 24);
Ctx.Count[5] := Byte(BitLenLo shr 16);
Ctx.Count[6] := Byte(BitLenLo shr 8);
Ctx.Count[7] := Byte(BitLenLo);
I := 0;
{ Fill partial buffer from previous call }
if Ctx.DataLen > 0 then
begin
while (Ctx.DataLen < 64) and (I < Len) do
begin
Ctx.DataBuf[Ctx.DataLen] := P[I];
Inc(Ctx.DataLen);
Inc(I);
end;
if Ctx.DataLen = 64 then
begin
SHA256Block(Ctx, @Ctx.DataBuf[0]);
Ctx.DataLen := 0;
end;
end;
{ Process full 64-byte blocks directly from input }
while I + 64 <= Len do
begin
SHA256Block(Ctx, P + I);
Inc(I, 64);
end;
{ Buffer remaining bytes }
while I < Len do
begin
Ctx.DataBuf[Ctx.DataLen] := P[I];
Inc(Ctx.DataLen);
Inc(I);
end;
end;
procedure SHA256Final(var Ctx: TSHA256Context; out Digest: TSHA256Digest);
var
SavedCount: array[0..7] of Byte;
I: Integer;
begin
if Ctx.Done then
begin
{ Already finalized - just output state again }
for I := 0 to 7 do
begin
Digest[I * 4] := Byte(Ctx.State[I] shr 24);
Digest[I * 4 + 1] := Byte(Ctx.State[I] shr 16);
Digest[I * 4 + 2] := Byte(Ctx.State[I] shr 8);
Digest[I * 4 + 3] := Byte(Ctx.State[I]);
end;
Exit;
end;
{ Save count before padding modifies it }
Move(Ctx.Count, SavedCount, 8);
{ Pad: append 0x80, then zeros, then 64-bit bit count }
Ctx.DataBuf[Ctx.DataLen] := $80;
Inc(Ctx.DataLen);
{ If not enough room for 8-byte count, pad to 64 and process }
if Ctx.DataLen > 56 then
begin
while Ctx.DataLen < 64 do
begin
Ctx.DataBuf[Ctx.DataLen] := 0;
Inc(Ctx.DataLen);
end;
SHA256Block(Ctx, @Ctx.DataBuf[0]);
Ctx.DataLen := 0;
end;
{ Pad with zeros up to byte 56 }
while Ctx.DataLen < 56 do
begin
Ctx.DataBuf[Ctx.DataLen] := 0;
Inc(Ctx.DataLen);
end;
{ Append 64-bit bit count (big-endian) }
Move(SavedCount, Ctx.DataBuf[56], 8);
SHA256Block(Ctx, @Ctx.DataBuf[0]);
Ctx.Done := True;
{ Output digest (big-endian) }
for I := 0 to 7 do
begin
Digest[I * 4] := Byte(Ctx.State[I] shr 24);
Digest[I * 4 + 1] := Byte(Ctx.State[I] shr 16);
Digest[I * 4 + 2] := Byte(Ctx.State[I] shr 8);
Digest[I * 4 + 3] := Byte(Ctx.State[I]);
end;
end;
function SHA256Buffer(const Buf; Len: LongWord): TSHA256Digest;
var
Ctx: TSHA256Context;
begin
SHA256Init(Ctx);
SHA256Update(Ctx, Buf, Len);
SHA256Final(Ctx, Result);
end;
function SHA256String(const S: string): TSHA256Digest;
begin
if Length(S) > 0 then
Result := SHA256Buffer(S[1], LongWord(Length(S)))
else
Result := SHA256Buffer(S, 0);
end;
function SHA256DigestToHex(const Digest: TSHA256Digest): string;
const
HexChars: array[0..15] of Char = '0123456789abcdef';
var
I: Integer;
begin
SetLength(Result, 64);
for I := 0 to 31 do
begin
Result[I * 2 + 1] := HexChars[(Digest[I] shr 4) and $0F];
Result[I * 2 + 2] := HexChars[Digest[I] and $0F];
end;
end;
function SHA256DigestEqual(const A, B: TSHA256Digest): Boolean;
var
I: Integer;
begin
Result := True;
for I := 0 to 31 do
begin
if A[I] <> B[I] then
begin
Result := False;
Exit;
end;
end;
end;
{ ======================================================================== }
{ SHA-512 family (shared core for SHA-384 and SHA-512) }
{ Uses 64-bit state words (QWord), 128-byte blocks, 80 rounds. }
{ ======================================================================== }
const
{ SHA-512 round constants (FIPS 180-4 section 4.2.3) }
K512: array[0..79] of QWord = (
QWord($428a2f98d728ae22), QWord($7137449123ef65cd),
QWord($b5c0fbcfec4d3b2f), QWord($e9b5dba58189dbbc),
QWord($3956c25bf348b538), QWord($59f111f1b605d019),
QWord($923f82a4af194f9b), QWord($ab1c5ed5da6d8118),
QWord($d807aa98a3030242), QWord($12835b0145706fbe),
QWord($243185be4ee4b28c), QWord($550c7dc3d5ffb4e2),
QWord($72be5d74f27b896f), QWord($80deb1fe3b1696b1),
QWord($9bdc06a725c71235), QWord($c19bf174cf692694),
QWord($e49b69c19ef14ad2), QWord($efbe4786384f25e3),
QWord($0fc19dc68b8cd5b5), QWord($240ca1cc77ac9c65),
QWord($2de92c6f592b0275), QWord($4a7484aa6ea6e483),
QWord($5cb0a9dcbd41fbd4), QWord($76f988da831153b5),
QWord($983e5152ee66dfab), QWord($a831c66d2db43210),
QWord($b00327c898fb213f), QWord($bf597fc7beef0ee4),
QWord($c6e00bf33da88fc2), QWord($d5a79147930aa725),
QWord($06ca6351e003826f), QWord($142929670a0e6e70),
QWord($27b70a8546d22ffc), QWord($2e1b21385c26c926),
QWord($4d2c6dfc5ac42aed), QWord($53380d139d95b3df),
QWord($650a73548baf63de), QWord($766a0abb3c77b2a8),
QWord($81c2c92e47edaee6), QWord($92722c851482353b),
QWord($a2bfe8a14cf10364), QWord($a81a664bbc423001),
QWord($c24b8b70d0f89791), QWord($c76c51a30654be30),
QWord($d192e819d6ef5218), QWord($d69906245565a910),
QWord($f40e35855771202a), QWord($106aa07032bbd1b8),
QWord($19a4c116b8d2d0c8), QWord($1e376c085141ab53),
QWord($2748774cdf8eeb99), QWord($34b0bcb5e19b48a8),
QWord($391c0cb3c5c95a63), QWord($4ed8aa4ae3418acb),
QWord($5b9cca4f7763e373), QWord($682e6ff3d6b2b8a3),
QWord($748f82ee5defb2fc), QWord($78a5636f43172f60),
QWord($84c87814a1f0ab72), QWord($8cc702081a6439ec),
QWord($90befffa23631e28), QWord($a4506cebde82bde9),
QWord($bef9a3f7b2c67915), QWord($c67178f2e372532b),
QWord($ca273eceea26619c), QWord($d186b8c721c0c207),
QWord($eada7dd6cde0eb1e), QWord($f57d4f7fee6ed178),
QWord($06f067aa72176fba), QWord($0a637dc5a2c898a6),
QWord($113f9804bef90dae), QWord($1b710b35131c471b),
QWord($28db77f523047d84), QWord($32caab7b40c72493),
QWord($3c9ebe0a15c9bebc), QWord($431d67c49c100d4c),
QWord($4cc5d4becb3e42b6), QWord($597f299cfc657e2a),
QWord($5fcb6fab3ad6faec), QWord($6c44198c4a475817)
);
function ROTR64(X: QWord; N: Byte): QWord; inline;
begin
Result := (X shr N) or (X shl (64 - N));
end;
function CH64(X, Y, Z: QWord): QWord; inline;
begin
Result := (X and Y) xor ((not X) and Z);
end;
function MAJ64(X, Y, Z: QWord): QWord; inline;
begin
Result := (X and Y) xor (X and Z) xor (Y and Z);
end;
function BSIG0_64(X: QWord): QWord; inline;
begin
Result := ROTR64(X, 28) xor ROTR64(X, 34) xor ROTR64(X, 39);
end;
function BSIG1_64(X: QWord): QWord; inline;
begin
Result := ROTR64(X, 14) xor ROTR64(X, 18) xor ROTR64(X, 41);
end;
function SSIG0_64(X: QWord): QWord; inline;
begin
Result := ROTR64(X, 1) xor ROTR64(X, 8) xor (X shr 7);
end;
function SSIG1_64(X: QWord): QWord; inline;
begin
Result := ROTR64(X, 19) xor ROTR64(X, 61) xor (X shr 6);
end;
procedure SHA512Block(var Ctx: TSHA512Context; Data: PByte);
var
W: array[0..79] of QWord;
A, B, C, D, E, F, G, HH: QWord;
T1, T2: QWord;
T: Integer;
begin
{ Prepare message schedule (big-endian, 64-bit words) }
for T := 0 to 15 do
W[T] := (QWord(Data[T * 8]) shl 56) or
(QWord(Data[T * 8 + 1]) shl 48) or
(QWord(Data[T * 8 + 2]) shl 40) or
(QWord(Data[T * 8 + 3]) shl 32) or
(QWord(Data[T * 8 + 4]) shl 24) or
(QWord(Data[T * 8 + 5]) shl 16) or
(QWord(Data[T * 8 + 6]) shl 8) or
QWord(Data[T * 8 + 7]);
for T := 16 to 79 do
W[T] := SSIG1_64(W[T - 2]) + W[T - 7] + SSIG0_64(W[T - 15]) + W[T - 16];
A := Ctx.State[0]; B := Ctx.State[1];
C := Ctx.State[2]; D := Ctx.State[3];
E := Ctx.State[4]; F := Ctx.State[5];
G := Ctx.State[6]; HH := Ctx.State[7];
for T := 0 to 79 do
begin
T1 := HH + BSIG1_64(E) + CH64(E, F, G) + K512[T] + W[T];
T2 := BSIG0_64(A) + MAJ64(A, B, C);
HH := G; G := F; F := E; E := D + T1;
D := C; C := B; B := A; A := T1 + T2;
end;
Ctx.State[0] := Ctx.State[0] + A;
Ctx.State[1] := Ctx.State[1] + B;
Ctx.State[2] := Ctx.State[2] + C;
Ctx.State[3] := Ctx.State[3] + D;
Ctx.State[4] := Ctx.State[4] + E;
Ctx.State[5] := Ctx.State[5] + F;
Ctx.State[6] := Ctx.State[6] + G;
Ctx.State[7] := Ctx.State[7] + HH;
end;
{ Shared Update for SHA-384/512 (128-byte blocks) }
procedure SHA512FamilyUpdate(var Ctx: TSHA512Context; const Buf; Len: LongWord);
var
P: PByte;
I: LongWord;
BitLenLo, BitLenHi: QWord;
begin
if Ctx.Done or (Len = 0) then Exit;
P := @Buf;
{ Update 128-bit bit count (big-endian in Count[0..15]).
We only use the low 64 bits since files won't exceed 2^64 bits. }
BitLenLo := (QWord(Ctx.Count[8]) shl 56) or (QWord(Ctx.Count[9]) shl 48) or
(QWord(Ctx.Count[10]) shl 40) or (QWord(Ctx.Count[11]) shl 32) or
(QWord(Ctx.Count[12]) shl 24) or (QWord(Ctx.Count[13]) shl 16) or
(QWord(Ctx.Count[14]) shl 8) or QWord(Ctx.Count[15]);
BitLenHi := (QWord(Ctx.Count[0]) shl 56) or (QWord(Ctx.Count[1]) shl 48) or
(QWord(Ctx.Count[2]) shl 40) or (QWord(Ctx.Count[3]) shl 32) or
(QWord(Ctx.Count[4]) shl 24) or (QWord(Ctx.Count[5]) shl 16) or
(QWord(Ctx.Count[6]) shl 8) or QWord(Ctx.Count[7]);
BitLenLo := BitLenLo + QWord(Len) * 8;
if BitLenLo < QWord(Len) * 8 then Inc(BitLenHi);
Ctx.Count[0] := Byte(BitLenHi shr 56); Ctx.Count[1] := Byte(BitLenHi shr 48);
Ctx.Count[2] := Byte(BitLenHi shr 40); Ctx.Count[3] := Byte(BitLenHi shr 32);
Ctx.Count[4] := Byte(BitLenHi shr 24); Ctx.Count[5] := Byte(BitLenHi shr 16);
Ctx.Count[6] := Byte(BitLenHi shr 8); Ctx.Count[7] := Byte(BitLenHi);
Ctx.Count[8] := Byte(BitLenLo shr 56); Ctx.Count[9] := Byte(BitLenLo shr 48);
Ctx.Count[10] := Byte(BitLenLo shr 40); Ctx.Count[11] := Byte(BitLenLo shr 32);
Ctx.Count[12] := Byte(BitLenLo shr 24); Ctx.Count[13] := Byte(BitLenLo shr 16);
Ctx.Count[14] := Byte(BitLenLo shr 8); Ctx.Count[15] := Byte(BitLenLo);
I := 0;
{ Fill partial buffer }
if Ctx.DataLen > 0 then
begin
while (Ctx.DataLen < 128) and (I < Len) do
begin
Ctx.DataBuf[Ctx.DataLen] := P[I];
Inc(Ctx.DataLen);
Inc(I);
end;
if Ctx.DataLen = 128 then
begin
SHA512Block(Ctx, @Ctx.DataBuf[0]);
Ctx.DataLen := 0;
end;
end;
{ Process full 128-byte blocks }
while I + 128 <= Len do
begin
SHA512Block(Ctx, P + I);
Inc(I, 128);
end;
{ Buffer remaining }
while I < Len do
begin
Ctx.DataBuf[Ctx.DataLen] := P[I];
Inc(Ctx.DataLen);
Inc(I);
end;
end;
{ Shared Final for SHA-384/512. DigestLen is 48 for SHA-384, 64 for SHA-512. }
procedure SHA512FamilyFinal(var Ctx: TSHA512Context; out Digest; DigestLen: Integer);
var
SavedCount: array[0..15] of Byte;
I: Integer;
D: PByte;
begin
D := @Digest;
if Ctx.Done then
begin
for I := 0 to (DigestLen div 8) - 1 do
begin
D[I * 8] := Byte(Ctx.State[I] shr 56);
D[I * 8 + 1] := Byte(Ctx.State[I] shr 48);
D[I * 8 + 2] := Byte(Ctx.State[I] shr 40);
D[I * 8 + 3] := Byte(Ctx.State[I] shr 32);
D[I * 8 + 4] := Byte(Ctx.State[I] shr 24);
D[I * 8 + 5] := Byte(Ctx.State[I] shr 16);
D[I * 8 + 6] := Byte(Ctx.State[I] shr 8);
D[I * 8 + 7] := Byte(Ctx.State[I]);
end;
Exit;
end;
Move(Ctx.Count, SavedCount, 16);
{ Pad: 0x80 + zeros + 128-bit count }
Ctx.DataBuf[Ctx.DataLen] := $80;
Inc(Ctx.DataLen);
if Ctx.DataLen > 112 then
begin
while Ctx.DataLen < 128 do
begin
Ctx.DataBuf[Ctx.DataLen] := 0;
Inc(Ctx.DataLen);
end;
SHA512Block(Ctx, @Ctx.DataBuf[0]);
Ctx.DataLen := 0;
end;
while Ctx.DataLen < 112 do
begin
Ctx.DataBuf[Ctx.DataLen] := 0;
Inc(Ctx.DataLen);
end;
Move(SavedCount, Ctx.DataBuf[112], 16);
SHA512Block(Ctx, @Ctx.DataBuf[0]);
Ctx.Done := True;
{ Output digest (big-endian, 64-bit words) }
for I := 0 to (DigestLen div 8) - 1 do
begin
D[I * 8] := Byte(Ctx.State[I] shr 56);
D[I * 8 + 1] := Byte(Ctx.State[I] shr 48);
D[I * 8 + 2] := Byte(Ctx.State[I] shr 40);
D[I * 8 + 3] := Byte(Ctx.State[I] shr 32);
D[I * 8 + 4] := Byte(Ctx.State[I] shr 24);
D[I * 8 + 5] := Byte(Ctx.State[I] shr 16);
D[I * 8 + 6] := Byte(Ctx.State[I] shr 8);
D[I * 8 + 7] := Byte(Ctx.State[I]);
end;
end;
{ ---- SHA-384 public API ---- }
procedure SHA384Init(var Ctx: TSHA384Context);
begin
{ SHA-384 initial values (FIPS 180-4 section 5.3.4) }
Ctx.State[0] := QWord($cbbb9d5dc1059ed8);
Ctx.State[1] := QWord($629a292a367cd507);
Ctx.State[2] := QWord($9159015a3070dd17);
Ctx.State[3] := QWord($152fecd8f70e5939);
Ctx.State[4] := QWord($67332667ffc00b31);
Ctx.State[5] := QWord($8eb44a8768581511);
Ctx.State[6] := QWord($db0c2e0d64f98fa7);
Ctx.State[7] := QWord($47b5481dbefa4fa4);
FillChar(Ctx.Count, 16, 0);
Ctx.Done := False;
Ctx.DataLen := 0;
end;
procedure SHA384Update(var Ctx: TSHA384Context; const Buf; Len: LongWord);
begin
SHA512FamilyUpdate(Ctx, Buf, Len);
end;
procedure SHA384Final(var Ctx: TSHA384Context; out Digest: TSHA384Digest);
begin
SHA512FamilyFinal(Ctx, Digest, 48);
end;
function SHA384Buffer(const Buf; Len: LongWord): TSHA384Digest;
var
Ctx: TSHA384Context;
begin
SHA384Init(Ctx);
SHA384Update(Ctx, Buf, Len);
SHA384Final(Ctx, Result);
end;
function SHA384String(const S: string): TSHA384Digest;
begin
if Length(S) > 0 then
Result := SHA384Buffer(S[1], LongWord(Length(S)))
else
Result := SHA384Buffer(S, 0);
end;
function SHA384DigestToHex(const Digest: TSHA384Digest): string;
const
HexChars: array[0..15] of Char = '0123456789abcdef';
var
I: Integer;
begin
SetLength(Result, 96);
for I := 0 to 47 do
begin
Result[I * 2 + 1] := HexChars[(Digest[I] shr 4) and $0F];
Result[I * 2 + 2] := HexChars[Digest[I] and $0F];
end;
end;
function SHA384DigestEqual(const A, B: TSHA384Digest): Boolean;
var
I: Integer;
begin
Result := True;
for I := 0 to 47 do
if A[I] <> B[I] then
begin
Result := False;
Exit;
end;
end;
{ ---- SHA-512 public API ---- }
procedure SHA512Init(var Ctx: TSHA512Context);
begin
{ SHA-512 initial values (FIPS 180-4 section 5.3.5) }
Ctx.State[0] := QWord($6a09e667f3bcc908);
Ctx.State[1] := QWord($bb67ae8584caa73b);
Ctx.State[2] := QWord($3c6ef372fe94f82b);
Ctx.State[3] := QWord($a54ff53a5f1d36f1);
Ctx.State[4] := QWord($510e527fade682d1);
Ctx.State[5] := QWord($9b05688c2b3e6c1f);
Ctx.State[6] := QWord($1f83d9abfb41bd6b);
Ctx.State[7] := QWord($5be0cd19137e2179);
FillChar(Ctx.Count, 16, 0);
Ctx.Done := False;
Ctx.DataLen := 0;
end;
procedure SHA512Update(var Ctx: TSHA512Context; const Buf; Len: LongWord);
begin
SHA512FamilyUpdate(Ctx, Buf, Len);
end;
procedure SHA512Final(var Ctx: TSHA512Context; out Digest: TSHA512Digest);
begin
SHA512FamilyFinal(Ctx, Digest, 64);
end;
function SHA512Buffer(const Buf; Len: LongWord): TSHA512Digest;
var
Ctx: TSHA512Context;
begin
SHA512Init(Ctx);
SHA512Update(Ctx, Buf, Len);
SHA512Final(Ctx, Result);
end;
function SHA512String(const S: string): TSHA512Digest;
begin
if Length(S) > 0 then
Result := SHA512Buffer(S[1], LongWord(Length(S)))
else
Result := SHA512Buffer(S, 0);
end;
function SHA512DigestToHex(const Digest: TSHA512Digest): string;
const
HexChars: array[0..15] of Char = '0123456789abcdef';
var
I: Integer;
begin
SetLength(Result, 128);
for I := 0 to 63 do
begin
Result[I * 2 + 1] := HexChars[(Digest[I] shr 4) and $0F];
Result[I * 2 + 2] := HexChars[Digest[I] and $0F];
end;
end;
function SHA512DigestEqual(const A, B: TSHA512Digest): Boolean;
var
I: Integer;
begin
Result := True;
for I := 0 to 63 do
if A[I] <> B[I] then
begin
Result := False;
Exit;
end;
end;
end.