diff --git a/CryptoLib.Tests/src/Crypto/AeadModeTestBase.pas b/CryptoLib.Tests/src/Crypto/AeadModeTestBase.pas index bb178c4d..910d00a3 100644 --- a/CryptoLib.Tests/src/Crypto/AeadModeTestBase.pas +++ b/CryptoLib.Tests/src/Crypto/AeadModeTestBase.pas @@ -37,6 +37,7 @@ interface ClpAesEngine, ClpAesBitSlicedEngine, ClpCryptoLibTypes, + CipherKernelToggle, CryptoLibTestBase, BlockCipherTestBase; @@ -83,6 +84,25 @@ TAeadModeTestBase = class abstract(TCryptoLibAlgorithmTestCase) // Drive RunInPlaceCase across ALens, once with a 16-byte key and no AAD and // once with a 32-byte key and 20 bytes of AAD, from a fixed seed. procedure DoInPlaceSweep(const ALens: array of Int32; ASeed: Int64); + + // One streaming-equivalence case at a single plaintext length: feed the + // message through ProcessBytes in many chunk sizes and assert, for both + // directions and both out-of-place and in-place, that the concatenated + // output equals the one-shot result AND the summed per-call Result values + // (+ DoFinal) equal the total. Returns '' on success, else a description. + function RunStreamingCase(const ARandom: ISecureRandom; + APlainLen, AKeyLen: Int32; const AAad: TBytes): String; + + // Drive RunStreamingCase across ALens x {16-byte key/no AAD, 32-byte key/AAD}. + procedure DoStreamingSweep(const ALens: array of Int32; ASeed: Int64); + + // Parameterless worker (fixed lengths + seed) for the engine / kernel sweeps. + procedure DoTestStreaming; + published + // Chunk-boundary safety net for ProcessBytes: runs the streaming sweep under + // the kernel on/off toggle and across the extra engines (bit-sliced soft-bulk + // + scalar), so every dispatch tier is exercised. Inherited by every suite. + procedure TestStreamingEquivalence; end; implementation @@ -234,4 +254,138 @@ procedure TAeadModeTestBase.DoInPlaceSweep(const ALens: array of Int32; end; end; +function TAeadModeTestBase.RunStreamingCase(const ARandom: ISecureRandom; + APlainLen, AKeyLen: Int32; const AAad: TBytes): String; +const + CChunks: array [0 .. 9] of Int32 = (1, 7, 15, 16, 17, 31, 63, 64, 65, 1048576); +var + LK, LIV, LP, LRefCt, LOut: TBytes; + LParams: IAeadParameters; + LRefLen, LCi, LN, LMode: Int32; + LInPlace: Boolean; + + // Stream AInLen bytes of AIn through a fresh cipher (direction AEnc) in + // AChunk-sized pieces; return the total produced (summed ProcessBytes Results + // + DoFinal), with the truncated output in AOut. AInPlace => a single buffer + // holds the input and is written over itself (output cursor trails input). + function Stream(AEnc: Boolean; const AIn: TBytes; AInLen, AChunk: Int32; + AInPlace: Boolean; out AOut: TBytes): Int32; + var + LCipher: IAeadCipher; + LBuf: TBytes; + LOff, LInOff, LCs, LCap: Int32; + begin + LCipher := CreateAeadCipher; + LCipher.Init(AEnc, LParams as ICipherParameters); + LCap := LCipher.GetOutputSize(AInLen); + // In-place: one buffer must hold BOTH the input and the (possibly larger on + // encrypt) output; decrypt output is smaller but the input is bigger. + if AInPlace and (AInLen > LCap) then + LCap := AInLen; + System.SetLength(LBuf, LCap); + if AInPlace and (AInLen > 0) then + System.Move(AIn[0], LBuf[0], AInLen); + + LOff := 0; + LInOff := 0; + while LInOff < AInLen do + begin + LCs := AInLen - LInOff; + if LCs > AChunk then + LCs := AChunk; + if AInPlace then + LOff := LOff + LCipher.ProcessBytes(LBuf, LInOff, LCs, LBuf, LOff) + else + LOff := LOff + LCipher.ProcessBytes(AIn, LInOff, LCs, LBuf, LOff); + LInOff := LInOff + LCs; + end; + LOff := LOff + LCipher.DoFinal(LBuf, LOff); + System.SetLength(LBuf, LOff); + AOut := LBuf; + Result := LOff; + end; + +begin + Result := ''; + System.SetLength(LK, AKeyLen); + ARandom.NextBytes(LK); + System.SetLength(LIV, 12); + ARandom.NextBytes(LIV); + System.SetLength(LP, APlainLen); + if APlainLen > 0 then + ARandom.NextBytes(LP); + LParams := TAeadParameters.Create(TKeyParameter.Create(LK) as IKeyParameter, + 16 * 8, LIV, AAad); + + // One-shot reference ciphertext||tag. + LRefLen := Stream(True, LP, APlainLen, 1048576, False, LRefCt); + + for LCi := 0 to High(CChunks) do + for LMode := 0 to 1 do + begin + LInPlace := LMode = 1; + + // Encrypt: chunked output+tag must equal the one-shot reference, and the + // summed Result values must total the reference length. + LN := Stream(True, LP, APlainLen, CChunks[LCi], LInPlace, LOut); + if LN <> LRefLen then + Exit(Format('[enc len=%d chunk=%d inplace=%d total=%d want=%d] ', + [APlainLen, CChunks[LCi], LMode, LN, LRefLen])); + if not AreEqual(LOut, LRefCt) then + Exit(Format('[enc len=%d chunk=%d inplace=%d ct-mismatch] ', + [APlainLen, CChunks[LCi], LMode])); + + // Decrypt: chunked recovery must equal the plaintext, total = APlainLen. + LN := Stream(False, LRefCt, LRefLen, CChunks[LCi], LInPlace, LOut); + if LN <> APlainLen then + Exit(Format('[dec len=%d chunk=%d inplace=%d got=%d] ', + [APlainLen, CChunks[LCi], LMode, LN])); + if (APlainLen > 0) and (not AreEqual(LOut, LP)) then + Exit(Format('[dec len=%d chunk=%d inplace=%d pt-mismatch] ', + [APlainLen, CChunks[LCi], LMode])); + end; +end; + +procedure TAeadModeTestBase.DoStreamingSweep(const ALens: array of Int32; + ASeed: Int64); +var + LRnd: ISecureRandom; + LFails: String; + LI: Int32; + LAad: TBytes; +begin + LRnd := TSecureRandom.Create(); + LRnd.SetSeed(ASeed); + System.SetLength(LAad, 20); + LRnd.NextBytes(LAad); + LFails := ''; + for LI := 0 to High(ALens) do + LFails := LFails + RunStreamingCase(LRnd, ALens[LI], 16, nil); + for LI := 0 to High(ALens) do + LFails := LFails + RunStreamingCase(LRnd, ALens[LI], 32, LAad); + if LFails <> '' then + begin + if FCurrentEngineLabel <> '' then + Fail(Format('streaming %s [%s]: %s', + [ModeLabel, FCurrentEngineLabel, LFails])) + else + Fail(Format('streaming %s: %s', [ModeLabel, LFails])); + end; +end; + +procedure TAeadModeTestBase.DoTestStreaming; +const + CLens: array [0 .. 9] of Int32 = (0, 1, 16, 17, 63, 64, 65, 4 * 16 + 7, + 17 * 16 + 9, 200); +begin + DoStreamingSweep(CLens, Int64(20260727)); +end; + +procedure TAeadModeTestBase.TestStreamingEquivalence; +begin + DoTestStreaming; + RunWithCipherKernelToggle(DoTestStreaming); + ForEachExtraEngine(DoTestStreaming); +end; + end. diff --git a/CryptoLib.Tests/src/Crypto/ChaCha20Poly1305Tests.pas b/CryptoLib.Tests/src/Crypto/ChaCha20Poly1305Tests.pas index b014aadf..4ac25304 100644 --- a/CryptoLib.Tests/src/Crypto/ChaCha20Poly1305Tests.pas +++ b/CryptoLib.Tests/src/Crypto/ChaCha20Poly1305Tests.pas @@ -56,6 +56,10 @@ TTestChaCha20Poly1305 = class(TCryptoLibAlgorithmTestCase) strict private function InitCipher(AForEncryption: Boolean; const AParams: IAeadParameters): IChaCha20Poly1305; + // One streaming-equivalence case: feed the message through ProcessBytes in + // many chunk sizes, out-of-place and in-place, both directions, and assert + // the concatenated output + summed Result values match the one-shot result. + function RunStreamingCase(APlainLen: Int32; AWithAad: Boolean): String; protected procedure SetUp; override; @@ -67,6 +71,7 @@ TTestChaCha20Poly1305 = class(TCryptoLibAlgorithmTestCase) procedure TestRandomised; procedure TestExceptionsAndTampering; procedure TestAeadInputChunking; + procedure TestStreamingEquivalence; end; @@ -84,6 +89,87 @@ function TTestChaCha20Poly1305.InitCipher(AForEncryption: Boolean; Result := LCipher; end; +function TTestChaCha20Poly1305.RunStreamingCase(APlainLen: Int32; + AWithAad: Boolean): String; +const + CChunks: array [0 .. 9] of Int32 = (1, 7, 15, 16, 17, 31, 63, 64, 65, 1048576); +var + LK, LN, LA, LP, LRefCt, LOut: TBytes; + LParams: IAeadParameters; + LSeed: UInt32; + LI, LRefLen, LCi, LMode, LNret: Int32; + LInPlace: Boolean; + + function Stream(AEnc: Boolean; const AIn: TBytes; AInLen, AChunk: Int32; + AInPlace: Boolean; out AOut: TBytes): Int32; + var + LCipher: IChaCha20Poly1305; + LBuf: TBytes; + LOff, LInOff, LCs, LCap: Int32; + begin + LCipher := InitCipher(AEnc, LParams); + LCap := LCipher.GetOutputSize(AInLen); + if AInPlace and (AInLen > LCap) then + LCap := AInLen; + SetLength(LBuf, LCap); + if AInPlace and (AInLen > 0) then + System.Move(AIn[0], LBuf[0], AInLen); + LOff := 0; + LInOff := 0; + while LInOff < AInLen do + begin + LCs := AInLen - LInOff; + if LCs > AChunk then + LCs := AChunk; + if AInPlace then + LOff := LOff + LCipher.ProcessBytes(LBuf, LInOff, LCs, LBuf, LOff) + else + LOff := LOff + LCipher.ProcessBytes(AIn, LInOff, LCs, LBuf, LOff); + LInOff := LInOff + LCs; + end; + LOff := LOff + LCipher.DoFinal(LBuf, LOff); + SetLength(LBuf, LOff); + AOut := LBuf; + Result := LOff; + end; + +begin + Result := ''; + LK := THexEncoder.Decode( + '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'); + LN := THexEncoder.Decode('0c0b0a090807060504030201'); + if AWithAad then + LA := THexEncoder.Decode('0201000306050407') + else + LA := nil; + SetLength(LP, APlainLen); + LSeed := $C0FEBEEF; + for LI := 0 to APlainLen - 1 do + begin + LSeed := LSeed * 1664525 + 1013904223; + LP[LI] := Byte(LSeed shr 9); + end; + LParams := TAeadParameters.Create(TKeyParameter.Create(LK) as IKeyParameter, + 16 * 8, LN, LA); + + LRefLen := Stream(True, LP, APlainLen, 1048576, False, LRefCt); + + for LCi := 0 to High(CChunks) do + for LMode := 0 to 1 do + begin + LInPlace := LMode = 1; + LNret := Stream(True, LP, APlainLen, CChunks[LCi], LInPlace, LOut); + if (LNret <> LRefLen) or (not AreEqual(LOut, LRefCt)) then + Exit(Format('[enc len=%d chunk=%d inplace=%d] ', + [APlainLen, CChunks[LCi], LMode])); + LNret := Stream(False, LRefCt, LRefLen, CChunks[LCi], LInPlace, LOut); + if (LNret <> APlainLen) or + ((APlainLen > 0) and (not AreEqual(LOut, LP))) then + Exit(Format('[dec len=%d chunk=%d inplace=%d] ', + [APlainLen, CChunks[LCi], LMode])); + end; +end; + procedure TTestChaCha20Poly1305.SetUp; begin inherited; @@ -421,6 +507,23 @@ procedure TTestChaCha20Poly1305.TestExceptionsAndTampering; end; end; +procedure TTestChaCha20Poly1305.TestStreamingEquivalence; +const + CLens: array [0 .. 9] of Int32 = (0, 1, 63, 64, 65, 127, 128, 129, 200, 600); +var + LFails: String; + LI: Int32; +begin + LFails := ''; + for LI := 0 to High(CLens) do + begin + LFails := LFails + RunStreamingCase(CLens[LI], False); + LFails := LFails + RunStreamingCase(CLens[LI], True); + end; + if LFails <> '' then + Fail('ChaCha20Poly1305 streaming: ' + LFails); +end; + initialization {$IFDEF FPC} diff --git a/CryptoLib/src/Crypto/Modes/ClpCcmBlockCipher.pas b/CryptoLib/src/Crypto/Modes/ClpCcmBlockCipher.pas index dc327708..a29c7fc7 100644 --- a/CryptoLib/src/Crypto/Modes/ClpCcmBlockCipher.pas +++ b/CryptoLib/src/Crypto/Modes/ClpCcmBlockCipher.pas @@ -208,8 +208,7 @@ function TCcmBlockCipher.GetBufferedLength: Int32; procedure TCcmBlockCipher.WipeKeyMaterial; begin TArrayUtilities.Fill(FMacBlock, 0, System.Length(FMacBlock), Byte(0)); - if FData <> nil then - TArrayUtilities.Fill(FData, 0, System.Length(FData), Byte(0)); + TArrayUtilities.Fill(FData, 0, System.Length(FData), Byte(0)); end; procedure TCcmBlockCipher.Init(AForEncryption: Boolean; diff --git a/CryptoLib/src/Crypto/Modes/ClpChaCha20Poly1305.pas b/CryptoLib/src/Crypto/Modes/ClpChaCha20Poly1305.pas index 5fd48f09..74cd48eb 100644 --- a/CryptoLib/src/Crypto/Modes/ClpChaCha20Poly1305.pas +++ b/CryptoLib/src/Crypto/Modes/ClpChaCha20Poly1305.pas @@ -65,7 +65,6 @@ interface SInvalidNonceOctetLength = 'invalid nonce octet length'; SInBytesNil = 'input bytes cannot be nil'; SInvalidOperationState = 'invalid operation state for current cipher state'; - SOutBytesNil = 'output bytes cannot be nil'; type /// @@ -572,8 +571,6 @@ function TChaCha20Poly1305.DoFinal(const AOutput: TCryptoLibByteArray; var LResultLen: Int32; begin - if (AOutput = nil) then - raise EArgumentNilCryptoLibException.CreateRes(@SOutBytesNil); if (AOutOff < 0) then raise EArgumentCryptoLibException.CreateRes(@SCannotBeNegative); diff --git a/CryptoLib/src/Crypto/Modes/ClpEaxBlockCipher.pas b/CryptoLib/src/Crypto/Modes/ClpEaxBlockCipher.pas index 9e65f8ca..e98027f4 100644 --- a/CryptoLib/src/Crypto/Modes/ClpEaxBlockCipher.pas +++ b/CryptoLib/src/Crypto/Modes/ClpEaxBlockCipher.pas @@ -163,6 +163,22 @@ TEaxBlockCipher = class(TAbstractAeadBlockCipher, IEaxBlockCipher, procedure FinalizeBodyOmacPartial(APartialPtr: PByte; APartialLen: Int32); + // ----- Buffer bookkeeping shared across the four ProcessBytes paths. ----- + /// Copy up to (ATarget - FBufOff) bytes from AInput into FBufBlock, + /// advancing FBufOff / AInOff / ALen; return True once FBufBlock holds + /// ATarget bytes. + function DrainInto(const AInput: TCryptoLibByteArray; var AInOff: Int32; + var ALen: Int32; ATarget: Int32): Boolean; + /// Stash the trailing (< a full step) bytes into FBufBlock for + /// the next call. + procedure StashResidue(const AInput: TCryptoLibByteArray; + AInOff, ALen: Int32); + /// Build one confirmed ciphertext block in AScratch by stitching the + /// held FMacSize-byte tail of FBufBlock with the leading + /// (FBlockSize - FMacSize) bytes of AInput (decrypt bulk block 0). + procedure StitchDecryptBlock0(const AInput: TCryptoLibByteArray; + AInOff: Int32; const AScratch: TCryptoLibByteArray); + strict protected function GetAlgorithmName: String; override; function GetModeName: String; override; @@ -227,17 +243,11 @@ procedure TEaxBlockCipher.WipeKeyMaterial; TArrayUtilities.Fill(FAssociatedTextMac, 0, System.Length(FAssociatedTextMac), Byte(0)); TArrayUtilities.Fill(FBufBlock, 0, System.Length(FBufBlock), Byte(0)); - if FOmacState <> nil then - TArrayUtilities.Fill(FOmacState, 0, System.Length(FOmacState), Byte(0)); - if FOmacLookahead <> nil then - TArrayUtilities.Fill(FOmacLookahead, 0, System.Length(FOmacLookahead), - Byte(0)); - if FOmacB <> nil then - TArrayUtilities.Fill(FOmacB, 0, System.Length(FOmacB), Byte(0)); - if FOmacP <> nil then - TArrayUtilities.Fill(FOmacP, 0, System.Length(FOmacP), Byte(0)); - if FCtrBlock <> nil then - TArrayUtilities.Fill(FCtrBlock, 0, System.Length(FCtrBlock), Byte(0)); + TArrayUtilities.Fill(FOmacState, 0, System.Length(FOmacState), Byte(0)); + TArrayUtilities.Fill(FOmacLookahead, 0, System.Length(FOmacLookahead), Byte(0)); + TArrayUtilities.Fill(FOmacB, 0, System.Length(FOmacB), Byte(0)); + TArrayUtilities.Fill(FOmacP, 0, System.Length(FOmacP), Byte(0)); + TArrayUtilities.Fill(FCtrBlock, 0, System.Length(FCtrBlock), Byte(0)); end; procedure TEaxBlockCipher.Init(AForEncryption: Boolean; @@ -568,10 +578,43 @@ function TEaxBlockCipher.ProcessByte(AInput: Byte; Result := Process(AInput, AOutput, AOutOff); end; +function TEaxBlockCipher.DrainInto(const AInput: TCryptoLibByteArray; + var AInOff: Int32; var ALen: Int32; ATarget: Int32): Boolean; +var + LToFill: Int32; +begin + LToFill := ATarget - FBufOff; + if LToFill > ALen then + LToFill := ALen; + System.Move(AInput[AInOff], FBufBlock[FBufOff], LToFill); + FBufOff := FBufOff + LToFill; + AInOff := AInOff + LToFill; + ALen := ALen - LToFill; + Result := FBufOff = ATarget; +end; + +procedure TEaxBlockCipher.StashResidue(const AInput: TCryptoLibByteArray; + AInOff, ALen: Int32); +begin + if ALen > 0 then + begin + System.Move(AInput[AInOff], FBufBlock[FBufOff], ALen); + FBufOff := FBufOff + ALen; + end; +end; + +procedure TEaxBlockCipher.StitchDecryptBlock0(const AInput: TCryptoLibByteArray; + AInOff: Int32; const AScratch: TCryptoLibByteArray); +begin + System.Move(FBufBlock[0], AScratch[0], FMacSize); + if FBlockSize > FMacSize then + System.Move(AInput[AInOff], AScratch[FMacSize], FBlockSize - FMacSize); +end; + function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; AInOff, ALen: Int32; const AOutput: TCryptoLibByteArray; AOutOff: Int32): Int32; var - LI, LResultLen, LToFill, LBulkBlocks, LBulkBytes, LKernelBlocks, LLastInOff, + LI, LResultLen, LBulkBlocks, LBulkBytes, LKernelBlocks, LLastInOff, LLastOutOff, LMiddleBlocks, LMiddleInOff, LMiddleOutOff: Int32; LScratch: TCryptoLibByteArray; begin @@ -591,14 +634,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; // DoFinal's subkey-B-vs-P decision. K < 4 falls through to scalar. if FUseFusedBody and (not FForEncryption) and (ALen > 0) then begin - LToFill := (FBlockSize + FMacSize) - FBufOff; - if LToFill > ALen then - LToFill := ALen; - System.Move(AInput[AInOff], FBufBlock[FBufOff], LToFill); - FBufOff := FBufOff + LToFill; - AInOff := AInOff + LToFill; - ALen := ALen - LToFill; - if FBufOff = FBlockSize + FMacSize then + if DrainInto(AInput, AInOff, ALen, FBlockSize + FMacSize) then begin TCheck.OutputLength(AOutput, AOutOff + LResultLen, FBlockSize, SOutputBufferTooShort); @@ -626,10 +662,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; // previous buffer with the first (FBlockSize - FMacSize) AInput // bytes into one confirmed ciphertext block). System.SetLength(LScratch, FBlockSize); - System.Move(FBufBlock[0], LScratch[0], FMacSize); - if FBlockSize > FMacSize then - System.Move(AInput[AInOff], LScratch[FMacSize], - FBlockSize - FMacSize); + StitchDecryptBlock0(AInput, AInOff, LScratch); CtrEncryptBlock(@LScratch[0], @AOutput[AOutOff + LResultLen]); FlushOmacLookahead(); System.Move(LScratch[0], FOmacLookahead[0], FBlockSize); @@ -661,10 +694,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; // directly from AInput. OMAC lookahead is threaded through each // block so the last confirmed block is always the lookahead. System.SetLength(LScratch, FBlockSize); - System.Move(FBufBlock[0], LScratch[0], FMacSize); - if FBlockSize > FMacSize then - System.Move(AInput[AInOff], LScratch[FMacSize], - FBlockSize - FMacSize); + StitchDecryptBlock0(AInput, AInOff, LScratch); CtrEncryptBlock(@LScratch[0], @AOutput[AOutOff + LResultLen]); FlushOmacLookahead(); System.Move(LScratch[0], FOmacLookahead[0], FBlockSize); @@ -692,11 +722,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; ALen := ALen - LBulkBytes; end; - if ALen > 0 then - begin - System.Move(AInput[AInOff], FBufBlock[FBufOff], ALen); - FBufOff := FBufOff + ALen; - end; + StashResidue(AInput, AInOff, ALen); Result := LResultLen; Exit; @@ -711,14 +737,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; begin if FBufOff > 0 then begin - LToFill := FBlockSize - FBufOff; - if LToFill > ALen then - LToFill := ALen; - System.Move(AInput[AInOff], FBufBlock[FBufOff], LToFill); - FBufOff := FBufOff + LToFill; - AInOff := AInOff + LToFill; - ALen := ALen - LToFill; - if FBufOff = FBlockSize then + if DrainInto(AInput, AInOff, ALen, FBlockSize) then begin TCheck.OutputLength(AOutput, AOutOff + LResultLen, FBlockSize, SOutputBufferTooShort); @@ -772,11 +791,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; ALen := ALen - LBulkBytes; end; - if ALen > 0 then - begin - System.Move(AInput[AInOff], FBufBlock[FBufOff], ALen); - FBufOff := FBufOff + ALen; - end; + StashResidue(AInput, AInOff, ALen); Result := LResultLen; Exit; @@ -797,14 +812,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; // Process() would. if FBufOff > 0 then begin - LToFill := FBlockSize - FBufOff; - if LToFill > ALen then - LToFill := ALen; - System.Move(AInput[AInOff], FBufBlock[FBufOff], LToFill); - FBufOff := FBufOff + LToFill; - AInOff := AInOff + LToFill; - ALen := ALen - LToFill; - if FBufOff = FBlockSize then + if DrainInto(AInput, AInOff, ALen, FBlockSize) then begin TCheck.OutputLength(AOutput, AOutOff + LResultLen, FBlockSize, SOutputBufferTooShort); @@ -834,11 +842,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; // Residue (< FBlockSize bytes) goes into FBufBlock; DoFinal will // consume it. - if ALen > 0 then - begin - System.Move(AInput[AInOff], FBufBlock[FBufOff], ALen); - FBufOff := FBufOff + ALen; - end; + StashResidue(AInput, AInOff, ALen); Result := LResultLen; Exit; @@ -856,14 +860,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; // this step may trigger it. After this step either FBufOff equals // FMacSize and ALen may still be > 0, or we ran out of input while // filling. - LToFill := (FBlockSize + FMacSize) - FBufOff; - if LToFill > ALen then - LToFill := ALen; - System.Move(AInput[AInOff], FBufBlock[FBufOff], LToFill); - FBufOff := FBufOff + LToFill; - AInOff := AInOff + LToFill; - ALen := ALen - LToFill; - if FBufOff = FBlockSize + FMacSize then + if DrainInto(AInput, AInOff, ALen, FBlockSize + FMacSize) then begin TCheck.OutputLength(AOutput, AOutOff + LResultLen, FBlockSize, SOutputBufferTooShort); @@ -891,10 +888,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; SOutputBufferTooShort); System.SetLength(LScratch, FBlockSize); - System.Move(FBufBlock[0], LScratch[0], FMacSize); - if FBlockSize > FMacSize then - System.Move(AInput[AInOff], LScratch[FMacSize], - FBlockSize - FMacSize); + StitchDecryptBlock0(AInput, AInOff, LScratch); FMac.BlockUpdate(LScratch, 0, FBlockSize); FCipher.ProcessBlock(LScratch, 0, AOutput, AOutOff + LResultLen); @@ -917,11 +911,7 @@ function TEaxBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; // Pack remaining bytes (strictly less than FBlockSize on this // branch) into FBufBlock. No flush is possible with < FBlockSize // new bytes: we would not reach FBlockSize + FMacSize total. - if ALen > 0 then - begin - System.Move(AInput[AInOff], FBufBlock[FBufOff], ALen); - FBufOff := FBufOff + ALen; - end; + StashResidue(AInput, AInOff, ALen); Result := LResultLen; Exit; diff --git a/CryptoLib/src/Crypto/Modes/ClpGcmBlockCipher.pas b/CryptoLib/src/Crypto/Modes/ClpGcmBlockCipher.pas index aaccccf8..cb939896 100644 --- a/CryptoLib/src/Crypto/Modes/ClpGcmBlockCipher.pas +++ b/CryptoLib/src/Crypto/Modes/ClpGcmBlockCipher.pas @@ -280,6 +280,16 @@ TGcmBlockCipher = class(TAbstractAeadBlockCipher, IGcmBlockCipher, var AInOff: Int32; var ALen: Int32; const AOutBuf: TCryptoLibByteArray; var AOutOff: Int32; ALimit: Int32); + // Run the whole-block dispatch staircase (widest supported tier down to a + // 2-block tail plus a single trailing block) over [AInOff, AInOff+ALen). + // AHoldBack is the byte count kept back beyond the last processed block: 0 + // for encrypt, FMacSize for decrypt (so the trailing tag is never consumed + // as payload). Direction selects the encrypt vs decrypt tier methods; the + // tier methods themselves are unchanged. Advances AInOff/ALen/AOutOff. + procedure RunTieredWholeBlocks(AForEncrypt: Boolean; + const AInBuf: TCryptoLibByteArray; var AInOff: Int32; var ALen: Int32; + const AOutBuf: TCryptoLibByteArray; var AOutOff: Int32; AHoldBack: Int32); + // --------------------------------------------------------------------- // CTR keystream generation helpers (scalar + 4-way + 8-way). // --------------------------------------------------------------------- @@ -698,11 +708,85 @@ function TGcmBlockCipher.ProcessByte(AInput: Byte; Result := 0; end; +procedure TGcmBlockCipher.RunTieredWholeBlocks(AForEncrypt: Boolean; + const AInBuf: TCryptoLibByteArray; var AInOff: Int32; var ALen: Int32; + const AOutBuf: TCryptoLibByteArray; var AOutOff: Int32; AHoldBack: Int32); + + // Consume every remaining 2-block group above the hold-back. After a 4-tier + // drain this iterates at most once (ALen < AHoldBack + 4*BlockSize); in the + // scalar case it is the full 2-block loop. + procedure RunTwoBlockTail; + begin + while ALen >= AHoldBack + BlockSize * 2 do + begin + CipherBlocks2(AInBuf, AInOff, AOutBuf, AOutOff, AForEncrypt); + System.Inc(AInOff, BlockSize * 2); + System.Dec(ALen, BlockSize * 2); + System.Inc(AOutOff, BlockSize * 2); + end; + end; + +begin + // Widest supported tier first. Each tier method loops internally over its + // stride, so after it the remainder is < that stride; the encrypt/decrypt + // pair share the same shape (decrypt just carries the tag hold-back as its + // per-tier limit). The 8-way branch runs the 4-way tier without re-checking + // IsFourWaySupported because 8-way implies 4-way; FSoftBulk is an exclusive + // peer of the hardware tiers, never a fall-through below them. + if TGcmBlockCipher.IsEightWaySupported and (ALen >= AHoldBack + BlockSize * 8) + then + begin + if AForEncrypt then + EncryptBlocks8(AInBuf, AInOff, ALen, AOutBuf, AOutOff) + else + DecryptBlocks8(AInBuf, AInOff, ALen, AOutBuf, AOutOff, + AHoldBack + BlockSize * 8); + if ALen >= AHoldBack + BlockSize * 4 then + begin + if AForEncrypt then + EncryptBlocks4(AInBuf, AInOff, ALen, AOutBuf, AOutOff) + else + DecryptBlocks4(AInBuf, AInOff, ALen, AOutBuf, AOutOff, + AHoldBack + BlockSize * 4); + end; + RunTwoBlockTail; + end + else if TGcmBlockCipher.IsFourWaySupported and + (ALen >= AHoldBack + BlockSize * 4) then + begin + if AForEncrypt then + EncryptBlocks4(AInBuf, AInOff, ALen, AOutBuf, AOutOff) + else + DecryptBlocks4(AInBuf, AInOff, ALen, AOutBuf, AOutOff, + AHoldBack + BlockSize * 4); + RunTwoBlockTail; + end + else if FSoftBulk and (ALen >= AHoldBack + BlockSize * 4) then + begin + if AForEncrypt then + EncryptBlocksSoftBulk4(AInBuf, AInOff, ALen, AOutBuf, AOutOff) + else + DecryptBlocksSoftBulk4(AInBuf, AInOff, ALen, AOutBuf, AOutOff, + AHoldBack + BlockSize * 4); + RunTwoBlockTail; + end + else + RunTwoBlockTail; + + // Trailing single block above the hold-back (encrypt: ALen >= BlockSize; + // decrypt: ALen >= BlockSize + FMacSize, i.e. Length(FBufBlock)). + if ALen >= AHoldBack + BlockSize then + begin + CipherBlock(AInBuf, AInOff, AOutBuf, AOutOff, AForEncrypt); + System.Inc(AInOff, BlockSize); + System.Dec(ALen, BlockSize); + end; +end; + function TGcmBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; AInOff, ALen: Int32; const AOutput: TCryptoLibByteArray; AOutOff: Int32): Int32; var LResultLen, LAvailable: Int32; - LBufLen, LThresh2, LThresh4, LThresh8: Int32; LBlocksNeeded: UInt32; begin CheckStatus(); @@ -747,67 +831,7 @@ function TGcmBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; AOutOff := AOutOff + BlockSize; end; - if TGcmBlockCipher.IsEightWaySupported and (ALen >= BlockSize * 8) then - begin - EncryptBlocks8(AInput, AInOff, ALen, AOutput, AOutOff); - if ALen >= BlockSize * 4 then - begin - EncryptBlocks4(AInput, AInOff, ALen, AOutput, AOutOff); - if ALen >= BlockSize * 2 then - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, True); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end - else if ALen >= BlockSize * 2 then - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, True); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end - else if TGcmBlockCipher.IsFourWaySupported and (ALen >= BlockSize * 4) then - begin - EncryptBlocks4(AInput, AInOff, ALen, AOutput, AOutOff); - if ALen >= BlockSize * 2 then - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, True); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end - else if FSoftBulk and (ALen >= BlockSize * 4) then - begin - EncryptBlocksSoftBulk4(AInput, AInOff, ALen, AOutput, AOutOff); - if ALen >= BlockSize * 2 then - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, True); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end - else - begin - while ALen >= BlockSize * 2 do - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, True); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end; - - if ALen >= BlockSize then - begin - CipherBlock(AInput, AInOff, AOutput, AOutOff, True); - AInOff := AInOff + BlockSize; - ALen := ALen - BlockSize; - end; + RunTieredWholeBlocks(True, AInput, AInOff, ALen, AOutput, AOutOff, 0); FBufOff := ALen; System.Move(AInput[AInOff], FBufBlock[0], FBufOff); @@ -867,72 +891,11 @@ function TGcmBlockCipher.ProcessBytes(const AInput: TCryptoLibByteArray; CipherBlock(FBufBlock, 0, AOutput, AOutOff, False); AOutOff := AOutOff + BlockSize; - LBufLen := System.Length(FBufBlock); - LThresh2 := LBufLen + BlockSize; - LThresh4 := LBufLen + (BlockSize * 3); - LThresh8 := LBufLen + (BlockSize * 7); - - if TGcmBlockCipher.IsEightWaySupported and (ALen >= LThresh8) then - begin - DecryptBlocks8(AInput, AInOff, ALen, AOutput, AOutOff, LThresh8); - if ALen >= LThresh4 then - begin - DecryptBlocks4(AInput, AInOff, ALen, AOutput, AOutOff, LThresh4); - if ALen >= LThresh2 then - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, False); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end - else if ALen >= LThresh2 then - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, False); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end - else if TGcmBlockCipher.IsFourWaySupported and (ALen >= LThresh4) then - begin - DecryptBlocks4(AInput, AInOff, ALen, AOutput, AOutOff, LThresh4); - if ALen >= LThresh2 then - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, False); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end - else if FSoftBulk and (ALen >= LThresh4) then - begin - DecryptBlocksSoftBulk4(AInput, AInOff, ALen, AOutput, AOutOff, LThresh4); - if ALen >= LThresh2 then - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, False); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end - else - begin - while ALen >= LThresh2 do - begin - CipherBlocks2(AInput, AInOff, AOutput, AOutOff, False); - AInOff := AInOff + (BlockSize * 2); - ALen := ALen - (BlockSize * 2); - AOutOff := AOutOff + (BlockSize * 2); - end; - end; - - if ALen >= LBufLen then - begin - CipherBlock(AInput, AInOff, AOutput, AOutOff, False); - AInOff := AInOff + BlockSize; - ALen := ALen - BlockSize; - end; + // Hold-back = FMacSize (= Length(FBufBlock) - BlockSize on decrypt) so the + // trailing tag is never consumed as payload; the thresholds LThreshN in the + // former inline staircase equalled AHoldBack + BlockSize*N. + RunTieredWholeBlocks(False, AInput, AInOff, ALen, AOutput, AOutOff, + System.Length(FBufBlock) - BlockSize); FBufOff := ALen; System.Move(AInput[AInOff], FBufBlock[0], FBufOff); diff --git a/CryptoLib/src/Crypto/Modes/ClpGcmSivBlockCipher.pas b/CryptoLib/src/Crypto/Modes/ClpGcmSivBlockCipher.pas index 22f84edf..8c0bf0ed 100644 --- a/CryptoLib/src/Crypto/Modes/ClpGcmSivBlockCipher.pas +++ b/CryptoLib/src/Crypto/Modes/ClpGcmSivBlockCipher.pas @@ -368,10 +368,8 @@ procedure TGcmSivBlockCipher.WipeKeyMaterial; TArrayUtilities.Fill(FTheGHash, 0, System.Length(FTheGHash), Byte(0)); TArrayUtilities.Fill(FTheReverse, 0, System.Length(FTheReverse), Byte(0)); TArrayUtilities.Fill(FHPow128, 0, System.Length(FHPow128), Byte(0)); - if FThePlain <> nil then - TArrayUtilities.Fill(FThePlain, 0, System.Length(FThePlain), Byte(0)); - if FTheEncData <> nil then - TArrayUtilities.Fill(FTheEncData, 0, System.Length(FTheEncData), Byte(0)); + TArrayUtilities.Fill(FThePlain, 0, System.Length(FThePlain), Byte(0)); + TArrayUtilities.Fill(FTheEncData, 0, System.Length(FTheEncData), Byte(0)); TArrayUtilities.Fill(FMacBlock, 0, System.Length(FMacBlock), Byte(0)); end; diff --git a/CryptoLib/src/Crypto/Modes/ClpOcbBlockCipher.pas b/CryptoLib/src/Crypto/Modes/ClpOcbBlockCipher.pas index 03b1b487..b0255002 100644 --- a/CryptoLib/src/Crypto/Modes/ClpOcbBlockCipher.pas +++ b/CryptoLib/src/Crypto/Modes/ClpOcbBlockCipher.pas @@ -217,8 +217,7 @@ destructor TOcbBlockCipher.Destroy; if FL <> nil then begin for LI := 0 to FL.Count - 1 do - if FL[LI] <> nil then - TArrayUtilities.Fill(FL[LI], 0, System.Length(FL[LI]), Byte(0)); + TArrayUtilities.Fill(FL[LI], 0, System.Length(FL[LI]), Byte(0)); end; FL.Free; inherited Destroy;