0

I am trying to write a couple functions that can encrypt and decrypt text using AES/GCM encryption in conjunction with PBKDF2 key generation. I am transitioning my code from CTR (SIC) encryption, and the MAC check failure is killing me when all else is working.

fun encryptAESBasic(input: String, password: String): String {
    val masterpw = getKey(password)
    val random = SecureRandom()
    val salt = ByteArray(16)
    random.nextBytes(salt)

    val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
    val spec: KeySpec = PBEKeySpec(masterpw.toString().toCharArray(), salt, 100, 256)
    val tmp: SecretKey = factory.generateSecret(spec)
    val cipher = Cipher.getInstance("AES/CTR/NoPadding")

    val iv = ByteArray(16)
    SecureRandom().nextBytes(iv)

    cipher.init(Cipher.ENCRYPT_MODE, tmp, IvParameterSpec(iv))
    val cipherText: ByteArray = cipher.doFinal(input.toByteArray(Charset.forName("UTF-8")))

    val ivstring: String = Base64.encodeToString(iv, Base64.NO_WRAP)
    val saltystring: String = Base64.encodeToString(salt, Base64.NO_WRAP)
    val cipherstring: String = Base64.encodeToString(cipherText, Base64.NO_WRAP)
    val returnstring: String = ivstring + "-" + saltystring + "-" + cipherstring

    return returnstring
}

fun decryptAESBasic(text: String, password: String): String {

    val arr = text.split("-")
    val iv = Base64.decode(arr[0].toByteArray(Charset.forName("UTF-8")), Base64.NO_WRAP)
    val salt = Base64.decode(arr[1].toByteArray(Charset.forName("UTF-8")), Base64.NO_WRAP)
    val data = arr[2].toByteArray(Charset.forName("UTF-8"))

    val masterpw = getKey(password)
    val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
    val spec: KeySpec = PBEKeySpec(masterpw.toString().toCharArray(), salt, 100, 256)
    val tmp: SecretKey = factory.generateSecret(spec)
    val key: ByteArray = tmp.getEncoded()

    val cipher = Cipher.getInstance("AES/CTR/NoPadding")
    cipher.init(Cipher.DECRYPT_MODE, tmp, IvParameterSpec(iv))
    val credential: ByteArray = cipher.doFinal(Base64.decode(data, Base64.NO_WRAP))
    return credential.toString(Charset.forName("UTF-8"))
}

fun getKey(masterPass: String): ByteArray {
    return masterPass.padEnd(32, '.').toByteArray(Charset.forName("UTF-8"))
}

Again, this code works, but I would like to change it from CTR to GCM but every time that I do I am met with a "mac check in GCM failed" error. Any help explaining how/why this is happening would be tremendously appreciated.

E/AndroidRuntime( 6461): Caused by: javax.crypto.AEADBadTagException: mac check in GCM failed
E/AndroidRuntime( 6461):        at java.lang.reflect.Constructor.newInstance0(Native Method)
E/AndroidRuntime( 6461):        at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
E/AndroidRuntime( 6461):        at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$AEADGenericBlockCipher.doFinal(BaseBlockCipher.java:1485)
E/AndroidRuntime( 6461):        at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:1217)
E/AndroidRuntime( 6461):        at javax.crypto.Cipher.doFinal(Cipher.java:2055)
E/AndroidRuntime( 6461):        at design.codeux.autofill_service.FlutterMyAutofillServiceKt.decryptAESBasic(FlutterMyAutofillService.kt:1003)
  • Try to specify the encoding when decoding the password, both during encryption and decryption: `masterpw.toString(Charset.forName("UTF-8")`. Without this specification, neither CTR nor GCM work on my machine. With specification both CTR and GCM work. The different behavior may be due to different default encodings. By the way GCM uses a 12 bytes nonce/IV. – Topaco Aug 02 '21 at 19:08
  • Also, usually the components (salt, IV, ciphertext) are concatenated at byte level and the result is Base64 encoded. A separator is not required because the lengths of salt and IV are known on both sides. But that's more of a convention than a must. – Topaco Aug 02 '21 at 19:13
  • Thank you so much! Your answer on another encryption question is actually what prompted me to change from CTR to GCM – Dysania Aug 02 '21 at 19:28
  • For completeness: [`ByteArray.toString(charset: Charset)`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/to-string.html) performs a decoding using the specified charset, while `ByteArray.toString()` returns the [object's classname@hashcode](https://stackoverflow.com/a/29140403) (e.g. [`B@481e504`) like in Java. – Topaco Aug 02 '21 at 19:44

1 Answers1

0

Instead of using IvParameterSpec in cipher.init(), use GCMParameterSpec

SMD
  • 404
  • 1
  • 4
  • 9