@@ -75,6 +75,41 @@ TGCM = class(TAuthenticatedCipherModesBase)
7575 // / </summary>
7676 FE_K_Y0 : T128;
7777
78+ // / <summary>
79+ // / Running GHASH state (NIST "X"). Allows multi-call Encode/Decode.
80+ // / Tag is finalized only in Done — see Cleanup-Roadmap §3.1 (Option A).
81+ // / </summary>
82+ FX : T128;
83+ // / <summary>
84+ // / Incomplete 16-byte GHASH block carried across Encode/Decode calls
85+ // / </summary>
86+ FGHASHPartial : array [0 ..15 ] of Byte;
87+ // / <summary>
88+ // / Number of valid bytes in FGHASHPartial (0..15)
89+ // / </summary>
90+ FGHASHPartialLen : Integer;
91+ // / <summary>
92+ // / Total ciphertext bytes processed since Init (for length block)
93+ // / </summary>
94+ FTotalCiphertextBytes : UInt64;
95+ // / <summary>
96+ // / True after AAD has been absorbed into FX (and padded to 16 bytes)
97+ // / </summary>
98+ FAuthDataHashed : Boolean;
99+ // / <summary>
100+ // / Leftover keystream from an incomplete CTR block (multi-call Encode/Decode)
101+ // / </summary>
102+ FKeystream : T128;
103+ // / <summary>
104+ // / Number of unused bytes remaining in FKeystream (0..15)
105+ // / </summary>
106+ FKeystreamRemain : Integer;
107+ // / <summary>
108+ // / True after Done has materialized the authentication tag.
109+ // / Prevents double-finalization and post-Done GHASH/CTR updates.
110+ // / </summary>
111+ FFinalized : Boolean;
112+
78113 // / <summary>
79114 // / XOR implementation for unsigned 128 bit numbers
80115 // / </summary>
@@ -198,6 +233,27 @@ TGCM = class(TAuthenticatedCipherModesBase)
198233 Ciphertext : PUInt8Array;
199234 CiphertextSize : Integer): T128;
200235
236+ // / <summary>
237+ // / Feeds data into the running GHASH state FX (supports partial blocks).
238+ // / </summary>
239+ procedure GHASHUpdate (Data: PUInt8Array; DataSize: Integer);
240+ // / <summary>
241+ // / Pads any incomplete GHASH block with zeros and multiplies into FX.
242+ // / </summary>
243+ procedure GHASHPadPartial ;
244+ // / <summary>
245+ // / Ensures AAD has been GHASH'd and padded once before ciphertext bytes.
246+ // / </summary>
247+ procedure EnsureAuthDataHashed ;
248+ // / <summary>
249+ // / Completes GHASH (length block) and writes CalculatedAuthenticationTag.
250+ // / </summary>
251+ procedure FinalizeAuthenticationTag ;
252+ // / <summary>
253+ // / GCM-CTR keystream XOR with multi-call partial-block carry.
254+ // / </summary>
255+ procedure ApplyCTR (Source, Dest: PUInt8Array; Size: Integer);
256+
201257 // / <summary>
202258 // / Encrypts a T128 value using the encryption method specified on init
203259 // / </summary>
@@ -263,6 +319,14 @@ TGCM = class(TAuthenticatedCipherModesBase)
263319 Dest : PUInt8Array;
264320 Size : Integer); override;
265321
322+ // / <summary>
323+ // / Finishes GHASH and materializes CalculatedAuthenticationTag.
324+ // / Must be called after the last Encode/Decode (cipher Done does this).
325+ // / Idempotent: a second call leaves the tag unchanged.
326+ // / After finalization, Encode/Decode raise until Init is called again.
327+ // / </summary>
328+ procedure Done ;
329+
266330 // / <summary>
267331 // / Returns a list of authentication tag lengths explicitely specified by
268332 // / the official specification of the standard.
@@ -275,6 +339,10 @@ TGCM = class(TAuthenticatedCipherModesBase)
275339
276340implementation
277341
342+ resourcestring
343+ sGCMAlreadyFinalized =
344+ ' GCM authentication already finalized; call Init before further Encode/Decode' ;
345+
278346function TGCM.XOR_T128 (const x, y : T128): T128;
279347begin
280348 Result[0 ] := x[0 ] xor y[0 ];
@@ -439,6 +507,18 @@ procedure TGCM.Init(EncryptionMethod : TEncodeDecodeMethod;
439507 Nullbytes[0 ] := 0 ;
440508 Nullbytes[1 ] := 0 ;
441509
510+ // Streaming GHASH + CTR state for multi-call Encode/Decode (Option A)
511+ FX[0 ] := 0 ;
512+ FX[1 ] := 0 ;
513+ FGHASHPartialLen := 0 ;
514+ FillChar(FGHASHPartial[0 ], SizeOf(FGHASHPartial), 0 );
515+ FTotalCiphertextBytes := 0 ;
516+ FAuthDataHashed := False;
517+ FKeystreamRemain := 0 ;
518+ FKeystream[0 ] := 0 ;
519+ FKeystream[1 ] := 0 ;
520+ FFinalized := False;
521+
442522 OldH := FH;
443523 EncryptionMethod(@Nullbytes[0 ], @FH[0 ], 16 );
444524
@@ -455,11 +535,103 @@ procedure TGCM.Init(EncryptionMethod : TEncodeDecodeMethod;
455535 b^ := 1 ;
456536 end
457537 else
538+ // One-shot GHASH over IV only (does not use streaming FX)
458539 FY := CalcGaloisHash(nil , 0 , @InitVector[0 ], length(InitVector));
459540
460541 FEncryptionMethod(@FY[0 ], @FE_K_Y0[0 ], 16 );
461542end ;
462543
544+ procedure TGCM.GHASHUpdate (Data: PUInt8Array; DataSize: Integer);
545+ var
546+ Offset, Take : Integer;
547+ begin
548+ if (DataSize <= 0 ) or (Data = nil ) then
549+ Exit;
550+
551+ Offset := 0 ;
552+
553+ if FGHASHPartialLen > 0 then
554+ begin
555+ Take := 16 - FGHASHPartialLen;
556+ if Take > DataSize then
557+ Take := DataSize;
558+ Move(Data^[Offset], FGHASHPartial[FGHASHPartialLen], Take);
559+ Inc(FGHASHPartialLen, Take);
560+ Inc(Offset, Take);
561+ if FGHASHPartialLen = 16 then
562+ begin
563+ FX := poly_mult_H(XOR_PointerWithT128(@FGHASHPartial[0 ], FX));
564+ FGHASHPartialLen := 0 ;
565+ end ;
566+ end ;
567+
568+ while Offset + 16 <= DataSize do
569+ begin
570+ FX := poly_mult_H(XOR_PointerWithT128(@Data^[Offset], FX));
571+ Inc(Offset, 16 );
572+ end ;
573+
574+ if Offset < DataSize then
575+ begin
576+ FGHASHPartialLen := DataSize - Offset;
577+ Move(Data^[Offset], FGHASHPartial[0 ], FGHASHPartialLen);
578+ end ;
579+ end ;
580+
581+ procedure TGCM.GHASHPadPartial ;
582+ var
583+ Block : T128;
584+ begin
585+ if FGHASHPartialLen > 0 then
586+ begin
587+ Block := nullbytes;
588+ Move(FGHASHPartial[0 ], Block[0 ], FGHASHPartialLen);
589+ FX := poly_mult_H(XOR_T128(Block, FX));
590+ FGHASHPartialLen := 0 ;
591+ end ;
592+ end ;
593+
594+ procedure TGCM.EnsureAuthDataHashed ;
595+ begin
596+ if FAuthDataHashed then
597+ Exit;
598+
599+ if Length(DataToAuthenticate) > 0 then
600+ GHASHUpdate(@DataToAuthenticate[0 ], Length(DataToAuthenticate));
601+ // Pad AAD to 16-byte boundary before ciphertext (NIST GHASH layout)
602+ GHASHPadPartial;
603+ FAuthDataHashed := True;
604+ end ;
605+
606+ procedure TGCM.FinalizeAuthenticationTag ;
607+ var
608+ AuthTag : T128;
609+ AuthCipherLength : T128;
610+ AuthLen : Integer;
611+ begin
612+ EnsureAuthDataHashed;
613+ // Pad incomplete ciphertext block
614+ GHASHPadPartial;
615+
616+ AuthLen := Length(DataToAuthenticate);
617+ SetAuthenticationCipherLength(AuthCipherLength, UInt64(AuthLen) shl 3 ,
618+ FTotalCiphertextBytes shl 3 );
619+ FX := poly_mult_H(XOR_T128(AuthCipherLength, FX));
620+ AuthTag := XOR_T128(FX, FE_K_Y0);
621+
622+ SetLength(FCalcAuthenticationTag, FCalcAuthenticationTagLength);
623+ if (FCalcAuthenticationTagLength > 0 ) then
624+ Move(AuthTag[0 ], FCalcAuthenticationTag[0 ], FCalcAuthenticationTagLength);
625+ end ;
626+
627+ procedure TGCM.Done ;
628+ begin
629+ if FFinalized then
630+ Exit;
631+ FinalizeAuthenticationTag;
632+ FFinalized := True;
633+ end ;
634+
463635function TGCM.CalcGaloisHash (AuthenticatedData : PUInt8Array; AuthLen : integer; Ciphertext : PUInt8Array;
464636 CiphertextSize: Integer): T128;
465637var
@@ -507,85 +679,90 @@ function TGCM.CalcGaloisHash(AuthenticatedData : PUInt8Array; AuthLen : integer;
507679 Result := poly_mult_H(XOR_T128(AuthCipherLength, x));
508680end ;
509681
510- procedure TGCM.Decode (Source, Dest: PUInt8Array; Size: Integer);
682+ procedure TGCM.ApplyCTR (Source, Dest: PUInt8Array; Size: Integer);
511683var
512- i, j, BlockCount : UInt64;
513- a_tag : T128;
514- pDataToAuth : PUInt8Array;
515- pSrc : PUInt8Array;
684+ i, Take : Integer;
685+ KSBytes : P16ByteArray;
516686begin
687+ if Size <= 0 then
688+ Exit;
689+
517690 i := 0 ;
518- BlockCount := Size div 16 ;
691+ // Drain leftover keystream from a previous partial block
692+ if FKeystreamRemain > 0 then
693+ begin
694+ KSBytes := @FKeystream[0 ];
695+ Take := FKeystreamRemain;
696+ if Take > Size then
697+ Take := Size;
698+ XOR_ArrayWithT128(Source, i, Take, FKeystream, Dest);
699+ // Shift remaining keystream left so index 0 is next unused byte
700+ if Take < FKeystreamRemain then
701+ Move(KSBytes^[Take], KSBytes^[0 ], FKeystreamRemain - Take);
702+ Dec(FKeystreamRemain, Take);
703+ Inc(i, Take);
704+ end ;
519705
520- for j := 1 to BlockCount do
706+ while i + 16 <= Size do
521707 begin
522708 INCR(FY);
523709 P128(@Dest^[i])^ := XOR_PointerWithT128(@Source^[i], EncodeT128(FY));
524- inc (i, 16 );
710+ Inc (i, 16 );
525711 end ;
526712
527713 if i < Size then
528714 begin
529715 INCR(FY);
530- XOR_ArrayWithT128(@Source^[0 ], i, UInt64(Size)-i, EncodeT128(FY), @Dest^[0 ]);
716+ FKeystream := EncodeT128(FY);
717+ Take := Size - i;
718+ XOR_ArrayWithT128(Source, i, Take, FKeystream, Dest);
719+ // Keep unused tail of this keystream block for the next call
720+ Move(P16ByteArray(@FKeystream[0 ])^[Take], P16ByteArray(@FKeystream[0 ])^[0 ], 16 - Take);
721+ // Clear used prefix is unnecessary; only FKeystreamRemain matters
722+ FKeystreamRemain := 16 - Take;
531723 end ;
724+ end ;
532725
533- pDataToAuth := nil ;
534- if Length(DataToAuthenticate) > 0 then
535- pDataToAuth := @DataToAuthenticate[0 ];
536- pSrc := nil ;
537- if Size > 0 then
538- pSrc := @source[0 ];
726+ procedure TGCM.Decode (Source, Dest: PUInt8Array; Size: Integer);
727+ begin
728+ if FFinalized then
729+ raise EDECCipherException.CreateRes(@sGCMAlreadyFinalized);
539730
540- a_tag := XOR_T128(CalcGaloisHash(pDataToAuth, Length(DataToAuthenticate),
541- pSrc, Size), FE_K_Y0) ;
731+ // AAD into GHASH once; tag finalized in Done (supports multi-call streams)
732+ EnsureAuthDataHashed ;
542733
543- Setlength(FCalcAuthenticationTag, FCalcAuthenticationTagLength);
544- if (FCalcAuthenticationTagLength > 0 ) then
545- Move(a_tag[0 ], FCalcAuthenticationTag[0 ], FCalcAuthenticationTagLength);
734+ if Size < 0 then
735+ Size := 0 ;
546736
547- // Check for correct authentication result is in Done of DECCipherModes
548- // if not IsEqual(FExpectedAuthenticationTag, FCalcAuthenticationTag) then
549- // raise EDECCipherAuthenticationException.CreateRes(@sInvalidAuthenticationValue);
737+ // GHASH over ciphertext before CTR (Source is ciphertext)
738+ if Size > 0 then
739+ begin
740+ GHASHUpdate(Source, Size);
741+ Inc(FTotalCiphertextBytes, UInt64(Size));
742+ end ;
550743
551- // In difference to the NIST recommendation we do not discard plaintext if
552- // authentication failed to make data recovery possible. But since we throw
553- // an exception the user will get notified that there's something wrong
554- // if not IsEqual(authenticaton_tag, ba_tag) then
555- // SetLength(plaintext, 0); // NIST FAIL => pt=''
744+ ApplyCTR(Source, Dest, Size);
556745end ;
557746
558747procedure TGCM.Encode (Source, Dest: PUInt8Array; Size: Integer);
559- var
560- i, j, div_len_plain : UInt64;
561- AuthTag : T128;
562- pDataToAuth : PUInt8Array;
563748begin
564- i := 0 ;
565- div_len_plain := Size div 16 ;
749+ if FFinalized then
750+ raise EDECCipherException.CreateRes(@sGCMAlreadyFinalized) ;
566751
567- for j := 1 to div_len_plain do
568- begin
569- INCR(FY);
752+ // AAD into GHASH once; tag finalized in Done (supports multi-call streams)
753+ EnsureAuthDataHashed;
570754
571- P128(@Dest^[i])^ := XOR_PointerWithT128(@Source^[i], EncodeT128(FY));
755+ if Size < 0 then
756+ Size := 0 ;
572757
573- inc(i,16 );
574- end ;
758+ ApplyCTR(Source, Dest, Size);
575759
576- if i < Size then
760+ // GHASH over ciphertext produced in Dest
761+ if Size > 0 then
577762 begin
578- INCR(FY );
579- XOR_ArrayWithT128(Source, i, UInt64(Size)-i, EncodeT128(FY), Dest );
763+ GHASHUpdate(Dest, Size );
764+ Inc(FTotalCiphertextBytes, UInt64(Size));
580765 end ;
581-
582- pDataToAuth := nil ;
583- if Length(DataToAuthenticate) > 0 then
584- pDataToAuth := @DataToAuthenticate[0 ];
585- AuthTag := XOR_T128(CalcGaloisHash(pDataToAuth, Length(DataToAuthenticate), @Dest[0 ], Size), FE_K_Y0);
586- Setlength(FCalcAuthenticationTag, FCalcAuthenticationTagLength);
587- if (FCalcAuthenticationTagLength > 0 ) then
588- Move(AuthTag[0 ], FCalcAuthenticationTag[0 ], FCalcAuthenticationTagLength);
589766end ;
590767
591768function TGCM.EncodeT128 (Value : T128): T128;
0 commit comments