Polish gh-16164

This commit is contained in:
Steve Riesenberg 2025-02-03 12:33:00 -06:00 committed by Rob Winch
parent 2b22cf2877
commit 5eb232cd3d
3 changed files with 129 additions and 10 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2016 the original author or authors. * Copyright 2011-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,10 +16,14 @@
package org.springframework.security.crypto.encrypt; package org.springframework.security.crypto.encrypt;
import java.util.function.Supplier;
import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.modes.CBCModeCipher;
import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV; import org.bouncycastle.crypto.params.ParametersWithIV;
@ -37,6 +41,8 @@ import org.springframework.security.crypto.util.EncodingUtils;
*/ */
public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryptor { public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryptor {
private Supplier<CBCModeCipher> cipherFactory = () -> CBCBlockCipher.newInstance(AESEngine.newInstance());
public BouncyCastleAesCbcBytesEncryptor(String password, CharSequence salt) { public BouncyCastleAesCbcBytesEncryptor(String password, CharSequence salt) {
super(password, salt); super(password, salt);
} }
@ -48,8 +54,8 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp
@Override @Override
public byte[] encrypt(byte[] bytes) { public byte[] encrypt(byte[] bytes) {
byte[] iv = this.ivGenerator.generateKey(); byte[] iv = this.ivGenerator.generateKey();
PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(this.cipherFactory.get(),
CBCBlockCipher.newInstance(AESEngine.newInstance()), new PKCS7Padding()); new PKCS7Padding());
blockCipher.init(true, new ParametersWithIV(this.secretKey, iv)); blockCipher.init(true, new ParametersWithIV(this.secretKey, iv));
byte[] encrypted = process(blockCipher, bytes); byte[] encrypted = process(blockCipher, bytes);
return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted;
@ -59,8 +65,8 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp
public byte[] decrypt(byte[] encryptedBytes) { public byte[] decrypt(byte[] encryptedBytes) {
byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength());
encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length);
PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(this.cipherFactory.get(),
CBCBlockCipher.newInstance(AESEngine.newInstance()), new PKCS7Padding()); new PKCS7Padding());
blockCipher.init(false, new ParametersWithIV(this.secretKey, iv)); blockCipher.init(false, new ParametersWithIV(this.secretKey, iv));
return process(blockCipher, encryptedBytes); return process(blockCipher, encryptedBytes);
} }
@ -82,4 +88,17 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp
return out; return out;
} }
/**
* Used to test compatibility with deprecated {@link AESFastEngine}.
*/
@SuppressWarnings("deprecation")
static BouncyCastleAesCbcBytesEncryptor withAESFastEngine(String password, CharSequence salt,
BytesKeyGenerator ivGenerator) {
BouncyCastleAesCbcBytesEncryptor bytesEncryptor = new BouncyCastleAesCbcBytesEncryptor(password, salt,
ivGenerator);
bytesEncryptor.cipherFactory = () -> new CBCBlockCipher(new AESFastEngine());
return bytesEncryptor;
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2016 the original author or authors. * Copyright 2011-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +16,11 @@
package org.springframework.security.crypto.encrypt; package org.springframework.security.crypto.encrypt;
import java.util.function.Supplier;
import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.AEADBlockCipher; import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.modes.GCMBlockCipher; import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters; import org.bouncycastle.crypto.params.AEADParameters;
@ -36,6 +39,9 @@ import org.springframework.security.crypto.util.EncodingUtils;
*/ */
public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryptor { public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryptor {
private Supplier<GCMBlockCipher> cipherFactory = () -> (GCMBlockCipher) GCMBlockCipher
.newInstance(AESEngine.newInstance());
public BouncyCastleAesGcmBytesEncryptor(String password, CharSequence salt) { public BouncyCastleAesGcmBytesEncryptor(String password, CharSequence salt) {
super(password, salt); super(password, salt);
} }
@ -47,7 +53,7 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp
@Override @Override
public byte[] encrypt(byte[] bytes) { public byte[] encrypt(byte[] bytes) {
byte[] iv = this.ivGenerator.generateKey(); byte[] iv = this.ivGenerator.generateKey();
GCMBlockCipher blockCipher = (GCMBlockCipher) GCMBlockCipher.newInstance(AESEngine.newInstance()); AEADBlockCipher blockCipher = this.cipherFactory.get();
blockCipher.init(true, new AEADParameters(this.secretKey, 128, iv, null)); blockCipher.init(true, new AEADParameters(this.secretKey, 128, iv, null));
byte[] encrypted = process(blockCipher, bytes); byte[] encrypted = process(blockCipher, bytes);
return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted; return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted;
@ -57,7 +63,7 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp
public byte[] decrypt(byte[] encryptedBytes) { public byte[] decrypt(byte[] encryptedBytes) {
byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength());
encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length); encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length);
GCMBlockCipher blockCipher = (GCMBlockCipher) GCMBlockCipher.newInstance(AESEngine.newInstance()); AEADBlockCipher blockCipher = this.cipherFactory.get();
blockCipher.init(false, new AEADParameters(this.secretKey, 128, iv, null)); blockCipher.init(false, new AEADParameters(this.secretKey, 128, iv, null));
return process(blockCipher, encryptedBytes); return process(blockCipher, encryptedBytes);
} }
@ -79,4 +85,17 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp
return out; return out;
} }
/**
* Used to test compatibility with deprecated {@link AESFastEngine}.
*/
@SuppressWarnings("deprecation")
static BouncyCastleAesGcmBytesEncryptor withAESFastEngine(String password, CharSequence salt,
BytesKeyGenerator ivGenerator) {
BouncyCastleAesGcmBytesEncryptor bytesEncryptor = new BouncyCastleAesGcmBytesEncryptor(password, salt,
ivGenerator);
bytesEncryptor.cipherFactory = () -> new GCMBlockCipher(new AESFastEngine());
return bytesEncryptor;
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2011-2021 the original author or authors. * Copyright 2011-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,10 +17,14 @@
package org.springframework.security.crypto.encrypt; package org.springframework.security.crypto.encrypt;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.codec.Hex;
@ -89,6 +93,64 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests {
testCompatibility(bcEncryptor, jceEncryptor); testCompatibility(bcEncryptor, jceEncryptor);
} }
@Test
public void bouncyCastleAesGcmWithAESFastEngineCompatible() throws Exception {
CryptoAssumptions.assumeGCMJCE();
BytesEncryptor fastEngineEncryptor = BouncyCastleAesGcmBytesEncryptor.withAESFastEngine(this.password,
this.salt, KeyGenerators.secureRandom(16));
BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesGcmBytesEncryptor(this.password, this.salt,
KeyGenerators.secureRandom(16));
testCompatibility(fastEngineEncryptor, defaultEngineEncryptor);
}
@Test
public void bouncyCastleAesCbcWithAESFastEngineCompatible() throws Exception {
CryptoAssumptions.assumeCBCJCE();
BytesEncryptor fastEngineEncryptor = BouncyCastleAesCbcBytesEncryptor.withAESFastEngine(this.password,
this.salt, KeyGenerators.secureRandom(16));
BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesCbcBytesEncryptor(this.password, this.salt,
KeyGenerators.secureRandom(16));
testCompatibility(fastEngineEncryptor, defaultEngineEncryptor);
}
/**
* Comment out @Disabled below to compare relative speed of deprecated AESFastEngine
* with the default AESEngine.
*/
@Disabled
@RepeatedTest(100)
public void bouncyCastleAesGcmWithAESFastEngineSpeedTest() throws Exception {
CryptoAssumptions.assumeGCMJCE();
BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesGcmBytesEncryptor(this.password, this.salt,
KeyGenerators.secureRandom(16));
BytesEncryptor fastEngineEncryptor = BouncyCastleAesGcmBytesEncryptor.withAESFastEngine(this.password,
this.salt, KeyGenerators.secureRandom(16));
long defaultNanos = testSpeed(defaultEngineEncryptor);
long fastNanos = testSpeed(fastEngineEncryptor);
System.out.println(nanosToReadableString("AES GCM w/Default Engine", defaultNanos));
System.out.println(nanosToReadableString("AES GCM w/ Fast Engine", fastNanos));
assertThat(fastNanos).isLessThan(defaultNanos);
}
/**
* Comment out @Disabled below to compare relative speed of deprecated AESFastEngine
* with the default AESEngine.
*/
@Disabled
@RepeatedTest(100)
public void bouncyCastleAesCbcWithAESFastEngineSpeedTest() throws Exception {
CryptoAssumptions.assumeCBCJCE();
BytesEncryptor defaultEngineEncryptor = new BouncyCastleAesCbcBytesEncryptor(this.password, this.salt,
KeyGenerators.secureRandom(16));
BytesEncryptor fastEngineEncryptor = BouncyCastleAesCbcBytesEncryptor.withAESFastEngine(this.password,
this.salt, KeyGenerators.secureRandom(16));
long defaultNanos = testSpeed(defaultEngineEncryptor);
long fastNanos = testSpeed(fastEngineEncryptor);
System.out.println(nanosToReadableString("AES CBC w/Default Engine", defaultNanos));
System.out.println(nanosToReadableString("AES CBC w/ Fast Engine", fastNanos));
assertThat(fastNanos).isLessThan(defaultNanos);
}
private void testEquivalence(BytesEncryptor left, BytesEncryptor right) { private void testEquivalence(BytesEncryptor left, BytesEncryptor right) {
for (int size = 1; size < 2048; size++) { for (int size = 1; size < 2048; size++) {
this.testData = new byte[size]; this.testData = new byte[size];
@ -107,7 +169,7 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests {
private void testCompatibility(BytesEncryptor left, BytesEncryptor right) { private void testCompatibility(BytesEncryptor left, BytesEncryptor right) {
// tests that right can decrypt what left encrypted and vice versa // tests that right can decrypt what left encrypted and vice versa
// and that the decypted data is the same as the original // and that the decrypted data is the same as the original
for (int size = 1; size < 2048; size++) { for (int size = 1; size < 2048; size++) {
this.testData = new byte[size]; this.testData = new byte[size];
this.secureRandom.nextBytes(this.testData); this.secureRandom.nextBytes(this.testData);
@ -120,6 +182,25 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests {
} }
} }
private long testSpeed(BytesEncryptor bytesEncryptor) {
long start = System.nanoTime();
for (int size = 0; size < 2048; size++) {
this.testData = new byte[size];
this.secureRandom.nextBytes(this.testData);
byte[] encrypted = bytesEncryptor.encrypt(this.testData);
byte[] decrypted = bytesEncryptor.decrypt(encrypted);
assertThat(decrypted).containsExactly(this.testData);
}
return System.nanoTime() - start;
}
private String nanosToReadableString(String label, long nanos) {
Duration duration = Duration.ofNanos(nanos);
Duration millis = duration.truncatedTo(ChronoUnit.MILLIS);
Duration micros = duration.minus(millis).dividedBy(1000);
return "%s: %dms %dμs".formatted(label, duration.toMillis(), micros.toNanos());
}
/** /**
* A BytesKeyGenerator that always generates the same sequence of values * A BytesKeyGenerator that always generates the same sequence of values
*/ */