Skip to content

Commit

Permalink
Merge pull request #4 from fedgehog/fail_fast_on_aes_key_length_issues
Browse files Browse the repository at this point in the history
Fail en-/decryption if key is too big
  • Loading branch information
t3hnar authored Aug 31, 2017
2 parents 2447d14 + 4031876 commit 554ccd1
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 8 deletions.
16 changes: 14 additions & 2 deletions src/main/scala/com/evolutiongaming/crypto/Crypto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import org.apache.commons.codec.binary.Hex
*/
object Crypto {
val aesTransformation: String = "AES/CTR/NoPadding"
val AesKeyBytesMaxSize: Int = 16

class AesKeyTooLong extends Exception(
s"AES key should have size no more than $AesKeyBytesMaxSize bytes"
)

/**
* Encrypt a String with the AES encryption standard and the supplied private key.
Expand All @@ -36,6 +41,7 @@ object Crypto {
* @return A Base64 encrypted string.
*/
def encryptAES(value: String, privateKey: String): String = {
validateAesKeyLength(privateKey)
val skeySpec = secretKeyWithSha256(privateKey, "AES")
val cipher = getCipherWithConfiguredProvider(aesTransformation)
cipher.init(Cipher.ENCRYPT_MODE, skeySpec)
Expand Down Expand Up @@ -66,6 +72,7 @@ object Crypto {
* @return The decrypted String.
*/
def decryptAES(value: String, privateKey: String): String = {
validateAesKeyLength(privateKey)
val seperator = "-"
val sepIndex = value.indexOf(seperator)
if (sepIndex < 0) {
Expand All @@ -84,6 +91,9 @@ object Crypto {
}
}

private def validateAesKeyLength(key: String): Unit =
require(key.getBytes("utf-8").length <= AesKeyBytesMaxSize, throw new AesKeyTooLong)

/**
* Transform an hexadecimal String to a byte array.
* From https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/play/api/libs/Codecs.scala
Expand All @@ -92,7 +102,7 @@ object Crypto {

/** Backward compatible AES ECB mode decryption support. */
private def decryptAESVersion0(value: String, privateKey: String): String = {
val raw = privateKey.substring(0, 16).getBytes("utf-8")
val raw = privateKey.substring(0, AesKeyBytesMaxSize).getBytes("utf-8")
val skeySpec = new SecretKeySpec(raw, "AES")
val cipher = getCipherWithConfiguredProvider("AES")
cipher.init(Cipher.DECRYPT_MODE, skeySpec)
Expand Down Expand Up @@ -129,7 +139,9 @@ object Crypto {
// max allowed length in bits / (8 bits to a byte)
// For AES we hardcode keylength to 128bit minimum to not depend on environment security policy settings:
// it may vary between 128 and 256 bits which can yield different encryption keys if we don't
val maxAllowedKeyLength = if (algorithm == "AES") 16 else Cipher.getMaxAllowedKeyLength(algorithm) / 8
val maxAllowedKeyLength =
if (algorithm == "AES") AesKeyBytesMaxSize
else Cipher.getMaxAllowedKeyLength(algorithm) / 8
val raw = messageDigest.digest().slice(0, maxAllowedKeyLength)
new SecretKeySpec(raw, algorithm)
}
Expand Down
33 changes: 27 additions & 6 deletions src/test/scala/com/evolutiongaming/crypto/CryptoSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,37 @@ package com.evolutiongaming.crypto
import org.scalatest.{FlatSpec, Matchers}

class CryptoSpec extends FlatSpec with Matchers {
"Crypto" should "encrypt and decrypt AES" in {
behavior of "Crypto"

it should "decrypt data encrypted with same AES key" in {
val secret = "test secret"
val data = "test data please ignore"
val original = "test data please ignore"

val encrypted = Crypto.encryptAES(data, secret)
val encrypted = Crypto.encryptAES(original, secret)
val decrypted = Crypto.decryptAES(encrypted, secret)

data shouldEqual decrypted
original shouldEqual decrypted
}

it should "not give same result when encryption and decryption keys are different" in {
val secret = "test secret"
val original = "test data please ignore"

val encrypted = Crypto.encryptAES(original, secret)
val decrypted = Crypto.decryptAES(encrypted, "wrongSecret12345")

original should not equal decrypted
}

it should "not encrypt with key bigger than 16 bytes" in {
assertThrows[Crypto.AesKeyTooLong](
Crypto.encryptAES("", "1234567890123456now_it_became_too_long")
)
}

val wrongDecrypted = Crypto.decryptAES(encrypted, "wrongSecret12345")
data should not equal wrongDecrypted
it should "not decrypt with key bigger than 16 bytes" in {
assertThrows[Crypto.AesKeyTooLong](
Crypto.decryptAES("", "1234567890123456now_it_became_too_long")
)
}
}

0 comments on commit 554ccd1

Please sign in to comment.