11/*
2- * Copyright 2011-2021 the original author or authors.
2+ * Copyright 2011-2025 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1717package org .springframework .security .crypto .encrypt ;
1818
1919import java .security .SecureRandom ;
20+ import java .time .Duration ;
21+ import java .time .temporal .ChronoUnit ;
2022import java .util .Random ;
2123import java .util .UUID ;
2224
2325import org .junit .jupiter .api .BeforeEach ;
26+ import org .junit .jupiter .api .Disabled ;
27+ import org .junit .jupiter .api .RepeatedTest ;
2428import org .junit .jupiter .api .Test ;
2529
2630import org .springframework .security .crypto .codec .Hex ;
@@ -89,6 +93,64 @@ public void bouncyCastleAesGcmWithSecureIvCompatible() throws Exception {
8993 testCompatibility (bcEncryptor , jceEncryptor );
9094 }
9195
96+ @ Test
97+ public void bouncyCastleAesGcmWithAESFastEngineCompatible () throws Exception {
98+ CryptoAssumptions .assumeGCMJCE ();
99+ BytesEncryptor fastEngineEncryptor = BouncyCastleAesGcmBytesEncryptor .withAESFastEngine (this .password ,
100+ this .salt , KeyGenerators .secureRandom (16 ));
101+ BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesGcmBytesEncryptor (this .password , this .salt ,
102+ KeyGenerators .secureRandom (16 ));
103+ testCompatibility (fastEngineEncryptor , defaultEngineEncryptor );
104+ }
105+
106+ @ Test
107+ public void bouncyCastleAesCbcWithAESFastEngineCompatible () throws Exception {
108+ CryptoAssumptions .assumeCBCJCE ();
109+ BytesEncryptor fastEngineEncryptor = BouncyCastleAesCbcBytesEncryptor .withAESFastEngine (this .password ,
110+ this .salt , KeyGenerators .secureRandom (16 ));
111+ BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesCbcBytesEncryptor (this .password , this .salt ,
112+ KeyGenerators .secureRandom (16 ));
113+ testCompatibility (fastEngineEncryptor , defaultEngineEncryptor );
114+ }
115+
116+ /**
117+ * Comment out @Disabled below to compare relative speed of deprecated AESFastEngine
118+ * with the default AESEngine.
119+ */
120+ @ Disabled
121+ @ RepeatedTest (100 )
122+ public void bouncyCastleAesGcmWithAESFastEngineSpeedTest () throws Exception {
123+ CryptoAssumptions .assumeGCMJCE ();
124+ BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesGcmBytesEncryptor (this .password , this .salt ,
125+ KeyGenerators .secureRandom (16 ));
126+ BytesEncryptor fastEngineEncryptor = BouncyCastleAesGcmBytesEncryptor .withAESFastEngine (this .password ,
127+ this .salt , KeyGenerators .secureRandom (16 ));
128+ long defaultNanos = testSpeed (defaultEngineEncryptor );
129+ long fastNanos = testSpeed (fastEngineEncryptor );
130+ System .out .println (nanosToReadableString ("AES GCM w/Default Engine" , defaultNanos ));
131+ System .out .println (nanosToReadableString ("AES GCM w/ Fast Engine" , fastNanos ));
132+ assertThat (fastNanos ).isLessThan (defaultNanos );
133+ }
134+
135+ /**
136+ * Comment out @Disabled below to compare relative speed of deprecated AESFastEngine
137+ * with the default AESEngine.
138+ */
139+ @ Disabled
140+ @ RepeatedTest (100 )
141+ public void bouncyCastleAesCbcWithAESFastEngineSpeedTest () throws Exception {
142+ CryptoAssumptions .assumeCBCJCE ();
143+ BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesCbcBytesEncryptor (this .password , this .salt ,
144+ KeyGenerators .secureRandom (16 ));
145+ BytesEncryptor fastEngineEncryptor = BouncyCastleAesCbcBytesEncryptor .withAESFastEngine (this .password ,
146+ this .salt , KeyGenerators .secureRandom (16 ));
147+ long defaultNanos = testSpeed (defaultEngineEncryptor );
148+ long fastNanos = testSpeed (fastEngineEncryptor );
149+ System .out .println (nanosToReadableString ("AES CBC w/Default Engine" , defaultNanos ));
150+ System .out .println (nanosToReadableString ("AES CBC w/ Fast Engine" , fastNanos ));
151+ assertThat (fastNanos ).isLessThan (defaultNanos );
152+ }
153+
92154 private void testEquivalence (BytesEncryptor left , BytesEncryptor right ) {
93155 for (int size = 1 ; size < 2048 ; size ++) {
94156 this .testData = new byte [size ];
@@ -107,7 +169,7 @@ private void testEquivalence(BytesEncryptor left, BytesEncryptor right) {
107169
108170 private void testCompatibility (BytesEncryptor left , BytesEncryptor right ) {
109171 // tests that right can decrypt what left encrypted and vice versa
110- // and that the decypted data is the same as the original
172+ // and that the decrypted data is the same as the original
111173 for (int size = 1 ; size < 2048 ; size ++) {
112174 this .testData = new byte [size ];
113175 this .secureRandom .nextBytes (this .testData );
@@ -120,6 +182,25 @@ private void testCompatibility(BytesEncryptor left, BytesEncryptor right) {
120182 }
121183 }
122184
185+ private long testSpeed (BytesEncryptor bytesEncryptor ) {
186+ long start = System .nanoTime ();
187+ for (int size = 0 ; size < 2048 ; size ++) {
188+ this .testData = new byte [size ];
189+ this .secureRandom .nextBytes (this .testData );
190+ byte [] encrypted = bytesEncryptor .encrypt (this .testData );
191+ byte [] decrypted = bytesEncryptor .decrypt (encrypted );
192+ assertThat (decrypted ).containsExactly (this .testData );
193+ }
194+ return System .nanoTime () - start ;
195+ }
196+
197+ private String nanosToReadableString (String label , long nanos ) {
198+ Duration duration = Duration .ofNanos (nanos );
199+ Duration millis = duration .truncatedTo (ChronoUnit .MILLIS );
200+ Duration micros = duration .minus (millis ).dividedBy (1000 );
201+ return "%s: %dms %dμs" .formatted (label , duration .toMillis (), micros .toNanos ());
202+ }
203+
123204 /**
124205 * A BytesKeyGenerator that always generates the same sequence of values
125206 */
0 commit comments