Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions Docs/plans/2026-07-25-aead-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# AEAD Architecture Package (from PR #90 Concern A)

**Branch:** `package/aead-architecture`
**Base:** `development`
**Donor:** [PR #90](https://github.com/MHumm/DelphiEncryptionCompendium/pull/90) (architecture only)
**GCM streaming reference:** PR #99 multi-call GCM semantics (absorbed)

## Goal

Separate AEAD architecture from ChaCha/Poly1305 (Cleanup-Roadmap Concern A vs B).

## What landed

| Area | Decision |
|------|----------|
| Mode object | Single `FAuthObj: TAuthenticatedCipherModesBase` instead of dual `FGCM`/`FCCM` |
| Public API | `IDECAuthenticatedCipher` unchanged |
| Protected API | `EncodeGCM`/`DecodeGCM`/`EncodeCCM`/`DecodeCCM` kept as dispatch wrappers (no rename break) |
| GCM streaming | PR #99 engine: GHASH partial + CTR keystream remainder + `FFinalized` |
| Tag timing | Tag is valid only after `Done` (multi-call GHASH). Callers must `Done` before reading the tag |
| CCM | Still one-shot; base `Done` is a no-op for the CCM object; ExpectedTag still verified in `TDECCipherModes.Done` |
| Poly1305 / ChaCha | **Out of scope** (package B) |
| `cmPoly1305` enum | **Not** added in this package |

## What was rejected from PR #90 (as-is)

1. **`fIsLastBlock` CTR model** — treats any non-16-aligned *call* as end of message; breaks multi-chunk streams (e.g. 7+25).
2. **Missing keystream remainder** — wrong ciphertext after partial blocks.
3. **Abstract hooks with empty `inherited`** — EAbstractError risk on CCM stubs.
4. **Burn-on-finalize of only H** without post-Done lock — unsafe continued Encode after Done.
5. **Shipping Poly1305/ChaCha/CPU units** with the architecture package.

## Lifecycle (binding)

```
Init(Key, IV)
→ set DataToAuthenticate / AuthenticationResultBitLength / ExpectedAuthenticationResult
→ Encode* or Decode* (multi-call OK for GCM; one-shot for CCM)
→ Done (finalizes GCM tag; verifies ExpectedTag if set)
→ read CalculatedAuthenticationResult
```

After `Done`, further GCM `Encode`/`Decode` raise until `Init` again. `Done` is idempotent for the tag.

## Files

| File | Role |
|------|------|
| `Source/DECAuthenticatedCipherModesBase.pas` | Shared AEAD base + virtual `Done` |
| `Source/DECCipherModes.pas` | `FAuthObj` wiring, leak-safe `InitMode`, unified `Done` |
| `Source/DECCipherModesGCM.pas` | Multi-call GCM (PR #99 semantics) |
| `Unit Tests/Tests/TestDECCipherModesGCM.pas` | Multi-chunk + Done lifecycle tests |
| `Unit Tests/Data/gcmEncryptExtIV256_large.rsp` | Corrected large-vector tag |

## Test results (Delphi 13, Win32 Console DUnit)

- GCM suite: **19/19** green (including multi-chunk 16+16, 7+25, Done lifecycle)
- CCM suite: **16/16** green
- Non-AEAD cipher modes: green
- Residual reds: pre-existing Keccak vector issues only (separate PRs #98/#100)

## Package B next

ChaCha20 / XChaCha20 / Poly1305 AEAD on top of this base (`FAuthObj` + `Done` contract).
50 changes: 41 additions & 9 deletions Source/DECAuthenticatedCipherModesBase.pas
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -40,9 +40,9 @@ EDECAuthLengthException = class(EDECException);

/// <summary>
/// A method of this type needs to be supplied for encrypting or decrypting
/// a block via this GCM algorithm. The method is implemented as a parameter,
/// to avoid the need to bring TGCM in the inheritance chain. TGCM thus can
/// be used for composition instead of inheritance.
/// a block via an authenticated cipher mode. The method is implemented as a
/// parameter to allow composition instead of inheritance (e.g. TGCM/TCCM
/// hold a reference to the underlying block cipher's encode method).
/// </summary>
/// <param name="Source">
/// Data to be encrypted
Expand All @@ -56,8 +56,24 @@ EDECAuthLengthException = class(EDECException);
TEncodeDecodeMethod = procedure(Source, Dest: Pointer; Size: Integer) of Object;

/// <summary>
/// Base class for authenticated cipher modes
/// Base class for authenticated cipher modes (GCM, CCM, future AEAD modes).
/// </summary>
/// <remarks>
/// Lifecycle for multi-call capable modes (e.g. GCM):
/// <para>
/// Init → set AAD / tag length / expected tag → Encode/Decode* → Done →
/// read CalculatedAuthenticationTag.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// CCM remains one-shot (single Encode/Decode with full message length);
/// Done still verifies ExpectedAuthenticationTag when set.
/// </para>
/// </remarks>
TAuthenticatedCipherModesBase = class(TObject)
strict protected
/// <summary>
Expand Down Expand Up @@ -93,7 +109,7 @@ TAuthenticatedCipherModesBase = class(TObject)
/// </param>
procedure SetAuthenticationTagLength(const Value: UInt32); virtual;
/// <summary>
/// Returns the length of the calculated authehtication value in bit
/// Returns the length of the calculated authentication value in bit
/// </summary>
/// <returns>
/// Length of the calculated authentication value in bit
Expand All @@ -114,7 +130,8 @@ TAuthenticatedCipherModesBase = class(TObject)
InitVector : TBytes); virtual;

/// <summary>
/// Encodes a block of data using the supplied cipher
/// Encodes a block of data using the supplied cipher. May be called
/// multiple times for modes that support streaming (e.g. GCM).
/// </summary>
/// <param name="Source">
/// Plain text to encrypt
Expand All @@ -129,7 +146,8 @@ TAuthenticatedCipherModesBase = class(TObject)
Dest : PUInt8Array;
Size : Integer); virtual; abstract;
/// <summary>
/// Decodes a block of data using the supplied cipher
/// Decodes a block of data using the supplied cipher. May be called
/// multiple times for modes that support streaming (e.g. GCM).
/// </summary>
/// <param name="Source">
/// Encrypted ciphertext to decrypt
Expand All @@ -144,6 +162,13 @@ TAuthenticatedCipherModesBase = class(TObject)
Dest : PUInt8Array;
Size : Integer); virtual; abstract;

/// <summary>
/// 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).
/// </summary>
procedure Done; virtual;

/// <summary>
/// Returns a list of authentication tag lengths explicitely specified by
/// the official specification of the standard.
Expand All @@ -170,7 +195,8 @@ TAuthenticatedCipherModesBase = class(TObject)
read GetAuthenticationTagBitLength
write SetAuthenticationTagLength;
/// <summary>
/// Calculated authentication value
/// Calculated authentication value. For multi-call modes this is only
/// complete after Done has been called.
/// </summary>
property CalculatedAuthenticationTag : TBytes
read FCalcAuthenticationTag
Expand Down Expand Up @@ -221,6 +247,12 @@ procedure TAuthenticatedCipherModesBase.Init(EncryptionMethod : TEncodeDecodeMet
FEncryptionMethod := EncryptionMethod;
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.
end;

procedure TAuthenticatedCipherModesBase.SetAuthenticationTagLength(const Value: UInt32);
begin
FCalcAuthenticationTagLength := Value shr 3;
Expand Down
Loading