diff --git a/crypto/pom.xml b/crypto/pom.xml
index 1df7080906..837a9a2c95 100644
--- a/crypto/pom.xml
+++ b/crypto/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.springframework.security
spring-security-crypto
- 4.0.1.CI-SNAPSHOT
+ 4.0.2.CI-SNAPSHOT
spring-security-crypto
spring-security-crypto
http://spring.io/spring-security
diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java
index 19c4b47f24..0bc7fe6c89 100644
--- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java
+++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java
@@ -22,19 +22,24 @@ import static org.springframework.security.crypto.encrypt.CipherUtils.newSecretK
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
+import java.security.spec.AlgorithmParameterSpec;
+
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
+import org.springframework.security.crypto.keygen.KeyGenerators;
/**
* Encryptor that uses 256-bit AES encryption.
*
* @author Keith Donald
+ * @author Dave Syer
*/
final class AesBytesEncryptor implements BytesEncryptor {
@@ -46,38 +51,83 @@ final class AesBytesEncryptor implements BytesEncryptor {
private final BytesKeyGenerator ivGenerator;
+ private CipherAlgorithm alg;
+
+ private static final String AES_CBC_ALGORITHM = "AES/CBC/PKCS5Padding";
+
+ private static final String AES_GCM_ALGORITHM = "AES/GCM/NoPadding";
+
+ public enum CipherAlgorithm {
+
+ CBC(AES_CBC_ALGORITHM, NULL_IV_GENERATOR), GCM(AES_GCM_ALGORITHM, KeyGenerators
+ .secureRandom(16));
+
+ private BytesKeyGenerator ivGenerator;
+ private String name;
+
+ private CipherAlgorithm(String name, BytesKeyGenerator ivGenerator) {
+ this.name = name;
+ this.ivGenerator = ivGenerator;
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+
+ public AlgorithmParameterSpec getParameterSpec(byte[] iv) {
+ return this == CBC ? new IvParameterSpec(iv) : new GCMParameterSpec(128, iv);
+ }
+
+ public Cipher createCipher() {
+ return newCipher(this.toString());
+ }
+
+ public BytesKeyGenerator defaultIvGenerator() {
+ return this.ivGenerator;
+ }
+ }
+
public AesBytesEncryptor(String password, CharSequence salt) {
this(password, salt, null);
}
public AesBytesEncryptor(String password, CharSequence salt,
BytesKeyGenerator ivGenerator) {
+ this(password, salt, ivGenerator, CipherAlgorithm.CBC);
+ }
+
+ public AesBytesEncryptor(String password, CharSequence salt,
+ BytesKeyGenerator ivGenerator, CipherAlgorithm alg) {
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(salt),
1024, 256);
SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec);
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
- encryptor = newCipher(AES_ALGORITHM);
- decryptor = newCipher(AES_ALGORITHM);
- this.ivGenerator = ivGenerator != null ? ivGenerator : NULL_IV_GENERATOR;
+ this.alg = alg;
+ this.encryptor = alg.createCipher();
+ this.decryptor = alg.createCipher();
+ this.ivGenerator = ivGenerator != null ? ivGenerator : alg.defaultIvGenerator();
}
public byte[] encrypt(byte[] bytes) {
- synchronized (encryptor) {
- byte[] iv = ivGenerator.generateKey();
- initCipher(encryptor, Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
- byte[] encrypted = doFinal(encryptor, bytes);
- return ivGenerator != NULL_IV_GENERATOR ? concatenate(iv, encrypted)
+ synchronized (this.encryptor) {
+ byte[] iv = this.ivGenerator.generateKey();
+ initCipher(this.encryptor, Cipher.ENCRYPT_MODE, this.secretKey,
+ this.alg.getParameterSpec(iv));
+ byte[] encrypted = doFinal(this.encryptor, bytes);
+ return this.ivGenerator != NULL_IV_GENERATOR ? concatenate(iv, encrypted)
: encrypted;
}
}
public byte[] decrypt(byte[] encryptedBytes) {
- synchronized (decryptor) {
+ synchronized (this.decryptor) {
byte[] iv = iv(encryptedBytes);
- initCipher(decryptor, Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
+ initCipher(this.decryptor, Cipher.DECRYPT_MODE, this.secretKey,
+ this.alg.getParameterSpec(iv));
return doFinal(
- decryptor,
- ivGenerator != NULL_IV_GENERATOR ? encrypted(encryptedBytes,
+ this.decryptor,
+ this.ivGenerator != NULL_IV_GENERATOR ? encrypted(encryptedBytes,
iv.length) : encryptedBytes);
}
}
@@ -85,26 +135,24 @@ final class AesBytesEncryptor implements BytesEncryptor {
// internal helpers
private byte[] iv(byte[] encrypted) {
- return ivGenerator != NULL_IV_GENERATOR ? subArray(encrypted, 0,
- ivGenerator.getKeyLength()) : NULL_IV_GENERATOR.generateKey();
+ return this.ivGenerator != NULL_IV_GENERATOR ? subArray(encrypted, 0,
+ this.ivGenerator.getKeyLength()) : NULL_IV_GENERATOR.generateKey();
}
private byte[] encrypted(byte[] encryptedBytes, int ivLength) {
return subArray(encryptedBytes, ivLength, encryptedBytes.length);
}
- private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
-
private static final BytesKeyGenerator NULL_IV_GENERATOR = new BytesKeyGenerator() {
private final byte[] VALUE = new byte[16];
public int getKeyLength() {
- return VALUE.length;
+ return this.VALUE.length;
}
public byte[] generateKey() {
- return VALUE;
+ return this.VALUE;
}
};
diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
index 09570b31ba..0e78e2c902 100644
--- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
+++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
@@ -15,6 +15,7 @@
*/
package org.springframework.security.crypto.encrypt;
+import org.springframework.security.crypto.encrypt.AesBytesEncryptor.CipherAlgorithm;
import org.springframework.security.crypto.keygen.KeyGenerators;
/**
@@ -25,6 +26,28 @@ import org.springframework.security.crypto.keygen.KeyGenerators;
*/
public class Encryptors {
+ /**
+ * Creates a standard password-based bytes encryptor using 256 bit AES encryption with
+ * Galois Counter Mode (GCM). Derives the secret key using PKCS #5's PBKDF2
+ * (Password-Based Key Derivation Function #2). Salts the password to prevent
+ * dictionary attacks against the key. The provided salt is expected to be
+ * hex-encoded; it should be random and at least 8 bytes in length. Also applies a
+ * random 16 byte initialization vector to ensure each encrypted message will be
+ * unique. Requires Java 6.
+ *
+ * @param password the password used to generate the encryptor's secret key; should
+ * not be shared
+ * @param salt a hex-encoded, random, site-global salt value to use to generate the
+ * key
+ *
+ * @see #standard(CharSequence, CharSequence) which uses the slightly weaker CBC mode
+ * (instead of GCM)
+ */
+ public static BytesEncryptor stronger(CharSequence password, CharSequence salt) {
+ return new AesBytesEncryptor(password.toString(), salt,
+ KeyGenerators.secureRandom(16), CipherAlgorithm.GCM);
+ }
+
/**
* Creates a standard password-based bytes encryptor using 256 bit AES encryption.
* Derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation
@@ -44,11 +67,24 @@ public class Encryptors {
}
/**
- * Creates a text encryptor that uses standard password-based encryption. Encrypted
+ * Creates a text encryptor that uses "stronger" password-based encryption. Encrypted
* text is hex-encoded.
*
* @param password the password used to generate the encryptor's secret key; should
* not be shared
+ * @see Encryptors#stronger(CharSequence, CharSequence)
+ */
+ public static TextEncryptor delux(CharSequence password, CharSequence salt) {
+ return new HexEncodingTextEncryptor(stronger(password, salt));
+ }
+
+ /**
+ * Creates a text encryptor that uses "standard" password-based encryption. Encrypted
+ * text is hex-encoded.
+ *
+ * @param password the password used to generate the encryptor's secret key; should
+ * not be shared
+ * @see Encryptors#standard(CharSequence, CharSequence)
*/
public static TextEncryptor text(CharSequence password, CharSequence salt) {
return new HexEncodingTextEncryptor(standard(password, salt));
diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java
index 81c1c20ab5..9af20d5f69 100644
--- a/crypto/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java
+++ b/crypto/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java
@@ -9,6 +9,17 @@ import org.junit.Test;
public class EncryptorsTests {
+ @Test
+ public void stronger() throws Exception {
+ BytesEncryptor encryptor = Encryptors.stronger("password", "5c0744940b5c369b");
+ byte[] result = encryptor.encrypt("text".getBytes("UTF-8"));
+ assertNotNull(result);
+ assertFalse(new String(result).equals("text"));
+ assertEquals("text", new String(encryptor.decrypt(result)));
+ assertFalse(new String(result).equals(new String(encryptor.encrypt("text"
+ .getBytes()))));
+ }
+
@Test
public void standard() throws Exception {
BytesEncryptor encryptor = Encryptors.standard("password", "5c0744940b5c369b");
@@ -20,6 +31,16 @@ public class EncryptorsTests {
.getBytes()))));
}
+ @Test
+ public void preferred() {
+ TextEncryptor encryptor = Encryptors.delux("password", "5c0744940b5c369b");
+ String result = encryptor.encrypt("text");
+ assertNotNull(result);
+ assertFalse(result.equals("text"));
+ assertEquals("text", encryptor.decrypt(result));
+ assertFalse(result.equals(encryptor.encrypt("text")));
+ }
+
@Test
public void text() {
TextEncryptor encryptor = Encryptors.text("password", "5c0744940b5c369b");