Polish gh-16164
This commit is contained in:
		
							parent
							
								
									2b22cf2877
								
							
						
					
					
						commit
						5eb232cd3d
					
				| 
						 | 
				
			
			@ -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");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
| 
						 | 
				
			
			@ -16,10 +16,14 @@
 | 
			
		|||
 | 
			
		||||
package org.springframework.security.crypto.encrypt;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
import org.bouncycastle.crypto.BufferedBlockCipher;
 | 
			
		||||
import org.bouncycastle.crypto.InvalidCipherTextException;
 | 
			
		||||
import org.bouncycastle.crypto.engines.AESEngine;
 | 
			
		||||
import org.bouncycastle.crypto.engines.AESFastEngine;
 | 
			
		||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
 | 
			
		||||
import org.bouncycastle.crypto.modes.CBCModeCipher;
 | 
			
		||||
import org.bouncycastle.crypto.paddings.PKCS7Padding;
 | 
			
		||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
 | 
			
		||||
import org.bouncycastle.crypto.params.ParametersWithIV;
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +41,8 @@ import org.springframework.security.crypto.util.EncodingUtils;
 | 
			
		|||
 */
 | 
			
		||||
public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryptor {
 | 
			
		||||
 | 
			
		||||
	private Supplier<CBCModeCipher> cipherFactory = () -> CBCBlockCipher.newInstance(AESEngine.newInstance());
 | 
			
		||||
 | 
			
		||||
	public BouncyCastleAesCbcBytesEncryptor(String password, CharSequence salt) {
 | 
			
		||||
		super(password, salt);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -48,8 +54,8 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp
 | 
			
		|||
	@Override
 | 
			
		||||
	public byte[] encrypt(byte[] bytes) {
 | 
			
		||||
		byte[] iv = this.ivGenerator.generateKey();
 | 
			
		||||
		PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(
 | 
			
		||||
				CBCBlockCipher.newInstance(AESEngine.newInstance()), new PKCS7Padding());
 | 
			
		||||
		PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(this.cipherFactory.get(),
 | 
			
		||||
				new PKCS7Padding());
 | 
			
		||||
		blockCipher.init(true, new ParametersWithIV(this.secretKey, iv));
 | 
			
		||||
		byte[] encrypted = process(blockCipher, bytes);
 | 
			
		||||
		return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted;
 | 
			
		||||
| 
						 | 
				
			
			@ -59,8 +65,8 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp
 | 
			
		|||
	public byte[] decrypt(byte[] encryptedBytes) {
 | 
			
		||||
		byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength());
 | 
			
		||||
		encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length);
 | 
			
		||||
		PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(
 | 
			
		||||
				CBCBlockCipher.newInstance(AESEngine.newInstance()), new PKCS7Padding());
 | 
			
		||||
		PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(this.cipherFactory.get(),
 | 
			
		||||
				new PKCS7Padding());
 | 
			
		||||
		blockCipher.init(false, new ParametersWithIV(this.secretKey, iv));
 | 
			
		||||
		return process(blockCipher, encryptedBytes);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -82,4 +88,17 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp
 | 
			
		|||
		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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
| 
						 | 
				
			
			@ -16,8 +16,11 @@
 | 
			
		|||
 | 
			
		||||
package org.springframework.security.crypto.encrypt;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Supplier;
 | 
			
		||||
 | 
			
		||||
import org.bouncycastle.crypto.InvalidCipherTextException;
 | 
			
		||||
import org.bouncycastle.crypto.engines.AESEngine;
 | 
			
		||||
import org.bouncycastle.crypto.engines.AESFastEngine;
 | 
			
		||||
import org.bouncycastle.crypto.modes.AEADBlockCipher;
 | 
			
		||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
 | 
			
		||||
import org.bouncycastle.crypto.params.AEADParameters;
 | 
			
		||||
| 
						 | 
				
			
			@ -36,6 +39,9 @@ import org.springframework.security.crypto.util.EncodingUtils;
 | 
			
		|||
 */
 | 
			
		||||
public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryptor {
 | 
			
		||||
 | 
			
		||||
	private Supplier<GCMBlockCipher> cipherFactory = () -> (GCMBlockCipher) GCMBlockCipher
 | 
			
		||||
		.newInstance(AESEngine.newInstance());
 | 
			
		||||
 | 
			
		||||
	public BouncyCastleAesGcmBytesEncryptor(String password, CharSequence salt) {
 | 
			
		||||
		super(password, salt);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +53,7 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp
 | 
			
		|||
	@Override
 | 
			
		||||
	public byte[] encrypt(byte[] bytes) {
 | 
			
		||||
		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));
 | 
			
		||||
		byte[] encrypted = process(blockCipher, bytes);
 | 
			
		||||
		return (iv != null) ? EncodingUtils.concatenate(iv, encrypted) : encrypted;
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +63,7 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp
 | 
			
		|||
	public byte[] decrypt(byte[] encryptedBytes) {
 | 
			
		||||
		byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength());
 | 
			
		||||
		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));
 | 
			
		||||
		return process(blockCipher, encryptedBytes);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -79,4 +85,17 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp
 | 
			
		|||
		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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
| 
						 | 
				
			
			@ -17,10 +17,14 @@
 | 
			
		|||
package org.springframework.security.crypto.encrypt;
 | 
			
		||||
 | 
			
		||||
import java.security.SecureRandom;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.time.temporal.ChronoUnit;
 | 
			
		||||
import java.util.Random;
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
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.springframework.security.crypto.codec.Hex;
 | 
			
		||||
| 
						 | 
				
			
			@ -89,6 +93,64 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests {
 | 
			
		|||
		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) {
 | 
			
		||||
		for (int size = 1; size < 2048; size++) {
 | 
			
		||||
			this.testData = new byte[size];
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +169,7 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTests {
 | 
			
		|||
 | 
			
		||||
	private void testCompatibility(BytesEncryptor left, BytesEncryptor right) {
 | 
			
		||||
		// 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++) {
 | 
			
		||||
			this.testData = new byte[size];
 | 
			
		||||
			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
 | 
			
		||||
	 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue