From 554bfa15763bebb343decc806b1679f9518c7d85 Mon Sep 17 00:00:00 2001 From: Ugochukwu Mmaduekwe Date: Wed, 26 Aug 2026 01:09:51 +0100 Subject: [PATCH] Size the hash staging buffer to the input and cache the HMAC pads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both hot paths allocated more than the work required. TransformUntyped and TransformStream sized their staging buffer to the full 64 KB default regardless of input, so hashing a one-byte message (as HKDF does, byte by byte) still cleared a 64 KB buffer; a shared StagingSize helper now clamps the buffer to the actual input length, honouring a user-set BufferSize as the chunk maximum. THMACNotBuildInAdapter recomputed ipad/opad and copied the key on every Initialize — and HKDF re-inits the HMAC for every Extract and Expand block — so an FPadsValid flag now caches the pads and rebuilds them only when the key changes, invalidated on both SetKey and Clear. --- HashLib/src/Base/HlpHMACNotBuildInAdapter.pas | 16 ++++-- HashLib/src/Base/HlpHash.pas | 56 +++++++++---------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/HashLib/src/Base/HlpHMACNotBuildInAdapter.pas b/HashLib/src/Base/HlpHMACNotBuildInAdapter.pas index 86367360..21cb96ee 100644 --- a/HashLib/src/Base/HlpHMACNotBuildInAdapter.pas +++ b/HashLib/src/Base/HlpHMACNotBuildInAdapter.pas @@ -22,6 +22,7 @@ THMACNotBuildInAdapter = class sealed(THash, IHMAC, IHMACNotBuildIn, ICrypto, var FHash: IHash; FOpad, FIpad, FKey: THashLibByteArray; + FPadsValid: Boolean; constructor Create(const AUnderlyingHash: IHash; const AHMACKey: THashLibByteArray); @@ -60,6 +61,7 @@ implementation procedure THMACNotBuildInAdapter.Clear(); begin TArrayUtils.ZeroFill(FKey); + FPadsValid := False; end; function THMACNotBuildInAdapter.Clone(): IHash; @@ -69,6 +71,7 @@ function THMACNotBuildInAdapter.Clone(): IHash; LHmacInstance := THMACNotBuildInAdapter.Create(FHash.Clone(), FKey); LHmacInstance.FOpad := System.Copy(FOpad); LHmacInstance.FIpad := System.Copy(FIpad); + LHmacInstance.FPadsValid := FPadsValid; Result := LHmacInstance; Result.BufferSize := BufferSize; end; @@ -104,6 +107,7 @@ procedure THMACNotBuildInAdapter.SetKey(const AValue: THashLibByteArray); begin FKey := System.Copy(AValue); end; + FPadsValid := False; end; procedure THMACNotBuildInAdapter.UpdatePads; @@ -112,13 +116,13 @@ procedure THMACNotBuildInAdapter.UpdatePads; LIdx, LBlockSize: Int32; begin LBlockSize := FHash.BlockSize; - if (System.Length(Key) > LBlockSize) then + if (System.Length(FKey) > LBlockSize) then begin - LKey := FHash.ComputeBytes(Key).GetBytes(); + LKey := FHash.ComputeBytes(FKey).GetBytes(); end else begin - LKey := Key; + LKey := FKey; end; TArrayUtils.Fill(FIpad, 0, LBlockSize, Byte($36)); @@ -137,7 +141,11 @@ procedure THMACNotBuildInAdapter.UpdatePads; procedure THMACNotBuildInAdapter.Initialize; begin FHash.Initialize(); - UpdatePads(); + if not FPadsValid then + begin + UpdatePads(); + FPadsValid := True; + end; FHash.TransformBytes(FIpad); end; diff --git a/HashLib/src/Base/HlpHash.pas b/HashLib/src/Base/HlpHash.pas index a7b151ae..7bd67d09 100644 --- a/HashLib/src/Base/HlpHash.pas +++ b/HashLib/src/Base/HlpHash.pas @@ -7,6 +7,7 @@ interface uses Classes, SysUtils, + Math, HlpHashLibTypes, HlpHashLibExceptions, HlpConverters, @@ -30,6 +31,9 @@ THash = class abstract(TInterfacedObject, IHash) const DefaultBufferSize = Int32(64 * 1024); // 64Kb + strict private + class function StagingSize(AMax: Int32; ATotal: Int64): Int32; static; inline; + strict protected function GetBlockSize: Int32; virtual; @@ -100,6 +104,11 @@ function THash.GetName: String; Result := Self.ClassName; end; +class function THash.StagingSize(AMax: Int32; ATotal: Int64): Int32; +begin + Result := Int32(Min(Int64(AMax), Max(ATotal, Int64(0)))); +end; + function THash.GetBufferSize: Int32; begin Result := FBufferSize; @@ -158,14 +167,7 @@ procedure THash.TransformUntyped(const AData; ALength: Int64); begin LPtrStart := @AData; - if BufferSize > ALength then // Sanity Check - begin - LBufferSize := BufferSize; - end - else - begin - LBufferSize := BufferSize; - end; + LBufferSize := StagingSize(BufferSize, ALength); if LPtrStart <> nil then begin @@ -253,42 +255,36 @@ procedure THash.TransformStream(const AStream: TStream; ALength: Int64); var LData: THashLibByteArray; LRead, LBufferSize: Int32; - LTotal: Int64; + LTotal, LPosition, LSize: Int64; begin {$IFDEF DEBUG} System.Assert((ALength = -1) or (ALength > 0)); {$ENDIF DEBUG} LTotal := 0; - if (AStream <> nil) then + if (AStream = nil) then begin - if (ALength > -1) then - begin - if ((AStream.Position + ALength) > AStream.Size) then - begin - raise EIndexOutOfRangeHashLibException.CreateRes( - @SReadBeyondStreamEndError); - end; - end; + raise EArgumentNilHashLibException.CreateRes(@SAStreamNilError); + end; + + LPosition := AStream.Position; + LSize := AStream.Size; - if (AStream.Position >= AStream.Size) then + if (ALength > -1) then + begin + if ((LPosition + ALength) > LSize) then begin - Exit; + raise EIndexOutOfRangeHashLibException.CreateRes( + @SReadBeyondStreamEndError); end; - end - else - begin - raise EArgumentNilHashLibException.CreateRes(@SAStreamNilError); end; - if BufferSize > AStream.Size then // Sanity Check - begin - LBufferSize := BufferSize; - end - else + if (LPosition >= LSize) then begin - LBufferSize := BufferSize; + Exit; end; + LBufferSize := StagingSize(BufferSize, LSize - LPosition); + System.SetLength(LData, LBufferSize); if (ALength = -1) then