Skip to content

Commit

Permalink
Merge pull request #6 from arixmkii/longer_key_support
Browse files Browse the repository at this point in the history
Disable key length limits if SHA function will be used
  • Loading branch information
t3hnar committed Jul 8, 2019
2 parents 8224045 + 15f9be2 commit 41a3b70
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 17 deletions.
17 changes: 7 additions & 10 deletions src/main/scala/com/evolutiongaming/crypto/Crypto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package com.evolutiongaming.crypto

import java.security.MessageDigest
import java.util.Base64

import javax.crypto.Cipher
import javax.crypto.spec.{IvParameterSpec, SecretKeySpec}

import org.apache.commons.codec.binary.Hex

/**
Expand Down Expand Up @@ -41,7 +41,6 @@ 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 All @@ -50,8 +49,8 @@ object Crypto {
// '2-*' represents an encrypted payload with an IV
// '1-*' represents an encrypted payload without an IV
Option(cipher.getIV) match {
case Some(iv) => s"2-${ Base64.getEncoder.encodeToString(iv ++ encryptedValue) }"
case None => s"1-${ Base64.getEncoder.encodeToString(encryptedValue) }"
case Some(iv) => s"2-${Base64.getEncoder.encodeToString(iv ++ encryptedValue)}"
case None => s"1-${Base64.getEncoder.encodeToString(encryptedValue)}" // will never fall here as CTR requires IV
}
}

Expand All @@ -72,7 +71,6 @@ 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 @@ -85,7 +83,7 @@ object Crypto {
decryptAESVersion1(data, privateKey)
case "2" =>
decryptAESVersion2(data, privateKey)
case _ =>
case _ =>
throw new RuntimeException("Unknown version")
}
}
Expand All @@ -102,6 +100,7 @@ object Crypto {

/** Backward compatible AES ECB mode decryption support. */
private def decryptAESVersion0(value: String, privateKey: String): String = {
validateAesKeyLength(privateKey)
val raw = privateKey.substring(0, AesKeyBytesMaxSize).getBytes("utf-8")
val skeySpec = new SecretKeySpec(raw, "AES")
val cipher = getCipherWithConfiguredProvider("AES")
Expand All @@ -113,7 +112,7 @@ object Crypto {
private def decryptAESVersion1(value: String, privateKey: String): String = {
val data = Base64.getDecoder.decode(value)
val skeySpec = secretKeyWithSha256(privateKey, "AES")
val cipher = getCipherWithConfiguredProvider(aesTransformation)
val cipher = getCipherWithConfiguredProvider("AES")
cipher.init(Cipher.DECRYPT_MODE, skeySpec)
new String(cipher.doFinal(data), "utf-8")
}
Expand All @@ -139,9 +138,7 @@ 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") AesKeyBytesMaxSize
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
85 changes: 78 additions & 7 deletions src/test/scala/com/evolutiongaming/crypto/CryptoSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,96 @@ class CryptoSpec extends FlatSpec with Matchers {
original shouldEqual decrypted
}

it should "not give same result when encryption and decryption keys are different" in {
it should "generate different encrypted strings on every run and should decrypt both" in {
val secret = "test secret"
val original = "test data please ignore"

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

encrypted should not equal encrypted2
original shouldEqual decrypted
original shouldEqual decrypted2
}

it should "decrypt data encrypted with same AES key (long)" in {
val original = "test data please ignore"
val key = "1234567890123456now_it_became_too_long"

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

original shouldEqual decrypted
}

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

val encrypted = Crypto.encryptAES(original, key)
val decrypted = Crypto.decryptAES(encrypted, otherKey)

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")
)
it should "not give same result when encryption and decryption keys are different (long and substring)" in {
val original = "test data please ignore"
val key = "1234567890123456now_it_became_too_long"

val encrypted = Crypto.encryptAES(original, key)
val decrypted = Crypto.decryptAES(encrypted, key.take(16))

original should not equal decrypted
}

// backward compatibility tests
it should "decrypt with key up to 16 bytes" in {
val key = "1234567890123456"
val original = "secretvalue"

val encrypted = "3d458dc2fe2cd11b9e42b2fee8b51f33"
Crypto.decryptAES(encrypted, key) shouldEqual original
}

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

it should "decrypt with key up to 16 bytes (v1)" in {
val key = "1234567890123456"
val original = "secretvalue"

val encrypted = "1-BNW/+juEl+2PQunvhx44/g=="
Crypto.decryptAES(encrypted, key) shouldEqual original
}

it should "decrypt with key bigger than 16 bytes (v1)" in {
val key = "1234567890123456" + "now_it_became_too_long"
val original = "secretvalue"

val encrypted = "1-1oz9Og7x4cOGLLstyGrD/Q=="
Crypto.decryptAES(encrypted, key) shouldEqual original
}

it should "decrypt with key up to 16 bytes (v2)" in {
val key = "1234567890123456"
val original = "secretvalue"

val encrypted = "2-02LBITbJAb8Mopvgtrd8p3ORc/ipArp6/ozd"
Crypto.decryptAES(encrypted, key) shouldEqual original
}

it should "decrypt with key bigger than 16 bytes (v2)" in {
val key = "1234567890123456" + "now_it_became_too_long"
val original = "secretvalue"

val encrypted = "2-aNJt/st3SsxhFYQ/ybgpM9vudiHQjUf1JqJD"
Crypto.decryptAES(encrypted, key) shouldEqual original
}
// enb of backward compatibility tests
}

0 comments on commit 41a3b70

Please sign in to comment.