mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-28 06:42:49 +00:00
Bouncy Castle 1.47 Support
This forces us to avoid using CipherOutputStream, and instead use the BlockCiphers directly. As an extra measure for correctness, test the equivalence of the BC implementations against data sizes from 1 to 2048 bytes. Fixes gh-2917
This commit is contained in:
parent
81c9fa805f
commit
b01437281d
@ -15,11 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.encrypt;
|
package org.springframework.security.crypto.encrypt;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
import org.bouncycastle.crypto.PBEParametersGenerator;
|
import org.bouncycastle.crypto.PBEParametersGenerator;
|
||||||
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
|
||||||
import org.bouncycastle.crypto.io.CipherOutputStream;
|
|
||||||
import org.bouncycastle.crypto.params.KeyParameter;
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
import org.springframework.security.crypto.codec.Hex;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||||
@ -52,26 +49,4 @@ abstract class BouncyCastleAesBytesEncryptor implements BytesEncryptor {
|
|||||||
keyGenerator.init(pkcs12PasswordBytes, Hex.decode(salt), 1024);
|
keyGenerator.init(pkcs12PasswordBytes, Hex.decode(salt), 1024);
|
||||||
this.secretKey = (KeyParameter) keyGenerator.generateDerivedParameters(256);
|
this.secretKey = (KeyParameter) keyGenerator.generateDerivedParameters(256);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] process(CipherOutputStream cipherOutputStream,
|
|
||||||
ByteArrayOutputStream byteArrayOutputStream, byte[] bytes) {
|
|
||||||
try {
|
|
||||||
cipherOutputStream.write(bytes);
|
|
||||||
// close() invokes the doFinal method of the encapsulated cipher object
|
|
||||||
// and flushes to the underlying outputStream. It must be called before
|
|
||||||
// we get the output.
|
|
||||||
cipherOutputStream.close();
|
|
||||||
return byteArrayOutputStream.toByteArray();
|
|
||||||
}
|
|
||||||
catch (Throwable e) {
|
|
||||||
try {
|
|
||||||
// attempt release of resources
|
|
||||||
cipherOutputStream.close();
|
|
||||||
}
|
|
||||||
catch (Throwable e1) {
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("unable to encrypt/decrypt", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,9 @@ package org.springframework.security.crypto.encrypt;
|
|||||||
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
|
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||||
import org.bouncycastle.crypto.io.CipherOutputStream;
|
|
||||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||||
import org.bouncycastle.crypto.paddings.PKCS7Padding;
|
import org.bouncycastle.crypto.paddings.PKCS7Padding;
|
||||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||||
@ -55,12 +54,7 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp
|
|||||||
PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(
|
PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(
|
||||||
new CBCBlockCipher(new AESFastEngine()), new PKCS7Padding());
|
new CBCBlockCipher(new AESFastEngine()), new PKCS7Padding());
|
||||||
blockCipher.init(true, new ParametersWithIV(secretKey, iv));
|
blockCipher.init(true, new ParametersWithIV(secretKey, iv));
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(
|
byte[] encrypted = process(blockCipher, bytes);
|
||||||
blockCipher.getOutputSize(bytes.length));
|
|
||||||
CipherOutputStream cipherOutputStream = new CipherOutputStream(
|
|
||||||
byteArrayOutputStream, blockCipher);
|
|
||||||
|
|
||||||
byte[] encrypted = process(cipherOutputStream, byteArrayOutputStream, bytes);
|
|
||||||
return iv != null ? concatenate(iv, encrypted) : encrypted;
|
return iv != null ? concatenate(iv, encrypted) : encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,11 +67,23 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp
|
|||||||
PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(
|
PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(
|
||||||
new CBCBlockCipher(new AESFastEngine()), new PKCS7Padding());
|
new CBCBlockCipher(new AESFastEngine()), new PKCS7Padding());
|
||||||
blockCipher.init(false, new ParametersWithIV(secretKey, iv));
|
blockCipher.init(false, new ParametersWithIV(secretKey, iv));
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(
|
return process(blockCipher, encryptedBytes);
|
||||||
blockCipher.getOutputSize(encryptedBytes.length));
|
}
|
||||||
CipherOutputStream cipherOutputStream = new CipherOutputStream(
|
|
||||||
byteArrayOutputStream, blockCipher);
|
|
||||||
|
|
||||||
return process(cipherOutputStream, byteArrayOutputStream, encryptedBytes);
|
private byte[] process(BufferedBlockCipher blockCipher, byte[] in) {
|
||||||
|
byte[] buf = new byte[blockCipher.getOutputSize(in.length)];
|
||||||
|
int bytesWritten = blockCipher.processBytes(in, 0, in.length, buf, 0);
|
||||||
|
try {
|
||||||
|
bytesWritten += blockCipher.doFinal(buf, bytesWritten);
|
||||||
|
}
|
||||||
|
catch (InvalidCipherTextException e) {
|
||||||
|
throw new IllegalStateException("unable to encrypt/decrypt", e);
|
||||||
|
}
|
||||||
|
if (bytesWritten == buf.length) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
byte[] out = new byte[bytesWritten];
|
||||||
|
System.arraycopy(buf, 0, out, 0, bytesWritten);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,9 @@ package org.springframework.security.crypto.encrypt;
|
|||||||
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
|
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||||
|
|
||||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||||
import org.bouncycastle.crypto.io.CipherOutputStream;
|
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;
|
||||||
import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm;
|
import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm;
|
||||||
@ -51,13 +50,9 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp
|
|||||||
byte[] iv = this.ivGenerator.generateKey();
|
byte[] iv = this.ivGenerator.generateKey();
|
||||||
|
|
||||||
GCMBlockCipher blockCipher = new GCMBlockCipher(new AESFastEngine());
|
GCMBlockCipher blockCipher = new GCMBlockCipher(new AESFastEngine());
|
||||||
blockCipher.init(true, new AEADParameters(secretKey, 128, iv));
|
blockCipher.init(true, new AEADParameters(secretKey, 128, iv, null));
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(
|
|
||||||
blockCipher.getOutputSize(bytes.length));
|
|
||||||
CipherOutputStream cipherOutputStream = new CipherOutputStream(byteArrayOutputStream,
|
|
||||||
blockCipher);
|
|
||||||
|
|
||||||
byte[] encrypted = process(cipherOutputStream, byteArrayOutputStream, bytes);
|
byte[] encrypted = process(blockCipher, bytes);
|
||||||
return iv != null ? concatenate(iv, encrypted) : encrypted;
|
return iv != null ? concatenate(iv, encrypted) : encrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,13 +63,25 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp
|
|||||||
encryptedBytes.length);
|
encryptedBytes.length);
|
||||||
|
|
||||||
GCMBlockCipher blockCipher = new GCMBlockCipher(new AESFastEngine());
|
GCMBlockCipher blockCipher = new GCMBlockCipher(new AESFastEngine());
|
||||||
blockCipher.init(false, new AEADParameters(secretKey, 128, iv));
|
blockCipher.init(false, new AEADParameters(secretKey, 128, iv, null));
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(
|
return process(blockCipher, encryptedBytes);
|
||||||
blockCipher.getOutputSize(encryptedBytes.length));
|
}
|
||||||
CipherOutputStream cipherOutputStream = new CipherOutputStream(
|
|
||||||
byteArrayOutputStream, blockCipher);
|
|
||||||
|
|
||||||
return process(cipherOutputStream, byteArrayOutputStream, encryptedBytes);
|
private byte[] process(AEADBlockCipher blockCipher, byte[] in) {
|
||||||
|
byte[] buf = new byte[blockCipher.getOutputSize(in.length)];
|
||||||
|
int bytesWritten = blockCipher.processBytes(in, 0, in.length, buf, 0);
|
||||||
|
try {
|
||||||
|
bytesWritten += blockCipher.doFinal(buf, bytesWritten);
|
||||||
|
}
|
||||||
|
catch (InvalidCipherTextException e) {
|
||||||
|
throw new IllegalStateException("unable to encrypt/decrypt", e);
|
||||||
|
}
|
||||||
|
if (bytesWritten == buf.length) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
byte[] out = new byte[bytesWritten];
|
||||||
|
System.arraycopy(buf, 0, out, 0, bytesWritten);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,17 +32,15 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTest {
|
|||||||
private byte[] testData;
|
private byte[] testData;
|
||||||
private String password;
|
private String password;
|
||||||
private String salt;
|
private String salt;
|
||||||
|
private SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
// generate random password, salt, and test data
|
// generate random password, salt, and test data
|
||||||
SecureRandom secureRandom = new SecureRandom();
|
|
||||||
password = UUID.randomUUID().toString();
|
password = UUID.randomUUID().toString();
|
||||||
byte[] saltBytes = new byte[16];
|
byte[] saltBytes = new byte[16];
|
||||||
secureRandom.nextBytes(saltBytes);
|
secureRandom.nextBytes(saltBytes);
|
||||||
salt = new String(Hex.encode(saltBytes));
|
salt = new String(Hex.encode(saltBytes));
|
||||||
testData = new byte[1024 * 1024];
|
|
||||||
secureRandom.nextBytes(testData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -87,30 +85,38 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTest {
|
|||||||
|
|
||||||
private void testEquivalence(BytesEncryptor left, BytesEncryptor right)
|
private void testEquivalence(BytesEncryptor left, BytesEncryptor right)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// tests that right and left generate the same encrypted bytes
|
for (int size = 1; size < 2048; size++) {
|
||||||
// and can decrypt back to the original input
|
testData = new byte[size];
|
||||||
byte[] leftEncrypted = left.encrypt(testData);
|
secureRandom.nextBytes(testData);
|
||||||
byte[] rightEncrypted = right.encrypt(testData);
|
// tests that right and left generate the same encrypted bytes
|
||||||
Assert.assertArrayEquals(leftEncrypted, rightEncrypted);
|
// and can decrypt back to the original input
|
||||||
byte[] leftDecrypted = left.decrypt(leftEncrypted);
|
byte[] leftEncrypted = left.encrypt(testData);
|
||||||
byte[] rightDecrypted = right.decrypt(rightEncrypted);
|
byte[] rightEncrypted = right.encrypt(testData);
|
||||||
Assert.assertArrayEquals(testData, leftDecrypted);
|
Assert.assertArrayEquals(leftEncrypted, rightEncrypted);
|
||||||
Assert.assertArrayEquals(testData, rightDecrypted);
|
byte[] leftDecrypted = left.decrypt(leftEncrypted);
|
||||||
|
byte[] rightDecrypted = right.decrypt(rightEncrypted);
|
||||||
|
Assert.assertArrayEquals(testData, leftDecrypted);
|
||||||
|
Assert.assertArrayEquals(testData, rightDecrypted);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testCompatibility(BytesEncryptor left, BytesEncryptor right)
|
private void testCompatibility(BytesEncryptor left, BytesEncryptor right)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// 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 decypted data is the same as the original
|
||||||
byte[] leftEncrypted = left.encrypt(testData);
|
for (int size = 1; size < 2048; size++) {
|
||||||
byte[] rightEncrypted = right.encrypt(testData);
|
testData = new byte[size];
|
||||||
byte[] leftDecrypted = left.decrypt(rightEncrypted);
|
secureRandom.nextBytes(testData);
|
||||||
byte[] rightDecrypted = right.decrypt(leftEncrypted);
|
byte[] leftEncrypted = left.encrypt(testData);
|
||||||
Assert.assertArrayEquals(testData, leftDecrypted);
|
byte[] rightEncrypted = right.encrypt(testData);
|
||||||
Assert.assertArrayEquals(testData, rightDecrypted);
|
byte[] leftDecrypted = left.decrypt(rightEncrypted);
|
||||||
|
byte[] rightDecrypted = right.decrypt(leftEncrypted);
|
||||||
|
Assert.assertArrayEquals(testData, leftDecrypted);
|
||||||
|
Assert.assertArrayEquals(testData, rightDecrypted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A BytesKeyGenerator that always generates the same sequence of values
|
* A BytesKeyGenerator that always generates the same sequence of values
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user