From 28527d23090316b9dbb69e7939692f0797b501f5 Mon Sep 17 00:00:00 2001 From: Ugochukwu Mmaduekwe Date: Wed, 22 Jul 2026 01:53:24 +0100 Subject: [PATCH] Add constant-time bit-sliced AES engine (TAesBitSlicedEngine) Add a table-free, constant-time software AES engine that drops into the existing IBlockCipher/IAesEngine seam alongside the table-based TAesEngine. The engine holds the AES state as eight bit-sliced UInt64 words and realises the S-box as a Boyar-Peralta Boolean circuit, so it performs no data- or key-dependent memory lookups and takes no secret-dependent branch - closing the cache-timing side channel inherent to the table engine. The key schedule is table-free and constant-time as well. Both directions are implemented and are bit-exact with TAesEngine for 128/192/256-bit keys; word rotations go through TBitOperations. The engine is opt-in by construction and is not registered as a default anywhere; paired with the branchless TBasicGcmMultiplier it composes into a constant-time software AES-GCM stack. Tests (TTestAesBitSliced): inherits the standard AES engine battery (known vectors, 10k-iteration Monte Carlo, bad-parameter rejection) and adds FIPS-197 KATs both directions, byte-for-byte parity against TAesEngine over random keys/blocks, encrypt/decrypt round-trips, a CBC round-trip through the mode, and end-to-end AES-GCM through the manual composition. --- .../Delphi/CryptoLib.BenchmarkConsole.dpr | 1 + .../Delphi.Examples/CryptoLib.Examples.dpr | 1 + .../Delphi.Tests/CryptoLib.Tests.Mobile.dpr | 2 + .../Delphi.Tests/CryptoLib.Tests.Mobile.dproj | 2 + .../Delphi.Tests/CryptoLib.Tests.dpr | 2 + .../FreePascal.Tests/CryptoLib.Tests.lpi | 6 +- .../FreePascal.Tests/CryptoLib.lpr | 6 +- .../FreePascal.Tests/CryptoLibConsole.lpi | 6 +- .../FreePascal.Tests/CryptoLibConsole.lpr | 33 +- .../src/Crypto/AesBitSlicedTests.pas | 455 ++++++++++ .../Crypto/Engines/ClpAesBitSlicedEngine.pas | 802 ++++++++++++++++++ .../Delphi/CryptoLib4PascalPackage.dpk | 1 + .../Packages/FPC/CryptoLib4PascalPackage.lpk | 6 +- .../Packages/FPC/CryptoLib4PascalPackage.pas | 2 +- 14 files changed, 1302 insertions(+), 23 deletions(-) create mode 100644 CryptoLib.Tests/src/Crypto/AesBitSlicedTests.pas create mode 100644 CryptoLib/src/Crypto/Engines/ClpAesBitSlicedEngine.pas diff --git a/CryptoLib.Benchmark/Delphi/CryptoLib.BenchmarkConsole.dpr b/CryptoLib.Benchmark/Delphi/CryptoLib.BenchmarkConsole.dpr index 5b1baf93..c36af03c 100644 --- a/CryptoLib.Benchmark/Delphi/CryptoLib.BenchmarkConsole.dpr +++ b/CryptoLib.Benchmark/Delphi/CryptoLib.BenchmarkConsole.dpr @@ -13,6 +13,7 @@ uses PerformanceBenchmark in '..\src\Core\PerformanceBenchmark.pas', SignerPerformanceBenchmark in '..\src\Core\SignerPerformanceBenchmark.pas', ClpAesEngine in '..\..\CryptoLib\src\Crypto\Engines\ClpAesEngine.pas', + ClpAesBitSlicedEngine in '..\..\CryptoLib\src\Crypto\Engines\ClpAesBitSlicedEngine.pas', ClpIAesHardwareEngine in '..\..\CryptoLib\src\Interfaces\Crypto\Engines\ClpIAesHardwareEngine.pas', ClpIAesEngineX86 in '..\..\CryptoLib\src\Interfaces\Crypto\Engines\ClpIAesEngineX86.pas', ClpAesEngineX86 in '..\..\CryptoLib\src\Crypto\Engines\ClpAesEngineX86.pas', diff --git a/CryptoLib.Examples/Delphi.Examples/CryptoLib.Examples.dpr b/CryptoLib.Examples/Delphi.Examples/CryptoLib.Examples.dpr index 820f46f7..ce023fd7 100644 --- a/CryptoLib.Examples/Delphi.Examples/CryptoLib.Examples.dpr +++ b/CryptoLib.Examples/Delphi.Examples/CryptoLib.Examples.dpr @@ -32,6 +32,7 @@ uses SecureRandomExample in '..\src\Examples\SecureRandomExample.pas', HybridEncryption in '..\src\Utilities\HybridEncryption.pas', ClpAesEngine in '..\..\CryptoLib\src\Crypto\Engines\ClpAesEngine.pas', + ClpAesBitSlicedEngine in '..\..\CryptoLib\src\Crypto\Engines\ClpAesBitSlicedEngine.pas', ClpIAesHardwareEngine in '..\..\CryptoLib\src\Interfaces\Crypto\Engines\ClpIAesHardwareEngine.pas', ClpIAesEngineX86 in '..\..\CryptoLib\src\Interfaces\Crypto\Engines\ClpIAesEngineX86.pas', ClpAesEngineX86 in '..\..\CryptoLib\src\Crypto\Engines\ClpAesEngineX86.pas', diff --git a/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.Mobile.dpr b/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.Mobile.dpr index a398067a..1118f259 100644 --- a/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.Mobile.dpr +++ b/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.Mobile.dpr @@ -9,6 +9,7 @@ uses MobileTestHostFormUnit in 'Mobile\MobileTestHostFormUnit.pas' {MobileTestHostForm}, MobileTestRunner in 'Mobile\MobileTestRunner.pas', ClpAesEngine in '..\..\CryptoLib\src\Crypto\Engines\ClpAesEngine.pas', + ClpAesBitSlicedEngine in '..\..\CryptoLib\src\Crypto\Engines\ClpAesBitSlicedEngine.pas', ClpIAesHardwareEngine in '..\..\CryptoLib\src\Interfaces\Crypto\Engines\ClpIAesHardwareEngine.pas', ClpIAesEngineX86 in '..\..\CryptoLib\src\Interfaces\Crypto\Engines\ClpIAesEngineX86.pas', ClpAesEngineX86 in '..\..\CryptoLib\src\Crypto\Engines\ClpAesEngineX86.pas', @@ -927,6 +928,7 @@ uses CtrDrbgTests in '..\src\Rngs\Drbg\CtrDrbgTests.pas', //FixedPointTests in '..\src\Math\EC\FixedPointTests.pas', AESTests in '..\src\Crypto\AESTests.pas', + AesBitSlicedTests in '..\src\Crypto\AesBitSlicedTests.pas', BlockCipherTestBase in '..\src\Crypto\BlockCipherTestBase.pas', AesBlockCipherTestBase in '..\src\Crypto\AesBlockCipherTestBase.pas', AesLightTests in '..\src\Crypto\AesLightTests.pas', diff --git a/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.Mobile.dproj b/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.Mobile.dproj index 1d990553..660cb15e 100644 --- a/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.Mobile.dproj +++ b/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.Mobile.dproj @@ -352,6 +352,7 @@ + @@ -1266,6 +1267,7 @@ + diff --git a/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.dpr b/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.dpr index 1ca7f398..7d0f5f1f 100644 --- a/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.dpr +++ b/CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.dpr @@ -28,6 +28,7 @@ uses TextTestRunner, {$ENDIF } ClpAesEngine in '..\..\CryptoLib\src\Crypto\Engines\ClpAesEngine.pas', + ClpAesBitSlicedEngine in '..\..\CryptoLib\src\Crypto\Engines\ClpAesBitSlicedEngine.pas', ClpIAesHardwareEngine in '..\..\CryptoLib\src\Interfaces\Crypto\Engines\ClpIAesHardwareEngine.pas', ClpIAesEngineX86 in '..\..\CryptoLib\src\Interfaces\Crypto\Engines\ClpIAesEngineX86.pas', ClpAesEngineX86 in '..\..\CryptoLib\src\Crypto\Engines\ClpAesEngineX86.pas', @@ -948,6 +949,7 @@ uses DigestRandomNumberTests in '..\src\Crypto\DigestRandomNumberTests.pas', FixedPointTests in '..\src\Math\EC\FixedPointTests.pas', AESTests in '..\src\Crypto\AESTests.pas', + AesBitSlicedTests in '..\src\Crypto\AesBitSlicedTests.pas', BlockCipherTestBase in '..\src\Crypto\BlockCipherTestBase.pas', AesBlockCipherTestBase in '..\src\Crypto\AesBlockCipherTestBase.pas', AesLightTests in '..\src\Crypto\AesLightTests.pas', diff --git a/CryptoLib.Tests/FreePascal.Tests/CryptoLib.Tests.lpi b/CryptoLib.Tests/FreePascal.Tests/CryptoLib.Tests.lpi index d6bd0c74..f5889648 100644 --- a/CryptoLib.Tests/FreePascal.Tests/CryptoLib.Tests.lpi +++ b/CryptoLib.Tests/FreePascal.Tests/CryptoLib.Tests.lpi @@ -79,7 +79,7 @@ - + @@ -897,6 +897,10 @@ + + + + diff --git a/CryptoLib.Tests/FreePascal.Tests/CryptoLib.lpr b/CryptoLib.Tests/FreePascal.Tests/CryptoLib.lpr index 1e9135e4..94960e3e 100644 --- a/CryptoLib.Tests/FreePascal.Tests/CryptoLib.lpr +++ b/CryptoLib.Tests/FreePascal.Tests/CryptoLib.lpr @@ -40,9 +40,9 @@ AeadTestUtilities, GcmReorderTests, GCMTests, GcmSivTests, GMacTests, Pkcs12Tests, Bip327MuSig2Tests, Bip340SchnorrTests, AlgorithmFinderTests, MlKemTests, MlDsaTests, PqcPkcsTests, SlhDsaTests, Lib25519Tests, - Asn1CipherBuilderWithKeyTests, CryptoLibTestBase, PkixFoundationTests, - PkixNameConstraintTests, PkixComparerTests, CertPathTests, - CertPathValidatorTests, PkixPolicyMappingTests, OcspTests, + Asn1CipherBuilderWithKeyTests, AesBitSlicedTests, CryptoLibTestBase, + PkixFoundationTests, PkixNameConstraintTests, PkixComparerTests, + CertPathTests, CertPathValidatorTests, PkixPolicyMappingTests, OcspTests, CryptoLibHashSetTests, PkitsTestBase, NistCertPathTests, AttrCertPathTests, CtrDrbgTests, DrbgTestSupport, HashDrbgTests, HMacDrbgTests, SimdSelectSlotTests, BinaryPrimitivesTests, PkcsEncryptedPrivateKeyInfoTests, diff --git a/CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpi b/CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpi index 8505f06e..a7d39284 100644 --- a/CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpi +++ b/CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpi @@ -39,7 +39,7 @@ - + @@ -856,6 +856,10 @@ + + + + diff --git a/CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpr b/CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpr index a2195c30..ff73c72d 100644 --- a/CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpr +++ b/CryptoLib.Tests/FreePascal.Tests/CryptoLibConsole.lpr @@ -41,22 +41,23 @@ AeadTestUtilities, GcmReorderTests, GCMTests, GcmSivTests, GMacTests, Pkcs12Tests, Bip327MuSig2Tests, Bip340SchnorrTests, AlgorithmFinderTests, MlKemTests, MlDsaTests, SlhDsaTests, PqcTestSampler, PqcPkcsTests, - Lib25519Tests, Asn1CipherBuilderWithKeyTests, CryptoLibTestBase, - PkixFoundationTests, PkixNameConstraintTests, PkixComparerTests, - CertPathTests, CertPathValidatorTests, PkixPolicyMappingTests, OcspTests, - CryptoLibHashSetTests, PkitsTestBase, NistCertPathTests, AttrCertPathTests, - CtrDrbgTests, DrbgTestSupport, HashDrbgTests, HMacDrbgTests, - SimdSelectSlotTests, BinaryPrimitivesTests, PkcsEncryptedPrivateKeyInfoTests, - Pkcs12StoreTests, OpenSslReaderTests, OpenSslWriterTests, X509CertGenTests, - X509CertificatePairTests, X509UtilitiesTests, FixedSecureRandom, - ShortenedDigest, CertTestUtilities, CryptoTestKeys, CipherKernelToggle, - CryptoLibTestResourceLoader, NistSecureRandom, CsvVectorParser, - JsonVectorParser, RspTxtVectorParser, RspTxtVectorParserTests, Bip327Vectors, - Bip340Vectors, HmacVectors, AsymmetricTestVectors, SymmetricBlockVectors, - ChaChaPoly1305Vectors, OpenSslVectors, PkcsVectors, CertVectors, PkitsVectors, - PqcSampleCredentials, CsvVectorLoaderBase, TestKeyBuilders, PemDerCodec, - PemReaderVectors, Argon2Vectors, PqcTestVectors, DrbgTestVectors, Int32Tests, - Int64Tests, ByteUtilitiesTests, IPAddressUtilitiesTests, PemReaderTests; + Lib25519Tests, Asn1CipherBuilderWithKeyTests, AesBitSlicedTests, + CryptoLibTestBase, PkixFoundationTests, PkixNameConstraintTests, + PkixComparerTests, CertPathTests, CertPathValidatorTests, + PkixPolicyMappingTests, OcspTests, CryptoLibHashSetTests, PkitsTestBase, + NistCertPathTests, AttrCertPathTests, CtrDrbgTests, DrbgTestSupport, + HashDrbgTests, HMacDrbgTests, SimdSelectSlotTests, BinaryPrimitivesTests, + PkcsEncryptedPrivateKeyInfoTests, Pkcs12StoreTests, OpenSslReaderTests, + OpenSslWriterTests, X509CertGenTests, X509CertificatePairTests, + X509UtilitiesTests, FixedSecureRandom, ShortenedDigest, CertTestUtilities, + CryptoTestKeys, CipherKernelToggle, CryptoLibTestResourceLoader, + NistSecureRandom, CsvVectorParser, JsonVectorParser, RspTxtVectorParser, + RspTxtVectorParserTests, Bip327Vectors, Bip340Vectors, HmacVectors, + AsymmetricTestVectors, SymmetricBlockVectors, ChaChaPoly1305Vectors, + OpenSslVectors, PkcsVectors, CertVectors, PkitsVectors, PqcSampleCredentials, + CsvVectorLoaderBase, TestKeyBuilders, PemDerCodec, PemReaderVectors, + Argon2Vectors, PqcTestVectors, DrbgTestVectors, Int32Tests, Int64Tests, + ByteUtilitiesTests, IPAddressUtilitiesTests, PemReaderTests; type diff --git a/CryptoLib.Tests/src/Crypto/AesBitSlicedTests.pas b/CryptoLib.Tests/src/Crypto/AesBitSlicedTests.pas new file mode 100644 index 00000000..ac44671a --- /dev/null +++ b/CryptoLib.Tests/src/Crypto/AesBitSlicedTests.pas @@ -0,0 +1,455 @@ +{ *********************************************************************************** } +{ * CryptoLib Library * } +{ * Author - Ugochukwu Mmaduekwe * } +{ * Github Repository * } +{ * * } +{ * Distributed under the MIT software license, see the accompanying file LICENSE * } +{ * or visit http://www.opensource.org/licenses/mit-license.php. * } +{ * * } +{ * Acknowledgements: * } +{ * * } +{ * Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring * } +{ * the development of this library * } +{ * ******************************************************************************* * } + +(* &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& *) + +unit AesBitSlicedTests; + +{ Correctness tests for TAesBitSlicedEngine, the constant-time table-free AES. + These MUST run with CRYPTOLIB_FORCE_SCALAR (CryptoLib.inc) so the AES-GCM leg + exercises the software ImplMul64 GHASH path rather than hardware PCLMUL/PMULL. } + +interface + +{$IFDEF FPC} +{$MODE DELPHI} +{$ENDIF FPC} + +uses + SysUtils, +{$IFDEF FPC} + fpcunit, + testregistry, +{$ELSE} + TestFramework, +{$ENDIF FPC} + ClpAesEngine, + ClpAesBitSlicedEngine, + ClpIBlockCipher, + ClpIAeadCipher, + ClpKeyParameter, + ClpIKeyParameter, + ClpParametersWithIV, + ClpAeadParameters, + ClpGcmBlockCipher, + ClpBasicGcmMultiplier, + ClpIGcmMultiplier, + ClpCbcBlockCipher, + ClpBufferedBlockCipher, + ClpIBufferedCipher, + ClpICipherParameters, + ClpCryptoLibTypes, + ClpCryptoLibExceptions, + CryptoLibTestBase, + BlockCipherTestBase, + AesBlockCipherTestBase; + +type + + TTestAesBitSliced = class(TAesBlockCipherTestBase) + strict protected + function GetEngineFactory: TBlockCipherFactory; override; + function EngineLabel: String; override; + strict private + // FIPS-197 Appendix C known-answer vectors (key / plaintext / ciphertext). + procedure DoKatTest(const AKeyHex, APlainHex, ACipherHex: String); + // Byte-for-byte parity of TAesBitSlicedEngine against TAesEngine over many + // random keys and blocks, in both directions, for one key length. + procedure DoParityTest(AKeyLen, AIterations: Int32); + // End-to-end AES-GCM through the manually-composed constant-time stack. + procedure DoGcmTest(const AKeyHex, ANonceHex, AAadHex, APlainHex, + ACipherHex, ATagHex: String); + function NewBitSlicedGcm(): IAeadCipher; + published + procedure TestFips197Kat; + procedure TestParityWithTableEngine; + procedure TestRoundTrip; + procedure TestCbcRoundTripThroughMode; + procedure TestAesGcmEndToEnd; + procedure TestInvalidKeyLength; + procedure TestResetAndReInit; + end; + +implementation + +const + // FIPS-197 Appendix C vectors, one PT/CT pair per key size. + KAT_KEY_128 = '000102030405060708090a0b0c0d0e0f'; + KAT_KEY_192 = '000102030405060708090a0b0c0d0e0f1011121314151617'; + KAT_KEY_256 = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'; + KAT_PLAIN = '00112233445566778899aabbccddeeff'; + KAT_CIPHER_128 = '69c4e0d86a7b0430d8cdb78070b4c55a'; + KAT_CIPHER_192 = 'dda97ca4864cdfe06eaf70a0ec0d7191'; + KAT_CIPHER_256 = '8ea2b7ca516745bfeafc49904b496089'; + +function CreateBitSlicedEngine: IBlockCipher; +begin + Result := TAesBitSlicedEngine.Create(); +end; + +{ TTestAesBitSliced } + +function TTestAesBitSliced.GetEngineFactory: TBlockCipherFactory; +begin + Result := @CreateBitSlicedEngine; +end; + +function TTestAesBitSliced.EngineLabel: String; +begin + Result := 'TAesBitSlicedEngine'; +end; + +function TTestAesBitSliced.NewBitSlicedGcm(): IAeadCipher; +begin + // The intended constant-time-software AES-GCM recipe: bit-sliced AES engine + // composed with the branchless software GHASH multiplier. + Result := TGcmBlockCipher.Create( + TAesBitSlicedEngine.Create() as IBlockCipher, + TBasicGcmMultiplier.Create() as IGcmMultiplier) as IAeadCipher; +end; + +procedure TTestAesBitSliced.DoKatTest(const AKeyHex, APlainHex, + ACipherHex: String); +var + LKey, LPlain, LCipher, LEnc, LDec: TBytes; + LEngine: IBlockCipher; + LKeyParam: IKeyParameter; +begin + LKey := DecodeHex(AKeyHex); + LPlain := DecodeHex(APlainHex); + LCipher := DecodeHex(ACipherHex); + LKeyParam := TKeyParameter.Create(LKey); + + System.SetLength(LEnc, 16); + LEngine := TAesBitSlicedEngine.Create(); + LEngine.Init(True, LKeyParam as ICipherParameters); + LEngine.ProcessBlock(LPlain, 0, LEnc, 0); + if not AreEqual(LEnc, LCipher) then + Fail(Format('FIPS-197 KAT encrypt failed for %d-bit key: expected %s got %s', + [System.Length(LKey) * 8, EncodeHex(LCipher), EncodeHex(LEnc)])); + + System.SetLength(LDec, 16); + LEngine := TAesBitSlicedEngine.Create(); + LEngine.Init(False, LKeyParam as ICipherParameters); + LEngine.ProcessBlock(LCipher, 0, LDec, 0); + if not AreEqual(LDec, LPlain) then + Fail(Format('FIPS-197 KAT decrypt failed for %d-bit key: expected %s got %s', + [System.Length(LKey) * 8, EncodeHex(LPlain), EncodeHex(LDec)])); +end; + +procedure TTestAesBitSliced.DoParityTest(AKeyLen, AIterations: Int32); +var + LI, LJ: Int32; + LKey, LBlock, LRef, LBs: TBytes; + LKeyParam: IKeyParameter; + LTable, LSliced: IBlockCipher; + LForEnc: Boolean; + LDir: Int32; +begin + System.SetLength(LKey, AKeyLen); + System.SetLength(LBlock, 16); + System.SetLength(LRef, 16); + System.SetLength(LBs, 16); + + for LDir := 0 to 1 do + begin + LForEnc := LDir = 0; + for LI := 0 to AIterations - 1 do + begin + for LJ := 0 to AKeyLen - 1 do + LKey[LJ] := Byte(Random(256)); + for LJ := 0 to 15 do + LBlock[LJ] := Byte(Random(256)); + + LKeyParam := TKeyParameter.Create(LKey); + + LTable := TAesEngine.Create(); + LTable.Init(LForEnc, LKeyParam as ICipherParameters); + LTable.ProcessBlock(LBlock, 0, LRef, 0); + + LSliced := TAesBitSlicedEngine.Create(); + LSliced.Init(LForEnc, LKeyParam as ICipherParameters); + LSliced.ProcessBlock(LBlock, 0, LBs, 0); + + if not AreEqual(LRef, LBs) then + Fail(Format('Parity failed (%d-bit key, %s, iter %d): table %s vs sliced %s', + [AKeyLen * 8, SysUtils.BoolToStr(LForEnc, True), LI, EncodeHex(LRef), + EncodeHex(LBs)])); + end; + end; +end; + +procedure TTestAesBitSliced.DoGcmTest(const AKeyHex, ANonceHex, AAadHex, + APlainHex, ACipherHex, ATagHex: String); +var + LKey, LNonce, LAad, LPlain, LCipher, LTag, LExpected: TBytes; + LKeyParam: IKeyParameter; + LParams: ICipherParameters; + LGcm: IAeadCipher; + LOut, LDec: TBytes; + LLen: Int32; +begin + LKey := DecodeHex(AKeyHex); + LNonce := DecodeHex(ANonceHex); + if AAadHex <> '' then + LAad := DecodeHex(AAadHex) + else + LAad := nil; + LPlain := DecodeHex(APlainHex); + LCipher := DecodeHex(ACipherHex); + LTag := DecodeHex(ATagHex); + LExpected := System.Copy(LCipher); + LExpected := LExpected + LTag; + + LKeyParam := TKeyParameter.Create(LKey); + LParams := TAeadParameters.Create(LKeyParam, System.Length(LTag) * 8, LNonce, + LAad) as ICipherParameters; + + // Encrypt: output must equal ciphertext || tag. + LGcm := NewBitSlicedGcm(); + LGcm.Init(True, LParams); + System.SetLength(LOut, LGcm.GetOutputSize(System.Length(LPlain))); + LLen := LGcm.ProcessBytes(LPlain, 0, System.Length(LPlain), LOut, 0); + LLen := LLen + LGcm.DoFinal(LOut, LLen); + System.SetLength(LOut, LLen); + if not AreEqual(LOut, LExpected) then + Fail(Format('AES-GCM encrypt mismatch: expected %s got %s', + [EncodeHex(LExpected), EncodeHex(LOut)])); + + // Decrypt: recover plaintext and verify the tag (DoFinal raises on bad tag). + LGcm := NewBitSlicedGcm(); + LGcm.Init(False, LParams); + System.SetLength(LDec, LGcm.GetOutputSize(System.Length(LOut))); + LLen := LGcm.ProcessBytes(LOut, 0, System.Length(LOut), LDec, 0); + LLen := LLen + LGcm.DoFinal(LDec, LLen); + System.SetLength(LDec, LLen); + if not AreEqual(LDec, LPlain) then + Fail(Format('AES-GCM decrypt mismatch: expected %s got %s', + [EncodeHex(LPlain), EncodeHex(LDec)])); +end; + +procedure TTestAesBitSliced.TestFips197Kat; +begin + DoKatTest(KAT_KEY_128, KAT_PLAIN, KAT_CIPHER_128); + DoKatTest(KAT_KEY_192, KAT_PLAIN, KAT_CIPHER_192); + DoKatTest(KAT_KEY_256, KAT_PLAIN, KAT_CIPHER_256); +end; + +procedure TTestAesBitSliced.TestParityWithTableEngine; +begin + // Deterministic corpus: fixed seed so a failure reproduces. + RandSeed := 20260722; + DoParityTest(16, 256); + DoParityTest(24, 256); + DoParityTest(32, 256); +end; + +procedure TTestAesBitSliced.TestRoundTrip; +var + LKeyLens: array [0 .. 2] of Int32; + LK, LJ: Int32; + LKey, LBlock, LEnc, LDec, LReEnc: TBytes; + LKeyParam: IKeyParameter; + LEng: IBlockCipher; +begin + RandSeed := 987654321; + LKeyLens[0] := 16; + LKeyLens[1] := 24; + LKeyLens[2] := 32; + System.SetLength(LBlock, 16); + System.SetLength(LEnc, 16); + System.SetLength(LDec, 16); + System.SetLength(LReEnc, 16); + + for LK := 0 to High(LKeyLens) do + begin + System.SetLength(LKey, LKeyLens[LK]); + for LJ := 0 to LKeyLens[LK] - 1 do + LKey[LJ] := Byte(Random(256)); + for LJ := 0 to 15 do + LBlock[LJ] := Byte(Random(256)); + LKeyParam := TKeyParameter.Create(LKey); + + // encrypt-then-decrypt returns the original block + LEng := TAesBitSlicedEngine.Create(); + LEng.Init(True, LKeyParam as ICipherParameters); + LEng.ProcessBlock(LBlock, 0, LEnc, 0); + LEng := TAesBitSlicedEngine.Create(); + LEng.Init(False, LKeyParam as ICipherParameters); + LEng.ProcessBlock(LEnc, 0, LDec, 0); + if not AreEqual(LDec, LBlock) then + Fail(Format('Round-trip enc->dec failed for %d-bit key', [LKeyLens[LK] * 8])); + + // decrypt-then-encrypt of an arbitrary block is also an identity + LEng := TAesBitSlicedEngine.Create(); + LEng.Init(False, LKeyParam as ICipherParameters); + LEng.ProcessBlock(LBlock, 0, LDec, 0); + LEng := TAesBitSlicedEngine.Create(); + LEng.Init(True, LKeyParam as ICipherParameters); + LEng.ProcessBlock(LDec, 0, LReEnc, 0); + if not AreEqual(LReEnc, LBlock) then + Fail(Format('Round-trip dec->enc failed for %d-bit key', [LKeyLens[LK] * 8])); + end; +end; + +procedure TTestAesBitSliced.TestCbcRoundTripThroughMode; +var + LKey, LIv, LInput, LEnc, LDec: TBytes; + LKeyParam: IKeyParameter; + LParams: ICipherParameters; + LEncCipher, LDecCipher: IBufferedCipher; +begin + // Exercise the decrypt path through a real chaining mode. + LKey := DecodeHex(KAT_KEY_256); + System.SetLength(LIv, 16); + FillChar(LIv[0], 16, 0); + LIv[0] := $A5; + LInput := DecodeHex( + '6bc1bee22e409f96e93d7e117393172a' + + 'ae2d8a571e03ac9c9eb76fac45af8e51' + + '30c81c46a35ce411e5fbc1191a0a52ef' + + 'f69f2445df4f9b17ad2b417be66c3710'); + + LKeyParam := TKeyParameter.Create(LKey); + LParams := TParametersWithIV.Create(LKeyParam, LIv) as ICipherParameters; + + LEncCipher := TBufferedBlockCipher.Create( + TCbcBlockCipher.Create(TAesBitSlicedEngine.Create()) as IBlockCipher); + LEncCipher.Init(True, LParams); + LEnc := LEncCipher.DoFinal(LInput); + + LDecCipher := TBufferedBlockCipher.Create( + TCbcBlockCipher.Create(TAesBitSlicedEngine.Create()) as IBlockCipher); + LDecCipher.Init(False, LParams); + LDec := LDecCipher.DoFinal(LEnc); + + if not AreEqual(LDec, LInput) then + Fail(Format('AES/CBC round-trip through bit-sliced engine failed: got %s', + [EncodeHex(LDec)])); +end; + +procedure TTestAesBitSliced.TestAesGcmEndToEnd; +begin + // McGrew-Viega AES-GCM test vectors, exercised through the constant-time + // software stack (bit-sliced AES + branchless GHASH). + + // Case 3: AES-128, no AAD, 64-byte plaintext. + DoGcmTest( + 'feffe9928665731c6d6a8f9467308308', + 'cafebabefacedbaddecaf888', + '', + 'd9313225f88406e5a55909c5aff5269a' + + '86a7a9531534f7da2e4c303d8a318a72' + + '1c3c0c95956809532fcf0e2449a6b525' + + 'b16aedf5aa0de657ba637b391aafd255', + '42831ec2217774244b7221b784d0d49c' + + 'e3aa212f2c02a4e035c17e2329aca12e' + + '21d514b25466931c7d8f6a5aac84aa05' + + '1ba30b396a0aac973d58e091473f5985', + '4d5c2af327cd64a62cf35abd2ba6fab4'); + + // Case 4: AES-128, with AAD, 60-byte plaintext. + DoGcmTest( + 'feffe9928665731c6d6a8f9467308308', + 'cafebabefacedbaddecaf888', + 'feedfacedeadbeeffeedfacedeadbeefabaddad2', + 'd9313225f88406e5a55909c5aff5269a' + + '86a7a9531534f7da2e4c303d8a318a72' + + '1c3c0c95956809532fcf0e2449a6b525' + + 'b16aedf5aa0de657ba637b39', + '42831ec2217774244b7221b784d0d49c' + + 'e3aa212f2c02a4e035c17e2329aca12e' + + '21d514b25466931c7d8f6a5aac84aa05' + + '1ba30b396a0aac973d58e091', + '5bc94fbc3221a5db94fae95ae7121a47'); + + // Case 16: AES-256, with AAD, 60-byte plaintext. + DoGcmTest( + 'feffe9928665731c6d6a8f9467308308' + + 'feffe9928665731c6d6a8f9467308308', + 'cafebabefacedbaddecaf888', + 'feedfacedeadbeeffeedfacedeadbeefabaddad2', + 'd9313225f88406e5a55909c5aff5269a' + + '86a7a9531534f7da2e4c303d8a318a72' + + '1c3c0c95956809532fcf0e2449a6b525' + + 'b16aedf5aa0de657ba637b39', + '522dc1f099567d07f47f37a32a84427d' + + '643a8cdcbfe5c0c97598a2bd2555d1aa' + + '8cb08e48590dbb3da7b08b1056828838' + + 'c5f61e6393ba7a0abcc9f662', + '76fc6ece0f4e1768cddf8853bb2d551b'); +end; + +procedure TTestAesBitSliced.TestInvalidKeyLength; +var + LEngine: IBlockCipher; + LBadKey: TBytes; + LRaised: Boolean; +begin + System.SetLength(LBadKey, 15); // not 128/192/256 bits + LEngine := TAesBitSlicedEngine.Create(); + LRaised := False; + try + LEngine.Init(True, TKeyParameter.Create(LBadKey) as ICipherParameters); + except + on E: EArgumentCryptoLibException do + LRaised := True; + end; + if not LRaised then + Fail('Expected EArgumentCryptoLibException for 15-byte key'); +end; + +procedure TTestAesBitSliced.TestResetAndReInit; +var + LKey, LBlock, LFirst, LSecond, LThird: TBytes; + LKeyParam: IKeyParameter; + LObj: TAesBitSlicedEngine; + LEngine: IBlockCipher; +begin + LKey := DecodeHex(KAT_KEY_128); + LBlock := DecodeHex(KAT_PLAIN); + LKeyParam := TKeyParameter.Create(LKey); + System.SetLength(LFirst, 16); + System.SetLength(LSecond, 16); + System.SetLength(LThird, 16); + + // Hold the concrete object so Reset (not part of IBlockCipher) is callable; + // the interface reference keeps it alive. + LObj := TAesBitSlicedEngine.Create(); + LEngine := LObj; + LEngine.Init(True, LKeyParam as ICipherParameters); + LEngine.ProcessBlock(LBlock, 0, LFirst, 0); + + // Reset must leave the keyed state intact. + LObj.Reset(); + LEngine.ProcessBlock(LBlock, 0, LSecond, 0); + if not AreEqual(LFirst, LSecond) then + Fail('ProcessBlock after Reset produced a different result'); + + // Re-Init with the same key must also reproduce the result. + LEngine.Init(True, LKeyParam as ICipherParameters); + LEngine.ProcessBlock(LBlock, 0, LThird, 0); + if not AreEqual(LFirst, LThird) then + Fail('ProcessBlock after re-Init produced a different result'); +end; + +initialization + +{$IFDEF FPC} + RegisterTest(TTestAesBitSliced); +{$ELSE} + RegisterTest(TTestAesBitSliced.Suite); +{$ENDIF FPC} + +end. diff --git a/CryptoLib/src/Crypto/Engines/ClpAesBitSlicedEngine.pas b/CryptoLib/src/Crypto/Engines/ClpAesBitSlicedEngine.pas new file mode 100644 index 00000000..19515ea1 --- /dev/null +++ b/CryptoLib/src/Crypto/Engines/ClpAesBitSlicedEngine.pas @@ -0,0 +1,802 @@ +{ *********************************************************************************** } +{ * CryptoLib Library * } +{ * Author - Ugochukwu Mmaduekwe * } +{ * Github Repository * } +{ * * } +{ * Distributed under the MIT software license, see the accompanying file LICENSE * } +{ * or visit http://www.opensource.org/licenses/mit-license.php. * } +{ * * } +{ * Acknowledgements: * } +{ * * } +{ * Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring * } +{ * the development of this library * } +{ * ******************************************************************************* * } + +(* &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& *) + +unit ClpAesBitSlicedEngine; + +{$I ..\..\Include\CryptoLib.inc} + +interface + +uses + SysUtils, + ClpIAesEngine, + ClpIBlockCipher, + ClpICipherParameters, + ClpIKeyParameter, + ClpCheck, + ClpPack, + ClpBitOperations, + ClpArrayUtilities, + ClpPlatformUtilities, + ClpCryptoLibTypes, + ClpCryptoLibExceptions; + +resourcestring + SAesEngineNotInitialized = 'AES engine not initialized'; + SInputBufferTooShort = 'input buffer too short'; + SOutputBufferTooShort = 'output buffer too short'; + SInvalidParameterAESInit = 'invalid parameter passed to AES init: %s'; + SInvalidKeyLength = 'key length not 128/192/256 bits'; + +type + + /// + /// Constant-time, table-free AES (Rijndael) per FIPS-197. + /// + /// + /// + /// A software AES engine that performs no data- or key-dependent memory + /// lookups and takes no secret-dependent branch: the S-box is realised as a + /// Boolean circuit (Boyar–Peralta) over a bit-sliced state held in eight + /// UInt64 words, and the linear layers are word rotations/XORs. It is + /// therefore immune to cache-timing side channels that affect the table-based + /// TAesEngine. + /// + /// + /// It is a full / peer to + /// TAesEngine (both encrypt and decrypt, 128/192/256-bit keys) and is + /// bit-exact with it. It is materially slower per byte than the table engine. + /// + /// + /// Intended use is constant-time software AES on hosts without hardware AES. + /// + /// + TAesBitSlicedEngine = class sealed(TInterfacedObject, IAesEngine, IBlockCipher) + + strict private + const + BLOCK_SIZE = Int32(16); + + type + // Bit-sliced AES state / round key: eight bit-planes, one per UInt64 word. + TBitSliceState = array [0 .. 7] of UInt64; + + const + // Round constants (powers of x in GF(2^8)); ten entries cover AES-128/192/256. + RCon: array [0 .. 9] of Byte = ($01, $02, $04, $08, $10, $20, $40, $80, + $1B, $36); + + var + FRounds: Int32; + // Expanded (uncompressed) round keys in bit-sliced form: (FRounds + 1) * 8 words. + FSkey: TCryptoLibUInt64Array; + FForEncryption: Boolean; + + class procedure Swapn(ACl, ACh: UInt64; AShift: Int32; + var AX, AY: UInt64); static; inline; + class procedure Ortho(var AQ: TBitSliceState); static; + class procedure InterleaveIn(const AW0, AW1, AW2, AW3: UInt32; + out AQ0, AQ1: UInt64); static; + class procedure InterleaveOut(out AW0, AW1, AW2, AW3: UInt32; + AQ0, AQ1: UInt64); static; + class procedure BitsliceSbox(var AQ: TBitSliceState); static; + class procedure BitsliceInvSbox(var AQ: TBitSliceState); static; + class procedure ShiftRows(var AQ: TBitSliceState); static; + class procedure InvShiftRows(var AQ: TBitSliceState); static; + class procedure MixColumns(var AQ: TBitSliceState); static; + class procedure InvMixColumns(var AQ: TBitSliceState); static; + class function Rotr32(AX: UInt64): UInt64; static; inline; + class function SubWord(AX: UInt32): UInt32; static; + class procedure AddRoundKey(var AQ: TBitSliceState; + const ASkey: TCryptoLibUInt64Array; ASkOff: Int32); static; inline; + + procedure KeySchedule(const AKey: TCryptoLibByteArray); + procedure BitsliceEncrypt(var AQ: TBitSliceState); + procedure BitsliceDecrypt(var AQ: TBitSliceState); + + strict protected + function GetAlgorithmName: String; virtual; + + public + + /// Initialise the bit-sliced AES cipher. + /// Whether to initialise for encryption. + /// The key parameters required to set up the cipher. + /// If the parameters argument is inappropriate. + procedure Init(AForEncryption: Boolean; + const AParameters: ICipherParameters); virtual; + + /// Process one 16-byte block. + /// Always 16 (AES block size in bytes). + /// If Init has not been called. + /// If the input or output buffer range is too short. + function ProcessBlock(const AInput: TCryptoLibByteArray; AInOff: Int32; + const AOutput: TCryptoLibByteArray; AOutOff: Int32): Int32; virtual; + + /// Return the AES block size. + /// 16 (fixed AES block size in bytes). + function GetBlockSize(): Int32; virtual; + + /// Reset the cipher back to its post-Init state. + procedure Reset(); virtual; + + /// The cipher name (AES[BitSliced]). + property AlgorithmName: String read GetAlgorithmName; + end; + +implementation + +{ TAesBitSlicedEngine } + +class procedure TAesBitSlicedEngine.Swapn(ACl, ACh: UInt64; AShift: Int32; + var AX, AY: UInt64); +var + LA, LB: UInt64; +begin + LA := AX; + LB := AY; + AX := (LA and ACl) or ((LB and ACl) shl AShift); + AY := ((LA and ACh) shr AShift) or (LB and ACh); +end; + +class procedure TAesBitSlicedEngine.Ortho(var AQ: TBitSliceState); +const + CL2 = UInt64($5555555555555555); + CH2 = UInt64($AAAAAAAAAAAAAAAA); + CL4 = UInt64($3333333333333333); + CH4 = UInt64($CCCCCCCCCCCCCCCC); + CL8 = UInt64($0F0F0F0F0F0F0F0F); + CH8 = UInt64($F0F0F0F0F0F0F0F0); +begin + Swapn(CL2, CH2, 1, AQ[0], AQ[1]); + Swapn(CL2, CH2, 1, AQ[2], AQ[3]); + Swapn(CL2, CH2, 1, AQ[4], AQ[5]); + Swapn(CL2, CH2, 1, AQ[6], AQ[7]); + + Swapn(CL4, CH4, 2, AQ[0], AQ[2]); + Swapn(CL4, CH4, 2, AQ[1], AQ[3]); + Swapn(CL4, CH4, 2, AQ[4], AQ[6]); + Swapn(CL4, CH4, 2, AQ[5], AQ[7]); + + Swapn(CL8, CH8, 4, AQ[0], AQ[4]); + Swapn(CL8, CH8, 4, AQ[1], AQ[5]); + Swapn(CL8, CH8, 4, AQ[2], AQ[6]); + Swapn(CL8, CH8, 4, AQ[3], AQ[7]); +end; + +class procedure TAesBitSlicedEngine.InterleaveIn(const AW0, AW1, AW2, + AW3: UInt32; out AQ0, AQ1: UInt64); +const + M16 = UInt64($0000FFFF0000FFFF); + M8 = UInt64($00FF00FF00FF00FF); +var + LX0, LX1, LX2, LX3: UInt64; +begin + LX0 := AW0; + LX1 := AW1; + LX2 := AW2; + LX3 := AW3; + LX0 := LX0 or (LX0 shl 16); + LX1 := LX1 or (LX1 shl 16); + LX2 := LX2 or (LX2 shl 16); + LX3 := LX3 or (LX3 shl 16); + LX0 := LX0 and M16; + LX1 := LX1 and M16; + LX2 := LX2 and M16; + LX3 := LX3 and M16; + LX0 := LX0 or (LX0 shl 8); + LX1 := LX1 or (LX1 shl 8); + LX2 := LX2 or (LX2 shl 8); + LX3 := LX3 or (LX3 shl 8); + LX0 := LX0 and M8; + LX1 := LX1 and M8; + LX2 := LX2 and M8; + LX3 := LX3 and M8; + AQ0 := LX0 or (LX2 shl 8); + AQ1 := LX1 or (LX3 shl 8); +end; + +class procedure TAesBitSlicedEngine.InterleaveOut(out AW0, AW1, AW2, + AW3: UInt32; AQ0, AQ1: UInt64); +const + M16 = UInt64($0000FFFF0000FFFF); + M8 = UInt64($00FF00FF00FF00FF); +var + LX0, LX1, LX2, LX3: UInt64; +begin + LX0 := AQ0 and M8; + LX1 := AQ1 and M8; + LX2 := (AQ0 shr 8) and M8; + LX3 := (AQ1 shr 8) and M8; + LX0 := LX0 or (LX0 shr 8); + LX1 := LX1 or (LX1 shr 8); + LX2 := LX2 or (LX2 shr 8); + LX3 := LX3 or (LX3 shr 8); + LX0 := LX0 and M16; + LX1 := LX1 and M16; + LX2 := LX2 and M16; + LX3 := LX3 and M16; + AW0 := UInt32(LX0) or UInt32(LX0 shr 16); + AW1 := UInt32(LX1) or UInt32(LX1 shr 16); + AW2 := UInt32(LX2) or UInt32(LX2 shr 16); + AW3 := UInt32(LX3) or UInt32(LX3 shr 16); +end; + +class procedure TAesBitSlicedEngine.BitsliceSbox(var AQ: TBitSliceState); +var + x0, x1, x2, x3, x4, x5, x6, x7: UInt64; + y1, y2, y3, y4, y5, y6, y7, y8, y9: UInt64; + y10, y11, y12, y13, y14, y15, y16, y17, y18, y19: UInt64; + y20, y21: UInt64; + z0, z1, z2, z3, z4, z5, z6, z7, z8, z9: UInt64; + z10, z11, z12, z13, z14, z15, z16, z17: UInt64; + t0, t1, t2, t3, t4, t5, t6, t7, t8, t9: UInt64; + t10, t11, t12, t13, t14, t15, t16, t17, t18, t19: UInt64; + t20, t21, t22, t23, t24, t25, t26, t27, t28, t29: UInt64; + t30, t31, t32, t33, t34, t35, t36, t37, t38, t39: UInt64; + t40, t41, t42, t43, t44, t45, t46, t47, t48, t49: UInt64; + t50, t51, t52, t53, t54, t55, t56, t57, t58, t59: UInt64; + t60, t61, t62, t63, t64, t65, t66, t67: UInt64; + s0, s1, s2, s3, s4, s5, s6, s7: UInt64; +begin + x0 := AQ[7]; + x1 := AQ[6]; + x2 := AQ[5]; + x3 := AQ[4]; + x4 := AQ[3]; + x5 := AQ[2]; + x6 := AQ[1]; + x7 := AQ[0]; + + // Top linear transformation. + y14 := x3 xor x5; + y13 := x0 xor x6; + y9 := x0 xor x3; + y8 := x0 xor x5; + t0 := x1 xor x2; + y1 := t0 xor x7; + y4 := y1 xor x3; + y12 := y13 xor y14; + y2 := y1 xor x0; + y5 := y1 xor x6; + y3 := y5 xor y8; + t1 := x4 xor y12; + y15 := t1 xor x5; + y20 := t1 xor x1; + y6 := y15 xor x7; + y10 := y15 xor t0; + y11 := y20 xor y9; + y7 := x7 xor y11; + y17 := y10 xor y11; + y19 := y10 xor y8; + y16 := t0 xor y11; + y21 := y13 xor y16; + y18 := x0 xor y16; + + // Non-linear section. + t2 := y12 and y15; + t3 := y3 and y6; + t4 := t3 xor t2; + t5 := y4 and x7; + t6 := t5 xor t2; + t7 := y13 and y16; + t8 := y5 and y1; + t9 := t8 xor t7; + t10 := y2 and y7; + t11 := t10 xor t7; + t12 := y9 and y11; + t13 := y14 and y17; + t14 := t13 xor t12; + t15 := y8 and y10; + t16 := t15 xor t12; + t17 := t4 xor t14; + t18 := t6 xor t16; + t19 := t9 xor t14; + t20 := t11 xor t16; + t21 := t17 xor y20; + t22 := t18 xor y19; + t23 := t19 xor y21; + t24 := t20 xor y18; + + t25 := t21 xor t22; + t26 := t21 and t23; + t27 := t24 xor t26; + t28 := t25 and t27; + t29 := t28 xor t22; + t30 := t23 xor t24; + t31 := t22 xor t26; + t32 := t31 and t30; + t33 := t32 xor t24; + t34 := t23 xor t33; + t35 := t27 xor t33; + t36 := t24 and t35; + t37 := t36 xor t34; + t38 := t27 xor t36; + t39 := t29 and t38; + t40 := t25 xor t39; + + t41 := t40 xor t37; + t42 := t29 xor t33; + t43 := t29 xor t40; + t44 := t33 xor t37; + t45 := t42 xor t41; + z0 := t44 and y15; + z1 := t37 and y6; + z2 := t33 and x7; + z3 := t43 and y16; + z4 := t40 and y1; + z5 := t29 and y7; + z6 := t42 and y11; + z7 := t45 and y17; + z8 := t41 and y10; + z9 := t44 and y12; + z10 := t37 and y3; + z11 := t33 and y4; + z12 := t43 and y13; + z13 := t40 and y5; + z14 := t29 and y2; + z15 := t42 and y9; + z16 := t45 and y14; + z17 := t41 and y8; + + // Bottom linear transformation. + t46 := z15 xor z16; + t47 := z10 xor z11; + t48 := z5 xor z13; + t49 := z9 xor z10; + t50 := z2 xor z12; + t51 := z2 xor z5; + t52 := z7 xor z8; + t53 := z0 xor z3; + t54 := z6 xor z7; + t55 := z16 xor z17; + t56 := z12 xor t48; + t57 := t50 xor t53; + t58 := z4 xor t46; + t59 := z3 xor t54; + t60 := t46 xor t57; + t61 := z14 xor t57; + t62 := t52 xor t58; + t63 := t49 xor t58; + t64 := z4 xor t59; + t65 := t61 xor t62; + t66 := z1 xor t63; + s0 := t59 xor t63; + s6 := t56 xor (not t62); + s7 := t48 xor (not t60); + t67 := t64 xor t65; + s3 := t53 xor t66; + s4 := t51 xor t66; + s5 := t47 xor t65; + s1 := t64 xor (not s3); + s2 := t55 xor (not t67); + + AQ[7] := s0; + AQ[6] := s1; + AQ[5] := s2; + AQ[4] := s3; + AQ[3] := s4; + AQ[2] := s5; + AQ[1] := s6; + AQ[0] := s7; +end; + +class procedure TAesBitSlicedEngine.BitsliceInvSbox(var AQ: TBitSliceState); +var + q0, q1, q2, q3, q4, q5, q6, q7: UInt64; +begin + q0 := not AQ[0]; + q1 := not AQ[1]; + q2 := AQ[2]; + q3 := AQ[3]; + q4 := AQ[4]; + q5 := not AQ[5]; + q6 := not AQ[6]; + q7 := AQ[7]; + AQ[7] := q1 xor q4 xor q6; + AQ[6] := q0 xor q3 xor q5; + AQ[5] := q7 xor q2 xor q4; + AQ[4] := q6 xor q1 xor q3; + AQ[3] := q5 xor q0 xor q2; + AQ[2] := q4 xor q7 xor q1; + AQ[1] := q3 xor q6 xor q0; + AQ[0] := q2 xor q5 xor q7; + + BitsliceSbox(AQ); + + q0 := not AQ[0]; + q1 := not AQ[1]; + q2 := AQ[2]; + q3 := AQ[3]; + q4 := AQ[4]; + q5 := not AQ[5]; + q6 := not AQ[6]; + q7 := AQ[7]; + AQ[7] := q1 xor q4 xor q6; + AQ[6] := q0 xor q3 xor q5; + AQ[5] := q7 xor q2 xor q4; + AQ[4] := q6 xor q1 xor q3; + AQ[3] := q5 xor q0 xor q2; + AQ[2] := q4 xor q7 xor q1; + AQ[1] := q3 xor q6 xor q0; + AQ[0] := q2 xor q5 xor q7; +end; + +class procedure TAesBitSlicedEngine.ShiftRows(var AQ: TBitSliceState); +var + LI: Int32; + x: UInt64; +begin + for LI := 0 to 7 do + begin + x := AQ[LI]; + AQ[LI] := (x and UInt64($000000000000FFFF)) + or ((x and UInt64($00000000FFF00000)) shr 4) + or ((x and UInt64($00000000000F0000)) shl 12) + or ((x and UInt64($0000FF0000000000)) shr 8) + or ((x and UInt64($000000FF00000000)) shl 8) + or ((x and UInt64($F000000000000000)) shr 12) + or ((x and UInt64($0FFF000000000000)) shl 4); + end; +end; + +class procedure TAesBitSlicedEngine.InvShiftRows(var AQ: TBitSliceState); +var + LI: Int32; + x: UInt64; +begin + for LI := 0 to 7 do + begin + x := AQ[LI]; + AQ[LI] := (x and UInt64($000000000000FFFF)) + or ((x and UInt64($000000000FFF0000)) shl 4) + or ((x and UInt64($00000000F0000000)) shr 12) + or ((x and UInt64($000000FF00000000)) shl 8) + or ((x and UInt64($0000FF0000000000)) shr 8) + or ((x and UInt64($000F000000000000)) shl 12) + or ((x and UInt64($FFF0000000000000)) shr 4); + end; +end; + +class function TAesBitSlicedEngine.Rotr32(AX: UInt64): UInt64; +begin + Result := TBitOperations.RotateRight64(AX, 32); +end; + +class procedure TAesBitSlicedEngine.MixColumns(var AQ: TBitSliceState); +var + q0, q1, q2, q3, q4, q5, q6, q7: UInt64; + r0, r1, r2, r3, r4, r5, r6, r7: UInt64; +begin + q0 := AQ[0]; + q1 := AQ[1]; + q2 := AQ[2]; + q3 := AQ[3]; + q4 := AQ[4]; + q5 := AQ[5]; + q6 := AQ[6]; + q7 := AQ[7]; + r0 := TBitOperations.RotateRight64(q0, 16); + r1 := TBitOperations.RotateRight64(q1, 16); + r2 := TBitOperations.RotateRight64(q2, 16); + r3 := TBitOperations.RotateRight64(q3, 16); + r4 := TBitOperations.RotateRight64(q4, 16); + r5 := TBitOperations.RotateRight64(q5, 16); + r6 := TBitOperations.RotateRight64(q6, 16); + r7 := TBitOperations.RotateRight64(q7, 16); + + AQ[0] := q7 xor r7 xor r0 xor Rotr32(q0 xor r0); + AQ[1] := q0 xor r0 xor q7 xor r7 xor r1 xor Rotr32(q1 xor r1); + AQ[2] := q1 xor r1 xor r2 xor Rotr32(q2 xor r2); + AQ[3] := q2 xor r2 xor q7 xor r7 xor r3 xor Rotr32(q3 xor r3); + AQ[4] := q3 xor r3 xor q7 xor r7 xor r4 xor Rotr32(q4 xor r4); + AQ[5] := q4 xor r4 xor r5 xor Rotr32(q5 xor r5); + AQ[6] := q5 xor r5 xor r6 xor Rotr32(q6 xor r6); + AQ[7] := q6 xor r6 xor r7 xor Rotr32(q7 xor r7); +end; + +class procedure TAesBitSlicedEngine.InvMixColumns(var AQ: TBitSliceState); +var + q0, q1, q2, q3, q4, q5, q6, q7: UInt64; + r0, r1, r2, r3, r4, r5, r6, r7: UInt64; +begin + q0 := AQ[0]; + q1 := AQ[1]; + q2 := AQ[2]; + q3 := AQ[3]; + q4 := AQ[4]; + q5 := AQ[5]; + q6 := AQ[6]; + q7 := AQ[7]; + r0 := TBitOperations.RotateRight64(q0, 16); + r1 := TBitOperations.RotateRight64(q1, 16); + r2 := TBitOperations.RotateRight64(q2, 16); + r3 := TBitOperations.RotateRight64(q3, 16); + r4 := TBitOperations.RotateRight64(q4, 16); + r5 := TBitOperations.RotateRight64(q5, 16); + r6 := TBitOperations.RotateRight64(q6, 16); + r7 := TBitOperations.RotateRight64(q7, 16); + + AQ[0] := q5 xor q6 xor q7 xor r0 xor r5 xor r7 + xor Rotr32(q0 xor q5 xor q6 xor r0 xor r5); + AQ[1] := q0 xor q5 xor r0 xor r1 xor r5 xor r6 xor r7 + xor Rotr32(q1 xor q5 xor q7 xor r1 xor r5 xor r6); + AQ[2] := q0 xor q1 xor q6 xor r1 xor r2 xor r6 xor r7 + xor Rotr32(q0 xor q2 xor q6 xor r2 xor r6 xor r7); + AQ[3] := q0 xor q1 xor q2 xor q5 xor q6 xor r0 xor r2 xor r3 xor r5 + xor Rotr32(q0 xor q1 xor q3 xor q5 xor q6 xor q7 xor r0 xor r3 xor r5 xor r7); + AQ[4] := q1 xor q2 xor q3 xor q5 xor r1 xor r3 xor r4 xor r5 xor r6 xor r7 + xor Rotr32(q1 xor q2 xor q4 xor q5 xor q7 xor r1 xor r4 xor r5 xor r6); + AQ[5] := q2 xor q3 xor q4 xor q6 xor r2 xor r4 xor r5 xor r6 xor r7 + xor Rotr32(q2 xor q3 xor q5 xor q6 xor r2 xor r5 xor r6 xor r7); + AQ[6] := q3 xor q4 xor q5 xor q7 xor r3 xor r5 xor r6 xor r7 + xor Rotr32(q3 xor q4 xor q6 xor q7 xor r3 xor r6 xor r7); + AQ[7] := q4 xor q5 xor q6 xor r4 xor r6 xor r7 + xor Rotr32(q4 xor q5 xor q7 xor r4 xor r7); +end; + +class procedure TAesBitSlicedEngine.AddRoundKey(var AQ: TBitSliceState; + const ASkey: TCryptoLibUInt64Array; ASkOff: Int32); +begin + AQ[0] := AQ[0] xor ASkey[ASkOff + 0]; + AQ[1] := AQ[1] xor ASkey[ASkOff + 1]; + AQ[2] := AQ[2] xor ASkey[ASkOff + 2]; + AQ[3] := AQ[3] xor ASkey[ASkOff + 3]; + AQ[4] := AQ[4] xor ASkey[ASkOff + 4]; + AQ[5] := AQ[5] xor ASkey[ASkOff + 5]; + AQ[6] := AQ[6] xor ASkey[ASkOff + 6]; + AQ[7] := AQ[7] xor ASkey[ASkOff + 7]; +end; + +class function TAesBitSlicedEngine.SubWord(AX: UInt32): UInt32; +var + LQ: TBitSliceState; +begin + System.FillChar(LQ, System.SizeOf(LQ), 0); + LQ[0] := AX; + Ortho(LQ); + BitsliceSbox(LQ); + Ortho(LQ); + Result := UInt32(LQ[0]); +end; + +procedure TAesBitSlicedEngine.KeySchedule(const AKey: TCryptoLibByteArray); +const + M1 = UInt64($1111111111111111); + M2 = UInt64($2222222222222222); + M4 = UInt64($4444444444444444); + M8 = UInt64($8888888888888888); +var + LKeyLen, LNk, LNkf, LI, LJ, LK, LV, LN: Int32; + LTmp: UInt32; + LSkey32: array [0 .. 59] of UInt32; + LCompSkey: TCryptoLibUInt64Array; + LQ: TBitSliceState; + LX0, LX1, LX2, LX3: UInt64; +begin + LKeyLen := System.Length(AKey); + case LKeyLen of + 16: + FRounds := 10; + 24: + FRounds := 12; + 32: + FRounds := 14; + else + begin + TArrayUtilities.Fill(AKey, 0, System.Length(AKey), Byte(0)); + raise EArgumentCryptoLibException.CreateRes(@SInvalidKeyLength); + end; + end; + + LNk := LKeyLen shr 2; + LNkf := (FRounds + 1) shl 2; + + LTmp := 0; + for LI := 0 to LNk - 1 do + begin + LTmp := TPack.LE_To_UInt32(AKey, LI shl 2); + LSkey32[LI] := LTmp; + end; + + LJ := 0; + LK := 0; + for LI := LNk to LNkf - 1 do + begin + if LJ = 0 then + begin + LTmp := TBitOperations.RotateLeft32(LTmp, 24); + LTmp := SubWord(LTmp) xor RCon[LK]; + end + else if (LNk > 6) and (LJ = 4) then + begin + LTmp := SubWord(LTmp); + end; + LTmp := LTmp xor LSkey32[LI - LNk]; + LSkey32[LI] := LTmp; + System.Inc(LJ); + if LJ = LNk then + begin + LJ := 0; + System.Inc(LK); + end; + end; + + // Compress round keys into two words per round key (one bit per nibble). + System.SetLength(LCompSkey, (FRounds + 1) * 2); + LI := 0; + LJ := 0; + while LI < LNkf do + begin + InterleaveIn(LSkey32[LI], LSkey32[LI + 1], LSkey32[LI + 2], + LSkey32[LI + 3], LQ[0], LQ[4]); + LQ[1] := LQ[0]; + LQ[2] := LQ[0]; + LQ[3] := LQ[0]; + LQ[5] := LQ[4]; + LQ[6] := LQ[4]; + LQ[7] := LQ[4]; + Ortho(LQ); + LCompSkey[LJ + 0] := (LQ[0] and M1) or (LQ[1] and M2) or (LQ[2] and M4) + or (LQ[3] and M8); + LCompSkey[LJ + 1] := (LQ[4] and M1) or (LQ[5] and M2) or (LQ[6] and M4) + or (LQ[7] and M8); + System.Inc(LI, 4); + System.Inc(LJ, 2); + end; + + // Expand the compressed round keys to the full bit-sliced form. + System.SetLength(FSkey, (FRounds + 1) * 8); + LN := (FRounds + 1) * 2; + LV := 0; + for LI := 0 to LN - 1 do + begin + LX0 := LCompSkey[LI]; + LX1 := LX0; + LX2 := LX0; + LX3 := LX0; + LX0 := LX0 and M1; + LX1 := LX1 and M2; + LX2 := LX2 and M4; + LX3 := LX3 and M8; + LX1 := LX1 shr 1; + LX2 := LX2 shr 2; + LX3 := LX3 shr 3; + FSkey[LV + 0] := (LX0 shl 4) - LX0; + FSkey[LV + 1] := (LX1 shl 4) - LX1; + FSkey[LV + 2] := (LX2 shl 4) - LX2; + FSkey[LV + 3] := (LX3 shl 4) - LX3; + System.Inc(LV, 4); + end; + + TArrayUtilities.Fill(AKey, 0, System.Length(AKey), Byte(0)); +end; + +procedure TAesBitSlicedEngine.BitsliceEncrypt(var AQ: TBitSliceState); +var + LU: Int32; +begin + AddRoundKey(AQ, FSkey, 0); + for LU := 1 to FRounds - 1 do + begin + BitsliceSbox(AQ); + ShiftRows(AQ); + MixColumns(AQ); + AddRoundKey(AQ, FSkey, LU shl 3); + end; + BitsliceSbox(AQ); + ShiftRows(AQ); + AddRoundKey(AQ, FSkey, FRounds shl 3); +end; + +procedure TAesBitSlicedEngine.BitsliceDecrypt(var AQ: TBitSliceState); +var + LU: Int32; +begin + AddRoundKey(AQ, FSkey, FRounds shl 3); + for LU := FRounds - 1 downto 1 do + begin + InvShiftRows(AQ); + BitsliceInvSbox(AQ); + AddRoundKey(AQ, FSkey, LU shl 3); + InvMixColumns(AQ); + end; + InvShiftRows(AQ); + BitsliceInvSbox(AQ); + AddRoundKey(AQ, FSkey, 0); +end; + +function TAesBitSlicedEngine.GetAlgorithmName: String; +begin + Result := 'AES'; +end; + +function TAesBitSlicedEngine.GetBlockSize: Int32; +begin + Result := BLOCK_SIZE; +end; + +procedure TAesBitSlicedEngine.Init(AForEncryption: Boolean; + const AParameters: ICipherParameters); +var + LKeyParameter: IKeyParameter; +begin + if not Supports(AParameters, IKeyParameter, LKeyParameter) then + begin + raise EArgumentCryptoLibException.CreateResFmt(@SInvalidParameterAESInit, + [TPlatformUtilities.GetTypeName(AParameters as TObject)]); + end; + + FForEncryption := AForEncryption; + KeySchedule(LKeyParameter.GetKey()); +end; + +procedure TAesBitSlicedEngine.Reset; +begin + // The bit-sliced engine keeps no per-block chaining state; nothing to reset. +end; + +function TAesBitSlicedEngine.ProcessBlock(const AInput: TCryptoLibByteArray; + AInOff: Int32; const AOutput: TCryptoLibByteArray; AOutOff: Int32): Int32; +var + LQ: TBitSliceState; + LW0, LW1, LW2, LW3: UInt32; +begin + if FSkey = nil then + begin + raise EInvalidOperationCryptoLibException.CreateRes + (@SAesEngineNotInitialized); + end; + + TCheck.DataLength(AInput, AInOff, 16, SInputBufferTooShort); + TCheck.OutputLength(AOutput, AOutOff, 16, SOutputBufferTooShort); + + // Load the single 16-byte block into lane 0 (q[0], q[4]); other lanes unused. + LW0 := TPack.LE_To_UInt32(AInput, AInOff); + LW1 := TPack.LE_To_UInt32(AInput, AInOff + 4); + LW2 := TPack.LE_To_UInt32(AInput, AInOff + 8); + LW3 := TPack.LE_To_UInt32(AInput, AInOff + 12); + + InterleaveIn(LW0, LW1, LW2, LW3, LQ[0], LQ[4]); + LQ[1] := 0; + LQ[2] := 0; + LQ[3] := 0; + LQ[5] := 0; + LQ[6] := 0; + LQ[7] := 0; + + Ortho(LQ); + if FForEncryption then + begin + BitsliceEncrypt(LQ); + end + else + begin + BitsliceDecrypt(LQ); + end; + Ortho(LQ); + + InterleaveOut(LW0, LW1, LW2, LW3, LQ[0], LQ[4]); + + TPack.UInt32_To_LE(LW0, AOutput, AOutOff); + TPack.UInt32_To_LE(LW1, AOutput, AOutOff + 4); + TPack.UInt32_To_LE(LW2, AOutput, AOutOff + 8); + TPack.UInt32_To_LE(LW3, AOutput, AOutOff + 12); + + Result := BLOCK_SIZE; +end; + +end. diff --git a/CryptoLib/src/Packages/Delphi/CryptoLib4PascalPackage.dpk b/CryptoLib/src/Packages/Delphi/CryptoLib4PascalPackage.dpk index 4e4d4dea..6c86f34d 100644 --- a/CryptoLib/src/Packages/Delphi/CryptoLib4PascalPackage.dpk +++ b/CryptoLib/src/Packages/Delphi/CryptoLib4PascalPackage.dpk @@ -37,6 +37,7 @@ requires contains ClpAesEngine in '..\..\Crypto\Engines\ClpAesEngine.pas', + ClpAesBitSlicedEngine in '..\..\Crypto\Engines\ClpAesBitSlicedEngine.pas', ClpIAesHardwareEngine in '..\..\Interfaces\Crypto\Engines\ClpIAesHardwareEngine.pas', ClpIAesEngineX86 in '..\..\Interfaces\Crypto\Engines\ClpIAesEngineX86.pas', ClpAesEngineX86 in '..\..\Crypto\Engines\ClpAesEngineX86.pas', diff --git a/CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.lpk b/CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.lpk index 68f6ea76..412629ed 100644 --- a/CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.lpk +++ b/CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.lpk @@ -31,7 +31,7 @@ Acknowledgements: Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring the development of this library "/> - + @@ -3461,6 +3461,10 @@ Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring the devel + + + + diff --git a/CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.pas b/CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.pas index aadde7dd..cbbff24f 100644 --- a/CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.pas +++ b/CryptoLib/src/Packages/FPC/CryptoLib4PascalPackage.pas @@ -279,7 +279,7 @@ interface ClpPkixCrlRevocationChecker, ClpPkixRevocationChecker, ClpPkixAttrCertChecker, ClpRfc3281CertPathUtilities, ClpPkixAttrCertPathValidator, ClpPkixAttrCertPathBuilder, - ClpCryptoLibExceptions; + ClpCryptoLibExceptions, ClpAesBitSlicedEngine; implementation