I am trying to get AES symmetric encryption working in my android app and am having a hard time getting it to work.
Sample code below
Activity
override fun onCreate() {
...
val key = KeyGenerator().generateSymemetricKey(ANDROID_KEYSTORE, "token", AES, PKCS7, CBC, PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
val encryptionPair = EncryptionHelper.encrypt(AES, "Testing", key)
encryptionPair?.let {
val decryptedString = EncryptionHelper.decrypt(AES, it.first, key, it.second)
Timber.d(decryptedString)
}
class KeyGenerator {
fun generateSymemetricKey(keystoreName: String, alias: String, algorithm: String, padding: String, blockMode: String, purposes: Int): Key {
val keystore = KeyStore.getInstance(keystoreName)
keystore.load(null)
if (!keystore.containsAlias(alias)) {
val keyGenerator = KeyGenerator.getInstance(algorithm, keystoreName)
val keyGenParameterSpec = KeyGenParameterSpec.Builder(alias, purposes)
.setBlockModes(blockMode)
.setEncryptionPaddings(padding)
.build()
keyGenerator.init(keyGenParameterSpec)
return keyGenerator.generateKey()
} else {
return keystore.getKey(alias, null) as Key
}
}
}
object EncryptionHelper {
fun encrypt(algorithm: String, text: String, key: Key): Pair<ByteArray, ByteArray>? = try {
val cipher = Cipher.getInstance(algorithm)
cipher.init(Cipher.ENCRYPT_MODE, key)
val cipherText = cipher.doFinal(text.toByteArray())
Pair(cipherText, cipher.iv)
} catch (exception: GeneralSecurityException) {
null
}
fun decrypt(algorithm: String, cipherText: ByteArray, key: Key, iv: ByteArray): String? = try {
val cipher = Cipher.getInstance(algorithm)
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
val plainText = cipher.doFinal(cipherText).toString()
plainText
} catch (exception: GeneralSecurityException) {
null
}
}
In the call to cipher.init
in encrypt
, BouncyCastle throws the following exception
2020-01-09 17:05:13.743 24911-24911/com.smartrent.alloytile E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.smartrent.alloytile, PID: 24911
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.smartrent.alloytile/com.smartrent.alloytile.MainActivity}: java.lang.NullPointerException: Attempt to get length of null array
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3092)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3235)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:6986)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
Caused by: java.lang.NullPointerException: Attempt to get length of null array
at com.android.org.bouncycastle.crypto.params.KeyParameter.<init>(KeyParameter.java:13)
at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:692)
at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineInit(BaseBlockCipher.java:1076)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2984)
at javax.crypto.Cipher.tryCombinations(Cipher.java:2891)
at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2796)
at javax.crypto.Cipher.chooseProvider(Cipher.java:773)
at javax.crypto.Cipher.init(Cipher.java:1143)
at javax.crypto.Cipher.init(Cipher.java:1084)
at com.smartrent.crypto.EncryptionHelper.encrypt(EncryptionHelper.kt:17)
at com.smartrent.alloytile.MainActivity.onCreate(MainActivity.kt:62)
at android.app.Activity.performCreate(Activity.java:7326)
at android.app.Activity.performCreate(Activity.java:7317)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3072)
In debugging the error I found that key.encoded
is null which is what is ultimately causing the problem. However, this is the way all tutorials I've found have handled symmetric encryption and nobody seemed to have this problem.
In the posted code a key is generated in the Android keystore system for encryption with AES using CBC mode and PKCS7 padding. For this constellation, "AES/CBC/PKCS7Padding"
must be passed as argument to Cipher.getInstance()
, as specified in this list of ciphers supported by the Android keystore system.