diff --git a/Source/DECAuthenticatedCipherModesBase.pas b/Source/DECAuthenticatedCipherModesBase.pas
index 85b05b5..56d9783 100644
--- a/Source/DECAuthenticatedCipherModesBase.pas
+++ b/Source/DECAuthenticatedCipherModesBase.pas
@@ -59,19 +59,21 @@ EDECAuthLengthException = class(EDECException);
/// Base class for authenticated cipher modes (GCM, CCM, future AEAD modes).
///
///
- /// Lifecycle for multi-call capable modes (e.g. GCM):
+ /// Lifecycle for authenticated modes (GCM, CCM):
///
/// Init → set AAD / tag length / expected tag → Encode/Decode* → Done →
/// read CalculatedAuthenticationTag.
///
///
- /// Done must be called before the calculated authentication tag is valid
- /// for multi-call streams. Done is idempotent. After Done, further
- /// Encode/Decode raises until Init is called again.
+ /// Done must be called before CalculatedAuthenticationTag may be read.
+ /// Reading the tag before Done raises EDECCipherException. Done is
+ /// idempotent. After Done, further Encode/Decode raises until Init is
+ /// called again.
///
///
- /// CCM remains one-shot (single Encode/Decode with full message length);
- /// Done still verifies ExpectedAuthenticationTag when set.
+ /// GCM supports multi-call Encode/Decode. CCM currently remains one-shot
+ /// (single Encode/Decode with the full message length); the tag is still
+ /// materialized in Done so both modes share the same lifecycle.
///
///
TAuthenticatedCipherModesBase = class(TObject)
@@ -100,6 +102,13 @@ TAuthenticatedCipherModesBase = class(TObject)
///
FEncryptionMethod : TEncodeDecodeMethod;
+ ///
+ /// True after Done has materialized the authentication tag. Reading
+ /// CalculatedAuthenticationTag before this is set raises. Encode/Decode
+ /// after finalization also raises until Init is called again.
+ ///
+ FFinalized : Boolean;
+
///
/// Defines the length of the resulting authentication value in bit.
///
@@ -120,6 +129,24 @@ TAuthenticatedCipherModesBase = class(TObject)
/// Length of the calculated authentication value in bit
///
function GetAuthenticationTagBitLength: UInt32; virtual;
+ ///
+ /// Returns the calculated authentication tag. Raises if Done has not
+ /// been called yet.
+ ///
+ ///
+ /// Calculated authentication tag bytes
+ ///
+ ///
+ /// Raised when the tag is read before Done.
+ ///
+ function GetCalculatedAuthenticationTag: TBytes; virtual;
+ ///
+ /// Raises EDECCipherException when Encode/Decode is attempted after Done.
+ ///
+ ///
+ /// Raised when the mode has already been finalized.
+ ///
+ procedure CheckNotFinalized;
public
///
/// Should be called when starting encryption/decryption in order to
@@ -169,8 +196,9 @@ TAuthenticatedCipherModesBase = class(TObject)
///
/// Finalizes the authentication tag after all Encode/Decode calls.
- /// Idempotent. Default implementation is a no-op (suitable for modes that
- /// already compute the tag inside Encode/Decode, e.g. CCM).
+ /// Idempotent. Marks the tag as readable via CalculatedAuthenticationTag.
+ /// Concrete modes that defer tag computation (GCM, CCM) override this
+ /// to materialize the tag before calling inherited.
///
procedure Done; virtual;
@@ -179,7 +207,8 @@ TAuthenticatedCipherModesBase = class(TObject)
/// the official specification of the standard.
///
///
- /// List of bit lengths
+ /// List of bit lengths prescribed by the mode specification. If the
+ /// mode does not prescribe any tag lengths, an empty array is returned.
///
function GetStandardAuthenticationTagBitLengths:TStandardBitLengths; virtual;
@@ -200,11 +229,15 @@ TAuthenticatedCipherModesBase = class(TObject)
read GetAuthenticationTagBitLength
write SetAuthenticationTagLength;
///
- /// Calculated authentication value. For multi-call modes this is only
- /// complete after Done has been called.
+ /// Calculated authentication value. Valid only after Done has been
+ /// called. Reading this property before Done raises EDECCipherException
+ /// so callers follow the Init → Encode/Decode* → Done → tag lifecycle.
///
+ ///
+ /// Raised when the property is read before Done.
+ ///
property CalculatedAuthenticationTag : TBytes
- read FCalcAuthenticationTag
+ read GetCalculatedAuthenticationTag
write FCalcAuthenticationTag;
///
@@ -221,6 +254,12 @@ implementation
uses
DECUtil;
+resourcestring
+ sAuthenticationTagNotFinalized =
+ 'Calculated authentication tag is not available before Done has been called';
+ sAuthenticatedModeAlreadyFinalized =
+ 'Authenticated cipher mode already finalized; call Init before further Encode/Decode';
+
{ TAuthenticatedCipherModesBase }
function TAuthenticatedCipherModesBase.GetAuthenticationTagBitLength: UInt32;
@@ -228,8 +267,25 @@ function TAuthenticatedCipherModesBase.GetAuthenticationTagBitLength: UInt32;
Result := FCalcAuthenticationTagLength shl 3;
end;
+function TAuthenticatedCipherModesBase.GetCalculatedAuthenticationTag: TBytes;
+begin
+ if not FFinalized then
+ raise EDECCipherException.CreateRes(@sAuthenticationTagNotFinalized);
+
+ Result := FCalcAuthenticationTag;
+end;
+
+procedure TAuthenticatedCipherModesBase.CheckNotFinalized;
+begin
+ if FFinalized then
+ raise EDECCipherException.CreateRes(@sAuthenticatedModeAlreadyFinalized);
+end;
+
function TAuthenticatedCipherModesBase.GetStandardAuthenticationTagBitLengths: TStandardBitLengths;
begin
+ // No prescribed lengths at this abstraction: return an empty array rather
+ // than a dummy 0-entry so callers can distinguish "none specified" from a
+ // specified length of 0 bits.
SetLength(Result, 0);
end;
@@ -250,12 +306,12 @@ procedure TAuthenticatedCipherModesBase.Init(EncryptionMethod : TEncodeDecodeMet
end;
FEncryptionMethod := EncryptionMethod;
+ FFinalized := False;
end;
procedure TAuthenticatedCipherModesBase.Done;
begin
- // Default: no deferred finalization (CCM computes the tag in Encode/Decode).
- // Streaming modes such as GCM override this to materialize the tag.
+ FFinalized := True;
end;
procedure TAuthenticatedCipherModesBase.SetAuthenticationTagLength(const Value: UInt32);
diff --git a/Source/DECCipherFormats.pas b/Source/DECCipherFormats.pas
index c459893..e9a03e6 100644
--- a/Source/DECCipherFormats.pas
+++ b/Source/DECCipherFormats.pas
@@ -1,4 +1,4 @@
-{*****************************************************************************
+{*****************************************************************************
The DEC team (see file NOTICE.txt) licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
@@ -732,8 +732,8 @@ function TDECFormattedCipher.EncodeBytes(const Source: TBytes): TBytes;
if Length(Result) > 0 then
Encode(Source[0], Result[0], Length(Source))
else
- if (FMode = cmGCM) then
- EncodeGCM(nil, nil, 0);
+ if (FMode = cmGCM) or (FMode = cmCCM) then
+ EncodeAuthenticated(nil, nil, 0);
end;
begin
@@ -755,8 +755,8 @@ function TDECFormattedCipher.DecodeBytes(const Source: TBytes): TBytes;
Decode(Source[0], Result[0], Length(Source));
end
else
- if (FMode = cmGCM) then
- DecodeGCM(nil, nil, 0);
+ if (FMode = cmGCM) or (FMode = cmCCM) then
+ DecodeAuthenticated(nil, nil, 0);
if not (FPaddingClass = nil) then
Result := FPaddingClass.RemovePadding(Result, Context.BlockSize);
diff --git a/Source/DECCipherInterface.pas b/Source/DECCipherInterface.pas
index a4e1c6e..f9a280a 100644
--- a/Source/DECCipherInterface.pas
+++ b/Source/DECCipherInterface.pas
@@ -1,4 +1,4 @@
-{*****************************************************************************
+{*****************************************************************************
The DEC team (see file NOTICE.txt) licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
@@ -665,11 +665,12 @@ interface
///
///
/// Result of the authentication. Raises an EDECCipherException if this is
- /// called for a cipher mode not supporting authentication.
+ /// called for a cipher mode not supporting authentication, or if Done
+ /// has not been called yet.
///
///
/// Exception raised if called for a cipher mode not supporting
- /// authentication.
+ /// authentication, or if the tag is read before Done.
///
function GetCalcAuthenticatonResult: TBytes;
///
@@ -705,8 +706,10 @@ interface
/// the official specification of the standard.
///
///
- /// List of bit lengths. If the cipher mode used is not an authenticated
- /// one, the array will just contain a single value of 0.
+ /// List of bit lengths prescribed by the authenticated mode. If the
+ /// cipher mode used is not an authenticated one, the array will just
+ /// contain a single value of 0. If an authenticated mode does not
+ /// prescribe tag lengths, an empty array is returned.
///
function GetStandardAuthenticationTagBitLengths:TStandardBitLengths;
@@ -756,12 +759,13 @@ interface
///
/// Some block chaining modes have the ability to authenticate the message
/// in addition to encrypting it. This property contains the generated
- /// authentication tag. Raises an EDECCipherException if this is
- /// called for a cipher mode not supporting authentication.
+ /// authentication tag. Call Done before reading it; reading the tag
+ /// before Done raises EDECCipherException. Raises an EDECCipherException
+ /// if this is called for a cipher mode not supporting authentication.
///
///
/// Exception raised if called for a cipher mode not supporting
- /// authentication.
+ /// authentication, or if the tag is read before Done.
///
property CalculatedAuthenticationResult : TBytes
read GetCalcAuthenticatonResult;
diff --git a/Source/DECCipherModes.pas b/Source/DECCipherModes.pas
index db5889d..4a5e817 100644
--- a/Source/DECCipherModes.pas
+++ b/Source/DECCipherModes.pas
@@ -81,11 +81,12 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher)
///
///
/// Result of the authentication. Raises an EDECCipherException if this is
- /// called for a cipher mode not supporting authentication.
+ /// called for a cipher mode not supporting authentication, or if Done
+ /// has not been called yet.
///
///
/// Exception raised if called for a cipher mode not supporting
- /// authentication.
+ /// authentication, or if the tag is read before Done.
///
function GetCalcAuthenticatonResult: TBytes;
///
@@ -235,16 +236,20 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher)
///
procedure EncodeCTSx(Source, Dest: PUInt8Array; Size: Integer); virtual;
///
- /// Authenticated encryption via FAuthObj (GCM, CCM). Kept as EncodeGCM for
- /// protected-API compatibility; dispatches to the active auth mode object.
+ /// Authenticated encryption via FAuthObj (GCM, CCM, future AEAD modes).
+ /// Replaces the former protected EncodeGCM / EncodeCCM entry points.
/// Callable even if source length is 0 (AAD-only / empty PT).
///
- procedure EncodeGCM(Source, Dest: PUInt8Array; Size: Integer); virtual;
- ///
- /// Authenticated encryption via FAuthObj. Alias retained for protected-API
- /// compatibility with code that overrode EncodeCCM.
- ///
- procedure EncodeCCM(Source, Dest: PUInt8Array; Size: Integer); virtual;
+ ///
+ /// Plain text to encrypt
+ ///
+ ///
+ /// Ciphertext after encryption
+ ///
+ ///
+ /// Number of bytes to encrypt
+ ///
+ procedure EncodeAuthenticated(Source, Dest: PUInt8Array; Size: Integer); virtual;
{$IFDEF DEC3_CMCTS}
///
/// double CBC, with
@@ -329,15 +334,19 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher)
///
procedure DecodeCTSx(Source, Dest: PUInt8Array; Size: Integer); virtual;
///
- /// Authenticated decryption via FAuthObj (GCM, CCM). Kept as DecodeGCM for
- /// protected-API compatibility.
+ /// Authenticated decryption via FAuthObj (GCM, CCM, future AEAD modes).
+ /// Replaces the former protected DecodeGCM / DecodeCCM entry points.
///
- procedure DecodeGCM(Source, Dest: PUInt8Array; Size: Integer); virtual;
- ///
- /// Authenticated decryption via FAuthObj. Alias retained for protected-API
- /// compatibility with code that overrode DecodeCCM.
- ///
- procedure DecodeCCM(Source, Dest: PUInt8Array; Size: Integer); virtual;
+ ///
+ /// Encrypted ciphertext to decrypt
+ ///
+ ///
+ /// Plaintext after decryption
+ ///
+ ///
+ /// Number of bytes to decrypt
+ ///
+ procedure DecodeAuthenticated(Source, Dest: PUInt8Array; Size: Integer); virtual;
{$IFDEF DEC3_CMCTS}
///
/// double CBC
@@ -401,8 +410,10 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher)
/// specified by the official specification of the standard.
///
///
- /// List of bit lengths. If the cipher mode used is not an authenticated
- /// one, the array will just contain a single value of 0.
+ /// List of bit lengths prescribed by the authenticated mode. If the
+ /// cipher mode used is not an authenticated one, the array will just
+ /// contain a single value of 0. If an authenticated mode does not
+ /// prescribe tag lengths, an empty array is returned.
///
function GetStandardAuthenticationTagBitLengths:TStandardBitLengths;
@@ -430,12 +441,13 @@ TDECCipherModes = class(TDECCipher, IDECAuthenticatedCipher)
///
/// Some block chaining modes have the ability to authenticate the message
/// in addition to encrypting it. This property contains the generated
- /// authentication tag. Raises an EDECCipherException if this is
- /// called for a cipher mode not supporting authentication.
+ /// authentication tag. Call Done before reading it; reading the tag
+ /// before Done raises EDECCipherException. Raises an EDECCipherException
+ /// if this is called for a cipher mode not supporting authentication.
///
///
/// Exception raised if called for a cipher mode not supporting
- /// authentication.
+ /// authentication, or if the tag is read before Done.
///
property CalculatedAuthenticationResult : TBytes
read GetCalcAuthenticatonResult;
@@ -528,8 +540,8 @@ procedure TDECCipherModes.Encode(const Source; var Dest; DataSize: Integer);
cmOFBx: EncodeOFBx(@Source, @Dest, DataSize);
cmCFS8: EncodeCFS8(@Source, @Dest, DataSize);
cmCFSx: EncodeCFSx(@Source, @Dest, DataSize);
- cmGCM : EncodeGCM(@Source, @Dest, DataSize);
- cmCCM : EncodeCCM(@Source, @Dest, DataSize);
+ cmGCM : EncodeAuthenticated(@Source, @Dest, DataSize);
+ cmCCM : EncodeAuthenticated(@Source, @Dest, DataSize);
end;
end;
@@ -854,23 +866,14 @@ procedure TDECCipherModes.EncodeCTSx(Source, Dest: PUInt8Array; Size: Integer);
FState := csEncode;
end;
-procedure TDECCipherModes.EncodeGCM(Source, Dest: PUInt8Array; Size: Integer);
+procedure TDECCipherModes.EncodeAuthenticated(Source, Dest: PUInt8Array; Size: Integer);
begin
- if (Size < 0) then
- Size := 0;
-
- // Dispatch through FAuthObj (TGCM when Mode=cmGCM). Independent of EncodeCCM
- // so a subclass override of one entry point does not affect the other.
- FAuthObj.Encode(Source, Dest, Size);
-end;
+ if not Assigned(FAuthObj) then
+ raise EDECCipherException.CreateResFmt(@sInvalidModeForMethod, ['cmGCM or cmCCM']);
-procedure TDECCipherModes.EncodeCCM(Source, Dest: PUInt8Array; Size: Integer);
-begin
if (Size < 0) then
Size := 0;
- // Same FAuthObj.Encode body as EncodeGCM, but a separate protected entry so
- // overriding EncodeGCM does not change CCM behaviour (and vice versa).
FAuthObj.Encode(Source, Dest, Size);
end;
@@ -916,8 +919,8 @@ procedure TDECCipherModes.Decode(const Source; var Dest; DataSize: Integer);
cmOFBx: DecodeOFBx(@Source, @Dest, DataSize);
cmCFS8: DecodeCFS8(@Source, @Dest, DataSize);
cmCFSx: DecodeCFSx(@Source, @Dest, DataSize);
- cmGCM : DecodeGCM(@Source, @Dest, DataSize);
- cmCCM : DecodeCCM(@Source, @Dest, DataSize);
+ cmGCM : DecodeAuthenticated(@Source, @Dest, DataSize);
+ cmCCM : DecodeAuthenticated(@Source, @Dest, DataSize);
end;
end;
@@ -956,21 +959,14 @@ procedure TDECCipherModes.DecodeECBx(Source, Dest: PUInt8Array; Size: Integer);
end;
end;
-procedure TDECCipherModes.DecodeGCM(Source, Dest: PUInt8Array; Size: Integer);
+procedure TDECCipherModes.DecodeAuthenticated(Source, Dest: PUInt8Array; Size: Integer);
begin
- if (Size < 0) then
- Size := 0;
-
- // Independent of DecodeCCM — see EncodeGCM/EncodeCCM.
- FAuthObj.Decode(Source, Dest, Size);
-end;
+ if not Assigned(FAuthObj) then
+ raise EDECCipherException.CreateResFmt(@sInvalidModeForMethod, ['cmGCM or cmCCM']);
-procedure TDECCipherModes.DecodeCCM(Source, Dest: PUInt8Array; Size: Integer);
-begin
if (Size < 0) then
Size := 0;
- // Separate protected entry from DecodeGCM; same FAuthObj.Decode body.
FAuthObj.Decode(Source, Dest, Size);
end;
@@ -1130,8 +1126,8 @@ procedure TDECCipherModes.Done;
if Assigned(FAuthObj) then
begin
- // Finalize multi-call authentication (GCM) before optional ExpectedTag check.
- // CCM Done is a no-op on the mode object (tag already computed in Encode/Decode).
+ // Finalize authentication (GCM GHASH / CCM CBC-MAC tag) before optional
+ // ExpectedTag check. Both modes materialize the tag in FAuthObj.Done.
FAuthObj.Done;
if (Length(FAuthObj.ExpectedAuthenticationTag) > 0) and
diff --git a/Source/DECCipherModesCCM.pas b/Source/DECCipherModesCCM.pas
index 3f835f6..6adcf35 100644
--- a/Source/DECCipherModesCCM.pas
+++ b/Source/DECCipherModesCCM.pas
@@ -1,4 +1,4 @@
-{*****************************************************************************
+{*****************************************************************************
The DEC team (see file NOTICE.txt) licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
@@ -51,6 +51,19 @@ TCCM = class(TAuthenticatedCipherModesBase)
/// Init vector which is modified during processing
///
FInitVector : TBlock16Byte;
+ ///
+ /// CBC-MAC state after Encode/Decode; the authentication tag is derived
+ /// from this in Done (S_0 XOR T), not inside Encode/Decode.
+ ///
+ FMacBlock : TBlock16Byte;
+ ///
+ /// L parameter (octets of the length field) used to restore CTR_0 in Done
+ ///
+ FLengthFieldOctets : UInt16;
+ ///
+ /// True after Encode/Decode has produced a CBC-MAC state for Done
+ ///
+ FMacReady : Boolean;
///
/// Encodes or decodes a block of data using the supplied cipher
@@ -69,6 +82,11 @@ TCCM = class(TAuthenticatedCipherModesBase)
/// When true it is encrypting data, else it is descrypting data
///
procedure EncodeDecode(Source, Dest: PUInt8Array; Size: Integer; Encode: Boolean);
+ ///
+ /// Derives CalculatedAuthenticationTag from FMacBlock (S_0 XOR T).
+ /// Called from Done after Encode/Decode have finished the CBC-MAC.
+ ///
+ procedure FinalizeAuthenticationTag;
strict protected
///
/// Defines the length of the resulting authentication value in bit.
@@ -127,6 +145,14 @@ TCCM = class(TAuthenticatedCipherModesBase)
Dest : PUInt8Array;
Size : Integer); override;
+ ///
+ /// Materializes CalculatedAuthenticationTag from the CBC-MAC state.
+ /// Must be called after the last Encode/Decode (cipher Done does this).
+ /// Idempotent: a second call leaves the tag unchanged.
+ /// After finalization, Encode/Decode raise until Init is called again.
+ ///
+ procedure Done; override;
+
///
/// Returns a list of authentication tag lengths explicitely specified by
/// the official specification of the standard.
@@ -165,6 +191,7 @@ destructor TCCM.Destroy;
ProtectBytes(FOrigInitVector);
ProtectBuffer(FInitVector, SizeOf(FInitVector));
+ ProtectBuffer(FMacBlock, SizeOf(FMacBlock));
ProtectBytes(FCalcAuthenticationTag);
ProtectBytes(FExpectedAuthenticationTag);
@@ -181,9 +208,6 @@ procedure TCCM.EncodeDecode(Source, Dest: PUInt8Array;
Encode: Boolean);
var
ecc : TBlock16Byte; // encrypted counter
- FixedTagBuf : TBlock16Byte; // during calculation buffer of authentication tag
- // might need to be bigger than then one specified
- // by the user
len : Int32;
k, L : UInt16;
b : UInt8;
@@ -211,6 +235,8 @@ procedure TCCM.EncodeDecode(Source, Dest: PUInt8Array;
end;
begin
+ CheckNotFinalized;
+
if (Size > 0) and
((not Assigned(Source)) or (not Assigned(Dest))) then
raise EDECCipherException.Create(sInvalidSourcePointer);
@@ -234,6 +260,7 @@ procedure TCCM.EncodeDecode(Source, Dest: PUInt8Array;
// Force Length(FInitVector) + L = 15. Since nLen <= 13, L is at least 2
L := 15 - InitVectLen;
+ FLengthFieldOctets := L;
// compose B_0 = Flags | Nonce N | l(m)
// octet 0: Flags = 64*HdrPresent | 8*((tLen-2) div 2 | (L-1)
@@ -354,15 +381,10 @@ procedure TCCM.EncodeDecode(Source, Dest: PUInt8Array;
FEncryptionMethod(@Buf[0], @Buf[0], Length(Buf));
end;
- // setup counter for the tag (zero the count)
- for k := 15 downto 16-L do
- FInitVector[k] := 0;
-
- FEncryptionMethod(@FInitVector[0], @ecc[0], Length(ecc));
-
- // store the TAG/Authentication result value
- XORBuffers(Buf[0], ecc[0], 16, FixedTagBuf);
- Move(FixedTagBuf[0], FCalcAuthenticationTag[0], length(FCalcAuthenticationTag));
+ // Keep CBC-MAC state for Done; do not materialize the tag here so CCM
+ // shares the Init → Encode/Decode* → Done → tag lifecycle with GCM.
+ Move(Buf[0], FMacBlock[0], SizeOf(FMacBlock));
+ FMacReady := True;
ProtectBuffer(Buf, SizeOf(Buf));
end;
@@ -394,6 +416,48 @@ procedure TCCM.Init(EncryptionMethod : TEncodeDecodeMethod;
inherited;
FOrigInitVector := InitVector;
+ FMacReady := False;
+ FLengthFieldOctets := 0;
+end;
+
+procedure TCCM.FinalizeAuthenticationTag;
+var
+ ecc : TBlock16Byte;
+ FixedTagBuf : TBlock16Byte;
+ k : UInt16;
+begin
+ // Restore CTR_0 (zero the count) and encrypt to get S_0, then tag = T XOR S_0.
+ // See RFC 3610 §2.6 / NIST SP 800-38C: authentication tag is not part of Encode.
+ for k := 15 downto 16 - FLengthFieldOctets do
+ begin
+ FInitVector[k] := 0;
+ end;
+
+ FEncryptionMethod(@FInitVector[0], @ecc[0], Length(ecc));
+
+ XORBuffers(FMacBlock[0], ecc[0], 16, FixedTagBuf);
+ if (Length(FCalcAuthenticationTag) > 0) then
+ begin
+ Move(FixedTagBuf[0], FCalcAuthenticationTag[0], Length(FCalcAuthenticationTag));
+ end;
+
+ ProtectBuffer(ecc, SizeOf(ecc));
+ ProtectBuffer(FixedTagBuf, SizeOf(FixedTagBuf));
+end;
+
+procedure TCCM.Done;
+begin
+ if FFinalized then
+ Exit;
+
+ if not FMacReady then
+ begin
+ // Empty payload / AAD-only: format B_0 with l(m)=0 and process AAD.
+ EncodeDecode(nil, nil, 0, True);
+ end;
+
+ FinalizeAuthenticationTag;
+ inherited;
end;
end.
diff --git a/Source/DECCipherModesGCM.pas b/Source/DECCipherModesGCM.pas
index e88476e..00f2826 100644
--- a/Source/DECCipherModesGCM.pas
+++ b/Source/DECCipherModesGCM.pas
@@ -104,11 +104,6 @@ TGCM = class(TAuthenticatedCipherModesBase)
/// Number of unused bytes remaining in FKeystream (0..15)
///
FKeystreamRemainLen : Integer;
- ///
- /// True after Done has materialized the authentication tag.
- /// Prevents double-finalization and post-Done GHASH/CTR updates.
- ///
- FFinalized : Boolean;
///
/// XOR implementation for unsigned 128 bit numbers
@@ -346,8 +341,6 @@ TGCM = class(TAuthenticatedCipherModesBase)
implementation
resourcestring
- sGCMAlreadyFinalized =
- 'GCM authentication already finalized; call Init before further Encode/Decode';
sGCMAADLocked =
'GCM DataToAuthenticate cannot be changed after Encode/Decode has started or after Done';
sGCMAuthTagLength =
@@ -541,7 +534,6 @@ procedure TGCM.Init(EncryptionMethod : TEncodeDecodeMethod;
FKeystreamRemainLen := 0;
FKeystreamLeftover[0] := 0;
FKeystreamLeftover[1] := 0;
- FFinalized := False;
OldH := FH;
EncryptionMethod(@Nullbytes[0], @FH[0], 16);
@@ -658,8 +650,9 @@ procedure TGCM.Done;
begin
if FFinalized then
Exit;
+
FinalizeAuthenticationTag;
- FFinalized := True;
+ inherited;
end;
function TGCM.CalcGaloisHash(AuthenticatedData : PUInt8Array; AuthLen : integer; Ciphertext : PUInt8Array;
@@ -757,8 +750,7 @@ procedure TGCM.ApplyCTR(Source, Dest: PUInt8Array; Size: Integer);
procedure TGCM.Decode(Source, Dest: PUInt8Array; Size: Integer);
begin
- if FFinalized then
- raise EDECCipherException.CreateRes(@sGCMAlreadyFinalized);
+ CheckNotFinalized;
// AAD into GHASH once; tag finalized in Done (supports multi-call streams)
EnsureAuthDataHashed;
@@ -778,8 +770,7 @@ procedure TGCM.Decode(Source, Dest: PUInt8Array; Size: Integer);
procedure TGCM.Encode(Source, Dest: PUInt8Array; Size: Integer);
begin
- if FFinalized then
- raise EDECCipherException.CreateRes(@sGCMAlreadyFinalized);
+ CheckNotFinalized;
// AAD into GHASH once; tag finalized in Done (supports multi-call streams)
EnsureAuthDataHashed;
diff --git a/Unit Tests/Tests/TestDECCipherModesCCM.pas b/Unit Tests/Tests/TestDECCipherModesCCM.pas
index fb827fe..c83604c 100644
--- a/Unit Tests/Tests/TestDECCipherModesCCM.pas
+++ b/Unit Tests/Tests/TestDECCipherModesCCM.pas
@@ -56,6 +56,8 @@ TestTDECCCM = class(TTestCase)
procedure DoTestInitFailureIVTooShort;
procedure DoTestRFC3610(EncodeTest: Boolean);
procedure DoTestAuthenticationBitLengthWrong;
+ procedure DoReadTagBeforeDone;
+ procedure DoEncodeAfterDone;
public
procedure SetUp; override;
procedure TearDown; override;
@@ -85,6 +87,18 @@ TestTDECCCM = class(TTestCase)
procedure TestGetStandardAuthenticationTagBitLengths;
procedure TestGetExpectedAuthenticationResult;
procedure TestSetExpectedAuthenticationResult;
+ ///
+ /// Reading CalculatedAuthenticationResult before Done must raise.
+ ///
+ procedure TestCalculatedAuthenticationResultBeforeDoneRaises;
+ ///
+ /// Encode after Done must raise until Init is called again.
+ ///
+ procedure TestEncodeAfterDoneRejected;
+ ///
+ /// Done twice must leave CalculatedAuthenticationResult unchanged.
+ ///
+ procedure TestDoneIdempotent;
end;
@@ -760,6 +774,7 @@ procedure TestTDECCCM.DoTestRFC3610(EncodeTest: Boolean);
CheckEquals(true, CompareMem(@buf,@DecodeBuf,plen), 'Plaintext wrong');
end;
+ CipherAES.Done;
TagResult := CipherAES.CalculatedAuthenticationResult;
// Test the generated tag
@@ -772,6 +787,73 @@ procedure TestTDECCCM.DoTestRFC3610(EncodeTest: Boolean);
end;
end;
+procedure TestTDECCCM.DoReadTagBeforeDone;
+var
+ Tag: TBytes;
+begin
+ Tag := FCipherAES.CalculatedAuthenticationResult;
+end;
+
+procedure TestTDECCCM.DoEncodeAfterDone;
+var
+ Data: TBytes;
+begin
+ SetLength(Data, 4);
+ FillChar(Data[0], Length(Data), $A5);
+ FCipherAES.EncodeBytes(Data);
+end;
+
+procedure TestTDECCCM.TestCalculatedAuthenticationResultBeforeDoneRaises;
+var
+ TestData: TSingleAuthenticatedTestData;
+begin
+ TestData := FTestDataList[0].TestData[0];
+ FCipherAES.Init(BytesOf(TFormat_HexL.Decode(TestData.CryptKey)),
+ BytesOf(TFormat_HexL.Decode(TestData.InitVector)),
+ $FF);
+ FCipherAES.AuthenticationResultBitLength := FTestDataList[0].Taglen;
+ FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(TestData.AAD));
+ FCipherAES.EncodeBytes(TFormat_HexL.Decode(BytesOf(TestData.PT)));
+ CheckException(DoReadTagBeforeDone, EDECCipherException,
+ 'CalculatedAuthenticationResult before Done must raise EDECCipherException');
+end;
+
+procedure TestTDECCCM.TestEncodeAfterDoneRejected;
+var
+ TestData: TSingleAuthenticatedTestData;
+begin
+ TestData := FTestDataList[0].TestData[0];
+ FCipherAES.Init(BytesOf(TFormat_HexL.Decode(TestData.CryptKey)),
+ BytesOf(TFormat_HexL.Decode(TestData.InitVector)),
+ $FF);
+ FCipherAES.AuthenticationResultBitLength := FTestDataList[0].Taglen;
+ FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(TestData.AAD));
+ FCipherAES.EncodeBytes(TFormat_HexL.Decode(BytesOf(TestData.PT)));
+ FCipherAES.Done;
+ CheckException(DoEncodeAfterDone, EDECCipherException,
+ 'Encode after Done must raise EDECCipherException');
+end;
+
+procedure TestTDECCCM.TestDoneIdempotent;
+var
+ TestData: TSingleAuthenticatedTestData;
+ Tag1, Tag2: TBytes;
+begin
+ TestData := FTestDataList[0].TestData[0];
+ FCipherAES.Init(BytesOf(TFormat_HexL.Decode(TestData.CryptKey)),
+ BytesOf(TFormat_HexL.Decode(TestData.InitVector)),
+ $FF);
+ FCipherAES.AuthenticationResultBitLength := FTestDataList[0].Taglen;
+ FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(TestData.AAD));
+ FCipherAES.EncodeBytes(TFormat_HexL.Decode(BytesOf(TestData.PT)));
+ FCipherAES.Done;
+ Tag1 := Copy(FCipherAES.CalculatedAuthenticationResult);
+ FCipherAES.Done;
+ Tag2 := FCipherAES.CalculatedAuthenticationResult;
+ CheckTrue(IsEqual(Tag1, Tag2),
+ 'Second Done must not change CalculatedAuthenticationResult');
+end;
+
initialization
// Register all test cases to be run
{$IFDEF DUnitX}
diff --git a/Unit Tests/Tests/TestDECCipherModesGCM.pas b/Unit Tests/Tests/TestDECCipherModesGCM.pas
index e640c14..3b29596 100644
--- a/Unit Tests/Tests/TestDECCipherModesGCM.pas
+++ b/Unit Tests/Tests/TestDECCipherModesGCM.pas
@@ -158,6 +158,7 @@ TestTDECGCM = class(TTestCase)
procedure DoDecodeAfterDone;
procedure DoChangeAADAfterEncode;
procedure DoSetAuthTagBitLengthTooLong;
+ procedure DoReadTagBeforeDone;
public
procedure SetUp; override;
procedure TearDown; override;
@@ -198,6 +199,10 @@ TestTDECGCM = class(TTestCase)
///
procedure TestAADChangeAfterEncodeRejected;
///
+ /// Reading CalculatedAuthenticationResult before Done must raise.
+ ///
+ procedure TestCalculatedAuthenticationResultBeforeDoneRaises;
+ ///
/// AuthenticationResultBitLength > 128 must raise (tag buffer is 16 bytes).
///
procedure TestAuthTagBitLengthTooLongRejected;
@@ -921,6 +926,27 @@ procedure TestTDECGCM.TestDoneIdempotent;
'Second Done must not change CalculatedAuthenticationResult');
end;
+procedure TestTDECGCM.DoReadTagBeforeDone;
+var
+ Tag: TBytes;
+begin
+ Tag := FCipherAES.CalculatedAuthenticationResult;
+end;
+
+procedure TestTDECGCM.TestCalculatedAuthenticationResultBeforeDoneRaises;
+var
+ ptBytes: TBytes;
+begin
+ ptBytes := TFormat_HexL.Decode(BytesOf(cCAVS_MultiChunkPT));
+ FCipherAES.Init(BytesOf(TFormat_HexL.Decode(cCAVS_MultiChunkKey)),
+ BytesOf(TFormat_HexL.Decode(cCAVS_MultiChunkIV)), $FF);
+ FCipherAES.AuthenticationResultBitLength := cCAVS_MultiChunkTagBits;
+ FCipherAES.DataToAuthenticate := TFormat_HexL.Decode(BytesOf(cCAVS_MultiChunkAAD));
+ FCipherAES.EncodeBytes(ptBytes);
+ CheckException(DoReadTagBeforeDone, EDECCipherException,
+ 'CalculatedAuthenticationResult before Done must raise EDECCipherException');
+end;
+
procedure TestTDECGCM.DoEncodeAfterDone;
begin
FCipherAES.EncodeBytes(FCallAfterDoneData);