forked from spring-projects/spring-security
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bouncy Castle implementations of AES-256.
Implments "AES/CBC/PKCS5Padding" and "AES/GCM/NoPadding" Fixes spring-projectsgh-2917
- Loading branch information
Will Tran
committed
Apr 13, 2016
1 parent
d05fe8e
commit b7b4507
Showing
5 changed files
with
469 additions
and
0 deletions.
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
.../main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright 2011-2016 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. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
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; | ||
import org.springframework.security.crypto.keygen.KeyGenerators; | ||
|
||
/** | ||
* Base class for AES-256 encryption using Bouncy Castle. | ||
* | ||
* @author William Tran | ||
* | ||
*/ | ||
abstract class BouncyCastleAesBytesEncryptor implements BytesEncryptor { | ||
|
||
final KeyParameter secretKey; | ||
final BytesKeyGenerator ivGenerator; | ||
|
||
BouncyCastleAesBytesEncryptor(String password, CharSequence salt) { | ||
this(password, salt, KeyGenerators.secureRandom(16)); | ||
} | ||
|
||
BouncyCastleAesBytesEncryptor(String password, CharSequence salt, | ||
BytesKeyGenerator ivGenerator) { | ||
if (ivGenerator.getKeyLength() != 16) { | ||
throw new IllegalArgumentException("ivGenerator key length != block size 16"); | ||
} | ||
this.ivGenerator = ivGenerator; | ||
PBEParametersGenerator keyGenerator = new PKCS5S2ParametersGenerator(); | ||
byte[] pkcs12PasswordBytes = PBEParametersGenerator | ||
.PKCS5PasswordToUTF8Bytes(password.toCharArray()); | ||
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); | ||
} | ||
} | ||
|
||
} |
83 changes: 83 additions & 0 deletions
83
...in/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright 2011-2016 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. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
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.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; | ||
import org.bouncycastle.crypto.params.ParametersWithIV; | ||
import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm; | ||
import org.springframework.security.crypto.keygen.BytesKeyGenerator; | ||
|
||
/** | ||
* An Encryptor equivalent to {@link AesBytesEncryptor} using | ||
* {@link CipherAlgorithm#CBC} that uses Bouncy Castle instead of JCE. The | ||
* algorithm is equivalent to "AES/CBC/PKCS5Padding". | ||
* | ||
* @author William Tran | ||
* | ||
*/ | ||
public class BouncyCastleAesCbcBytesEncryptor extends BouncyCastleAesBytesEncryptor { | ||
|
||
public BouncyCastleAesCbcBytesEncryptor(String password, CharSequence salt) { | ||
super(password, salt); | ||
} | ||
|
||
public BouncyCastleAesCbcBytesEncryptor(String password, CharSequence salt, | ||
BytesKeyGenerator ivGenerator) { | ||
super(password, salt, ivGenerator); | ||
} | ||
|
||
@Override | ||
public byte[] encrypt(byte[] bytes) { | ||
byte[] iv = this.ivGenerator.generateKey(); | ||
|
||
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); | ||
return iv != null ? concatenate(iv, encrypted) : encrypted; | ||
} | ||
|
||
@Override | ||
public byte[] decrypt(byte[] encryptedBytes) { | ||
byte[] iv = subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); | ||
encryptedBytes = subArray(encryptedBytes, this.ivGenerator.getKeyLength(), | ||
encryptedBytes.length); | ||
|
||
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(cipherOutputStream, byteArrayOutputStream, encryptedBytes); | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
...in/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright 2011-2016 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. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
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.engines.AESFastEngine; | ||
import org.bouncycastle.crypto.io.CipherOutputStream; | ||
import org.bouncycastle.crypto.modes.GCMBlockCipher; | ||
import org.bouncycastle.crypto.params.AEADParameters; | ||
import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm; | ||
import org.springframework.security.crypto.keygen.BytesKeyGenerator; | ||
|
||
/** | ||
* An Encryptor equivalent to {@link AesBytesEncryptor} using | ||
* {@link CipherAlgorithm#GCM} that uses Bouncy Castle instead of JCE. The | ||
* algorithm is equivalent to "AES/GCM/NoPadding". | ||
* | ||
* @author William Tran | ||
* | ||
*/ | ||
public class BouncyCastleAesGcmBytesEncryptor extends BouncyCastleAesBytesEncryptor { | ||
|
||
public BouncyCastleAesGcmBytesEncryptor(String password, CharSequence salt) { | ||
super(password, salt); | ||
} | ||
|
||
public BouncyCastleAesGcmBytesEncryptor(String password, CharSequence salt, | ||
BytesKeyGenerator ivGenerator) { | ||
super(password, salt, ivGenerator); | ||
} | ||
|
||
@Override | ||
public byte[] encrypt(byte[] bytes) { | ||
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); | ||
|
||
byte[] encrypted = process(cipherOutputStream, byteArrayOutputStream, bytes); | ||
return iv != null ? concatenate(iv, encrypted) : encrypted; | ||
} | ||
|
||
@Override | ||
public byte[] decrypt(byte[] encryptedBytes) { | ||
byte[] iv = subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength()); | ||
encryptedBytes = subArray(encryptedBytes, this.ivGenerator.getKeyLength(), | ||
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); | ||
|
||
return process(cipherOutputStream, byteArrayOutputStream, encryptedBytes); | ||
} | ||
|
||
} |
150 changes: 150 additions & 0 deletions
150
...springframework/security/crypto/encrypt/BouncyCastleAesBytesEncryptorEquivalencyTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
/* | ||
* Copyright 2011-2016 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. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.springframework.security.crypto.encrypt; | ||
|
||
import java.security.SecureRandom; | ||
import java.util.Random; | ||
import java.util.UUID; | ||
|
||
import org.junit.Assert; | ||
import org.junit.Assume; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.springframework.security.crypto.codec.Hex; | ||
import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm; | ||
import org.springframework.security.crypto.keygen.BytesKeyGenerator; | ||
import org.springframework.security.crypto.keygen.KeyGenerators; | ||
|
||
public class BouncyCastleAesBytesEncryptorEquivalencyTest { | ||
|
||
private byte[] testData; | ||
private String password; | ||
private String salt; | ||
|
||
@Before | ||
public void setup() { | ||
Assume.assumeTrue( | ||
"couldn't create AesBytesEncryptor, is JCE unlimited strength enabled?", | ||
isAes256Available()); | ||
|
||
// 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 | ||
public void bouncyCastleAesCbcWithPredictableIvEquvalent() throws Exception { | ||
BytesEncryptor bcEncryptor = new BouncyCastleAesCbcBytesEncryptor(password, salt, | ||
new PredictableRandomBytesKeyGenerator(16)); | ||
BytesEncryptor jceEncryptor = new AesBytesEncryptor(password, salt, | ||
new PredictableRandomBytesKeyGenerator(16)); | ||
testEquivalence(bcEncryptor, jceEncryptor); | ||
} | ||
|
||
@Test | ||
public void bouncyCastleAesCbcWithSecureIvCompatible() throws Exception { | ||
BytesEncryptor bcEncryptor = new BouncyCastleAesCbcBytesEncryptor(password, salt, | ||
KeyGenerators.secureRandom(16)); | ||
BytesEncryptor jceEncryptor = new AesBytesEncryptor(password, salt, | ||
KeyGenerators.secureRandom(16)); | ||
testCompatibility(bcEncryptor, jceEncryptor); | ||
} | ||
|
||
@Test | ||
public void bouncyCastleAesGcmWithPredictableIvEquvalent() throws Exception { | ||
BytesEncryptor bcEncryptor = new BouncyCastleAesGcmBytesEncryptor(password, salt, | ||
new PredictableRandomBytesKeyGenerator(16)); | ||
BytesEncryptor jceEncryptor = new AesBytesEncryptor(password, salt, | ||
new PredictableRandomBytesKeyGenerator(16), CipherAlgorithm.GCM); | ||
testEquivalence(bcEncryptor, jceEncryptor); | ||
} | ||
|
||
@Test | ||
public void bouncyCastleAesGcmWithSecureIvCompatible() throws Exception { | ||
BytesEncryptor bcEncryptor = new BouncyCastleAesGcmBytesEncryptor(password, salt, | ||
KeyGenerators.secureRandom(16)); | ||
BytesEncryptor jceEncryptor = new AesBytesEncryptor(password, salt, | ||
KeyGenerators.secureRandom(16), CipherAlgorithm.GCM); | ||
testCompatibility(bcEncryptor, jceEncryptor); | ||
} | ||
|
||
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); | ||
} | ||
|
||
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); | ||
} | ||
|
||
private boolean isAes256Available() { | ||
try { | ||
return javax.crypto.Cipher.getMaxAllowedKeyLength("AES") >= 256; | ||
} | ||
catch (Exception e) { | ||
return false; | ||
} | ||
|
||
} | ||
|
||
/** | ||
* A BytesKeyGenerator that always generates the same sequence of values | ||
*/ | ||
private static class PredictableRandomBytesKeyGenerator implements BytesKeyGenerator { | ||
|
||
private final Random random; | ||
|
||
private final int keyLength; | ||
|
||
public PredictableRandomBytesKeyGenerator(int keyLength) { | ||
this.random = new Random(1); | ||
this.keyLength = keyLength; | ||
} | ||
|
||
public int getKeyLength() { | ||
return keyLength; | ||
} | ||
|
||
public byte[] generateKey() { | ||
byte[] bytes = new byte[keyLength]; | ||
random.nextBytes(bytes); | ||
return bytes; | ||
} | ||
|
||
} | ||
|
||
} |
Oops, something went wrong.