diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptor.java index ea7e724a62..8fc132b660 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptor.java @@ -15,11 +15,8 @@ */ package org.springframework.security.crypto.encrypt; -import java.io.ByteArrayOutputStream; - import org.bouncycastle.crypto.PBEParametersGenerator; import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; -import org.bouncycastle.crypto.io.CipherOutputStream; import org.bouncycastle.crypto.params.KeyParameter; import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.keygen.BytesKeyGenerator; @@ -52,26 +49,4 @@ abstract class BouncyCastleAesBytesEncryptor implements BytesEncryptor { keyGenerator.init(pkcs12PasswordBytes, Hex.decode(salt), 1024); 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); - } - } - } diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java index fa40bace89..fcc08beb16 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java @@ -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.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.io.CipherOutputStream; import org.bouncycastle.crypto.modes.CBCBlockCipher; import org.bouncycastle.crypto.paddings.PKCS7Padding; import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; @@ -55,12 +54,7 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( new CBCBlockCipher(new AESFastEngine()), new PKCS7Padding()); blockCipher.init(true, new ParametersWithIV(secretKey, iv)); - 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; } @@ -73,11 +67,23 @@ public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryp PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher( new CBCBlockCipher(new AESFastEngine()), new PKCS7Padding()); blockCipher.init(false, new ParametersWithIV(secretKey, iv)); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( - blockCipher.getOutputSize(encryptedBytes.length)); - CipherOutputStream cipherOutputStream = new CipherOutputStream( - byteArrayOutputStream, blockCipher); + return process(blockCipher, encryptedBytes); + } - 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; } } diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java index 62f92fab13..bf65fee41b 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java @@ -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.subArray; -import java.io.ByteArrayOutputStream; - +import org.bouncycastle.crypto.InvalidCipherTextException; 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.params.AEADParameters; import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm; @@ -51,13 +50,9 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp byte[] iv = this.ivGenerator.generateKey(); GCMBlockCipher blockCipher = new GCMBlockCipher(new AESFastEngine()); - blockCipher.init(true, new AEADParameters(secretKey, 128, iv)); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( - blockCipher.getOutputSize(bytes.length)); - CipherOutputStream cipherOutputStream = new CipherOutputStream(byteArrayOutputStream, - blockCipher); + blockCipher.init(true, new AEADParameters(secretKey, 128, iv, null)); - byte[] encrypted = process(cipherOutputStream, byteArrayOutputStream, bytes); + byte[] encrypted = process(blockCipher, bytes); return iv != null ? concatenate(iv, encrypted) : encrypted; } @@ -68,13 +63,25 @@ public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryp encryptedBytes.length); GCMBlockCipher blockCipher = new GCMBlockCipher(new AESFastEngine()); - blockCipher.init(false, new AEADParameters(secretKey, 128, iv)); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream( - blockCipher.getOutputSize(encryptedBytes.length)); - CipherOutputStream cipherOutputStream = new CipherOutputStream( - byteArrayOutputStream, blockCipher); + blockCipher.init(false, new AEADParameters(secretKey, 128, iv, null)); + return process(blockCipher, encryptedBytes); + } - 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; } } diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTest.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTest.java index 27d35e318c..9668247f18 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTest.java +++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTest.java @@ -32,17 +32,15 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTest { private byte[] testData; private String password; private String salt; + private SecureRandom secureRandom = new SecureRandom(); @Before public void setup() { // generate random password, salt, and test data - SecureRandom secureRandom = new SecureRandom(); password = UUID.randomUUID().toString(); byte[] saltBytes = new byte[16]; secureRandom.nextBytes(saltBytes); salt = new String(Hex.encode(saltBytes)); - testData = new byte[1024 * 1024]; - secureRandom.nextBytes(testData); } @Test @@ -87,30 +85,38 @@ public class BouncyCastleAesBytesEncryptorEquivalencyTest { private void testEquivalence(BytesEncryptor left, BytesEncryptor right) throws Exception { - // tests that right and left generate the same encrypted bytes - // and can decrypt back to the original input - byte[] leftEncrypted = left.encrypt(testData); - byte[] rightEncrypted = right.encrypt(testData); - Assert.assertArrayEquals(leftEncrypted, rightEncrypted); - byte[] leftDecrypted = left.decrypt(leftEncrypted); - byte[] rightDecrypted = right.decrypt(rightEncrypted); - Assert.assertArrayEquals(testData, leftDecrypted); - Assert.assertArrayEquals(testData, rightDecrypted); + for (int size = 1; size < 2048; size++) { + testData = new byte[size]; + secureRandom.nextBytes(testData); + // tests that right and left generate the same encrypted bytes + // and can decrypt back to the original input + byte[] leftEncrypted = left.encrypt(testData); + byte[] rightEncrypted = right.encrypt(testData); + Assert.assertArrayEquals(leftEncrypted, rightEncrypted); + 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) throws Exception { // tests that right can decrypt what left encrypted and vice versa // and that the decypted data is the same as the original - byte[] leftEncrypted = left.encrypt(testData); - byte[] rightEncrypted = right.encrypt(testData); - byte[] leftDecrypted = left.decrypt(rightEncrypted); - byte[] rightDecrypted = right.decrypt(leftEncrypted); - Assert.assertArrayEquals(testData, leftDecrypted); - Assert.assertArrayEquals(testData, rightDecrypted); + for (int size = 1; size < 2048; size++) { + testData = new byte[size]; + secureRandom.nextBytes(testData); + byte[] leftEncrypted = left.encrypt(testData); + byte[] rightEncrypted = right.encrypt(testData); + 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 */