mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-08 05:02:13 +00:00
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…
x
Reference in New Issue
Block a user