Browse files

Crypto improvemets

* Added comprehensive docs on the algorithms used by the Crypto library,
  and consequences of using those algorithms.
* Made the JSSE provider and AES encryption transformation algorithm
  configurable.
  • Loading branch information...
1 parent fdf3ec8 commit e8601b885da7a2203190468b7686a0c2dbfa9156 @jroper jroper committed Jul 22, 2013
View
103 framework/src/play-java/src/main/java/play/libs/Crypto.java
@@ -1,54 +1,127 @@
package play.libs;
+/**
+ * Cryptographic utilities.
+ * <p/>
+ * These utilities are intended as a convenience, however it is important to read each methods documentation and
+ * understand the concepts behind encryption to use this class properly. Safe encryption is hard, and there is no
+ * substitute for an adequate understanding of cryptography. These methods will not be suitable for all encryption
+ * needs.
+ *
+ * For more information about cryptography, we recommend reading the OWASP Cryptographic Storage Cheatsheet:
+ *
+ * https://www.owasp.org/index.php/Cryptographic_Storage_Cheat_Sheet
+ */
public class Crypto {
/**
* Signs the given String with HMAC-SHA1 using the given key.
+ * <p/>
+ * By default this uses the platform default JSSE provider. This can be overridden by defining
+ * <code>application.crypto.provider</code> in <codeapplication.conf</code>.
+ *
+ * @param message The message to sign.
+ * @param key The private key to sign with.
+ * @return A hexadecimal encoded signature.
*/
public static String sign(String message, byte[] key) {
return play.api.libs.Crypto.sign(message, key);
}
/**
- * Signs the given String with HMAC-SHA1 using the application secret key.
+ * Signs the given String with HMAC-SHA1 using the application's secret key.
+ * <p/>
+ * By default this uses the platform default JSSE provider. This can be overridden by defining
+ * <code>application.crypto.provider</code> in <codeapplication.conf</code>.
+ *
+ * @param message The message to sign.
+ * @return A hexadecimal encoded signature.
*/
public static String sign(String message) {
return play.api.libs.Crypto.sign(message);
}
/**
- * Encrypt a String with the AES encryption standard using the application secret
- * @param value The String to encrypt
- * @return An hexadecimal encrypted string
+ * Encrypt a String with the AES encryption standard using the application's secret key.
+ * <p/>
+ * The provider used is by default this uses the platform default JSSE provider. This can be overridden by defining
+ * <code>application.crypto.provider</code> in <code>application.conf</code>.
+ * <p/>
+ * The transformation algorithm used is the provider specific implementation of the <code>AES</code> name. On
+ * Oracles JDK, this is <code>AES/ECB/PKCS5Padding</code>. This algorithm is suitable for small amounts of data,
+ * typically less than 32 bytes, hence is useful for encrypting credit card numbers, passwords etc. For larger
+ * blocks of data, this algorithm may expose patterns and be vulnerable to repeat attacks.
+ * <p/>
+ * The transformation algorithm can be configured by defining <code>application.crypto.aes.transformation</code> in
+ * <code>application.conf</code>. Although any cipher transformation algorithm can be selected here, the secret key
+ * spec used is always AES, so only AES transformation algorithms will work.
+ *
+ * @param value The String to encrypt.
+ * @return An hexadecimal encrypted string.
*/
public static String encryptAES(String value) {
return play.api.libs.Crypto.encryptAES(value);
}
/**
- * Encrypt a String with the AES encryption standard. Private key must have a length of 16 bytes
- * @param value The String to encrypt
- * @param privateKey The key used to encrypt
- * @return An hexadecimal encrypted string
+ * Encrypt a String with the AES encryption standard and the supplied private key.
+ * <p/>
+ * The private key must have a length of 16 bytes.
+ * <p/>
+ * The provider used is by default this uses the platform default JSSE provider. This can be overridden by defining
+ * <code>application.crypto.provider</code> in <code>application.conf</code>.
+ * <p/>
+ * The transformation algorithm used is the provider specific implementation of the <code>AES</code> name. On
+ * Oracles JDK, this is <code>AES/ECB/PKCS5Padding</code>. This algorithm is suitable for small amounts of data,
+ * typically less than 32bytes, hence is useful for encrypting credit card numbers, passwords etc. For larger
+ * blocks of data, this algorithm may expose patterns and be vulnerable to repeat attacks.
+ * <p/>
+ * The transformation algorithm can be configured by defining <code>application.crypto.aes.transformation</code> in
+ * <code>application.conf</code>. Although any cipher transformation algorithm can be selected here, the secret key
+ * spec used is always AES, so only AES transformation algorithms will work.
+ *
+ * @param value The String to encrypt.
+ * @param privateKey The key used to encrypt.
+ * @return An hexadecimal encrypted string.
*/
public static String encryptAES(String value, String privateKey) {
return play.api.libs.Crypto.encryptAES(value, privateKey);
}
/**
- * Decrypt a String with the AES encryption standard using the application secret
- * @param value An hexadecimal encrypted string
- * @return The decrypted String
+ * Decrypt a String with the AES encryption standard using the application's secret key.
+ * <p/>
+ * The provider used is by default this uses the platform default JSSE provider. This can be overridden by defining
+ * <code>application.crypto.provider</code> in <code>application.conf</code>.
+ * <p/>
+ * The transformation used is by default <code>AES/ECB/PKCS5Padding</code>. It can be configured by defining
+ * <code>application.crypto.aes.transformation</code> in <code>application.conf</code>. Although any cipher
+ * transformation algorithm can be selected here, the secret key spec used is always AES, so only AES transformation
+ * algorithms will work.
+ *
+ * @param value An hexadecimal encrypted string.
+ * @return The decrypted String.
*/
public static String decryptAES(String value) {
return play.api.libs.Crypto.decryptAES(value);
}
/**
- * Decrypt a String with the AES encryption standard. Private key must have a length of 16 bytes
- * @param value An hexadecimal encrypted string
- * @param privateKey The key used to encrypt
- * @return The decrypted String
+ * Decrypt a String with the AES encryption standard.
+ * <p/>
+ * The private key must have a length of 16 bytes.
+ * <p/>
+ * The provider used is by default this uses the platform default JSSE provider. This can be overridden by defining
+ * <code>application.crypto.provider</code> in <code>application.conf</code>.
+ * <p/>
+ * The transformation used is by default <code>AES/ECB/PKCS5Padding</code>. It can be configured by defining
+ * <code>application.crypto.aes.transformation</code> in <code>application.conf</code>. Although any cipher
+ * transformation algorithm can be selected here, the secret key spec used is always AES, so only AES transformation
+ * algorithms will work.
+ *
+ * @param value An hexadecimal encrypted string.
+ * @param privateKey The key used to encrypt.
+ * @return The decrypted String.
*/
public static String decryptAES(String value, String privateKey) {
return play.api.libs.Crypto.decryptAES(value, privateKey);
View
110 framework/src/play/src/main/scala/play/api/libs/Crypto.scala
@@ -8,22 +8,50 @@ import play.api.PlayException
/**
* Cryptographic utilities.
+ *
+ * These utilities are intended as a convenience, however it is important to read each methods documentation and
+ * understand the concepts behind encryption to use this class properly. Safe encryption is hard, and there is no
+ * substitute for an adequate understanding of cryptography. These methods will not be suitable for all encryption
+ * needs.
+ *
+ * For more information about cryptography, we recommend reading the OWASP Cryptographic Storage Cheatsheet:
+ *
+ * https://www.owasp.org/index.php/Cryptographic_Storage_Cheat_Sheet
*/
object Crypto {
- private def secret: Option[String] = Play.maybeApplication.flatMap(_.configuration.getString("application.secret"))
+ private def getConfig(key: String) = Play.maybeApplication.flatMap(_.configuration.getString(key))
+
+ private def secret: Option[String] = getConfig("application.secret")
+
+ private lazy val provider: Option[String] = getConfig("application.crypto.provider")
+
+ private lazy val transformation: String = getConfig("application.crypto.aes.transformation").getOrElse("AES")
/**
* Signs the given String with HMAC-SHA1 using the given key.
+ *
+ * By default this uses the platform default JSSE provider. This can be overridden by defining
+ * `application.crypto.provider` in `application.conf`.
+ *
+ * @param message The message to sign.
+ * @param key The private key to sign with.
+ * @return A hexadecimal encoded signature.
*/
def sign(message: String, key: Array[Byte]): String = {
- val mac = Mac.getInstance("HmacSHA1")
+ val mac = provider.map(p => Mac.getInstance("HmacSHA1", p)).getOrElse(Mac.getInstance("HmacSHA1"))
mac.init(new SecretKeySpec(key, "HmacSHA1"))
Codecs.toHexString(mac.doFinal(message.getBytes("utf-8")))
}
/**
* Signs the given String with HMAC-SHA1 using the application’s secret key.
+ *
+ * By default this uses the platform default JSSE provider. This can be overridden by defining
+ * `application.crypto.provider` in `application.conf`.
+ *
+ * @param message The message to sign.
+ * @return A hexadecimal encoded signature.
*/
def sign(message: String): String = {
secret.map(secret => sign(message, secret.getBytes("utf-8"))).getOrElse {
@@ -32,9 +60,22 @@ object Crypto {
}
/**
- * Encrypt a String with the AES encryption standard using the application secret
- * @param value The String to encrypt
- * @return An hexadecimal encrypted string
+ * Encrypt a String with the AES encryption standard using the application's secret key.
+ *
+ * The provider used is by default this uses the platform default JSSE provider. This can be overridden by defining
+ * `application.crypto.provider` in `application.conf`.
+ *
+ * The transformation algorithm used is the provider specific implementation of the `AES` name. On Oracles JDK,
+ * this is `AES/ECB/PKCS5Padding`. This algorithm is suitable for small amounts of data, typically less than 32
+ * bytes, hence is useful for encrypting credit card numbers, passwords etc. For larger blocks of data, this
+ * algorithm may expose patterns and be vulnerable to repeat attacks.
+ *
+ * The transformation algorithm can be configured by defining `application.crypto.aes.transformation` in
+ * `application.conf`. Although any cipher transformation algorithm can be selected here, the secret key spec used
+ * is always AES, so only AES transformation algorithms will work.
+ *
+ * @param value The String to encrypt.
+ * @return An hexadecimal encrypted string.
*/
def encryptAES(value: String): String = {
secret.map(secret => encryptAES(value, secret.substring(0, 16))).getOrElse {
@@ -43,23 +84,46 @@ object Crypto {
}
/**
- * Encrypt a String with the AES encryption standard. Private key must have a length of 16 bytes
- * @param value The String to encrypt
- * @param privateKey The key used to encrypt
- * @return An hexadecimal encrypted string
+ * Encrypt a String with the AES encryption standard and the supplied private key.
+ *
+ * The private key must have a length of 16 bytes.
+ *
+ * The provider used is by default this uses the platform default JSSE provider. This can be overridden by defining
+ * `application.crypto.provider` in `application.conf`.
+ *
+ * The transformation algorithm used is the provider specific implementation of the `AES` name. On Oracles JDK,
+ * this is `AES/ECB/PKCS5Padding`. This algorithm is suitable for small amounts of data, typically less than 32
+ * bytes, hence is useful for encrypting credit card numbers, passwords etc. For larger blocks of data, this
+ * algorithm may expose patterns and be vulnerable to repeat attacks.
+ *
+ * The transformation algorithm can be configured by defining `application.crypto.aes.transformation` in
+ * `application.conf`. Although any cipher transformation algorithm can be selected here, the secret key spec used
+ * is always AES, so only AES transformation algorithms will work.
+ *
+ * @param value The String to encrypt.
+ * @param privateKey The key used to encrypt.
+ * @return An hexadecimal encrypted string.
*/
def encryptAES(value: String, privateKey: String): String = {
val raw = privateKey.getBytes("utf-8")
val skeySpec = new SecretKeySpec(raw, "AES")
- val cipher = Cipher.getInstance("AES")
+ val cipher = provider.map(p => Cipher.getInstance(transformation, p)).getOrElse(Cipher.getInstance(transformation))
cipher.init(Cipher.ENCRYPT_MODE, skeySpec)
Codecs.toHexString(cipher.doFinal(value.getBytes("utf-8")))
}
/**
- * Decrypt a String with the AES encryption standard using the application secret
- * @param value An hexadecimal encrypted string
- * @return The decrypted String
+ * Decrypt a String with the AES encryption standard using the application's secret key.
+ *
+ * The provider used is by default this uses the platform default JSSE provider. This can be overridden by defining
+ * `application.crypto.provider` in `application.conf`.
+ *
+ * The transformation used is by default `AES/ECB/PKCS5Padding`. It can be configured by defining
+ * `application.crypto.aes.transformation` in `application.conf`. Although any cipher transformation algorithm can
+ * be selected here, the secret key spec used is always AES, so only AES transformation algorithms will work.
+ *
+ * @param value An hexadecimal encrypted string.
+ * @return The decrypted String.
*/
def decryptAES(value: String): String = {
secret.map(secret => decryptAES(value, secret.substring(0, 16))).getOrElse {
@@ -68,15 +132,25 @@ object Crypto {
}
/**
- * Decrypt a String with the AES encryption standard. Private key must have a length of 16 bytes
- * @param value An hexadecimal encrypted string
- * @param privateKey The key used to encrypt
- * @return The decrypted String
+ * Decrypt a String with the AES encryption standard.
+ *
+ * The private key must have a length of 16 bytes.
+ *
+ * The provider used is by default this uses the platform default JSSE provider. This can be overridden by defining
+ * `application.crypto.provider` in `application.conf`.
+ *
+ * The transformation used is by default `AES/ECB/PKCS5Padding`. It can be configured by defining
+ * `application.crypto.aes.transformation` in `application.conf`. Although any cipher transformation algorithm can
+ * be selected here, the secret key spec used is always AES, so only AES transformation algorithms will work.
+ *
+ * @param value An hexadecimal encrypted string.
+ * @param privateKey The key used to encrypt.
+ * @return The decrypted String.
*/
def decryptAES(value: String, privateKey: String): String = {
val raw = privateKey.getBytes("utf-8")
val skeySpec = new SecretKeySpec(raw, "AES")
- val cipher = Cipher.getInstance("AES")
+ val cipher = provider.map(p => Cipher.getInstance(transformation, p)).getOrElse(Cipher.getInstance(transformation))
cipher.init(Cipher.DECRYPT_MODE, skeySpec)
new String(cipher.doFinal(Codecs.hexStringToByte(value)))
}

0 comments on commit e8601b8

Please sign in to comment.