I learning the cryptography in Android. Try to encrypt/decrypt user's password. Encryption method is working pretty fine, but decryption method is always getting me an error.
Here is code of the CryptoManager class:
package edu.xfoleks.komodoro.presentation.utils
import android.security.keystore.KeyProperties
import android.security.keystore.KeyProtection
import android.util.Base64
import java.nio.charset.StandardCharsets
import java.security.KeyStore
import java.util.Calendar
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.IvParameterSpec
class CryptoManager {
init {
generateSecretKey()
}
private fun generateSecretKey() {
val keyStore = KeyStore.getInstance(KEY_STORE).apply {
load(null)
}
val keyGenerator = KeyGenerator.getInstance(ALGORITHM).apply {
init(KEY_SIZE)
}
val secretKey = keyGenerator.generateKey()
val startTime = Calendar.getInstance()
val expireTime = Calendar.getInstance().apply {
add(Calendar.YEAR, 2)
}
val entry = KeyStore.SecretKeyEntry(secretKey)
val protectionParameter =
KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setKeyValidityStart(startTime.time)
.setKeyValidityEnd(expireTime.time)
.setBlockModes(BLOCK_MODE)
.setEncryptionPaddings(PADDING)
.build()
keyStore.setEntry(ALIAS, entry, protectionParameter)
}
fun encrypt(plainText: String): String {
val keyStore = KeyStore.getInstance(KEY_STORE).apply { load(null) }
val secretKey = keyStore.getKey(ALIAS, null)
return try {
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val cipherText = Base64.encodeToString(cipher.doFinal(plainText.toByteArray()), Base64.DEFAULT)
val iv = Base64.encodeToString(cipher.iv, Base64.DEFAULT)
"${cipherText}.$iv"
} catch (ex: Exception) {
ex.printStackTrace()
""
}
}
fun decrypt(cipherText: String): String? {
val keyStore = KeyStore.getInstance(KEY_STORE).apply { load(null) }
val secretKey = keyStore.getKey(ALIAS, null)
val array = cipherText.split(".")
val cipherData = Base64.decode(array[0], Base64.DEFAULT)
val iv = Base64.decode(array[1], Base64.DEFAULT)
return try {
val cipher = Cipher.getInstance(TRANSFORMATION)
val spec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
val clearText = cipher.doFinal(cipherData)
val encoded = String(clearText, 0, clearText.size, StandardCharsets.UTF_8)
encoded
} catch (ex: Exception) {
ex.printStackTrace()
null
}
}
companion object {
private const val KEY_STORE = "AndroidKeyStore"
private const val KEY_SIZE = 256
private const val ALIAS = "secret"
private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"
}
}
Classes, which working with decryption/encryption:
class LogInViewModel @Inject constructor(
private val userInteractor: UserInteractor
) : ViewModel() {
private var _user = MutableLiveData<UserInfoItem>()
private fun decryptPassword(password: String): String? {
val cryptoManager = CryptoManager()
return cryptoManager.decrypt(password)
}
fun logIn(userName: String, password: String): Boolean {
val user = UserInfoItem(userName, password)
_user.value = user
val existingUser = userInteractor.getUser(user.toUser()).toUserInfoItem()
val decryptedPassword = decryptPassword(existingUser.password)
return user.userName.equals(existingUser.userName) && user.password.equals(decryptedPassword)
}
}
class RegistrationViewModel @Inject constructor(
private val userInteractor: UserInteractor
) : ViewModel() {
private var _user = MutableLiveData<UserInfoItem>()
private fun encryptPassword(password: String): String {
if (password.isEmpty()) {
throw EmptyPasswordException()
}
val cryptoManager = CryptoManager()
return cryptoManager.encrypt(password)
}
fun registerUser(userName: String, password: String) {
if (userName.isEmpty() || password.isEmpty()) { throw UserNotCreatedException() }
val encryptedPassword = encryptPassword(password)
val user = UserInfoItem(userName, encryptedPassword)
_user.value = user
userInteractor.registerUser(user.toUser())
}
}
The error message:
javax.crypto.BadPaddingException
at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:609)
at javax.crypto.Cipher.doFinal(Cipher.java:2056)
at edu.xfoleks.komodoro.presentation.utils.CryptoManager.decrypt(CryptoManager.kt:83)
at edu.xfoleks.komodoro.presentation.screens.viewmodels.LogInViewModel.decryptPassword(LogInViewModel.kt:22)
at edu.xfoleks.komodoro.presentation.screens.viewmodels.LogInViewModel.logIn(LogInViewModel.kt:29)
at edu.xfoleks.komodoro.presentation.screens.fragments.LogInFragment.onCreateView$lambda$5(LogInFragment.kt:47)
at edu.xfoleks.komodoro.presentation.screens.fragments.LogInFragment.$r8$lambda$NaDtgxEMrBbrOBDtRje0oMjYmGs(Unknown Source:0)
at edu.xfoleks.komodoro.presentation.screens.fragments.LogInFragment$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7506)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1211)
at android.view.View.performClickInternal(View.java:7483)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:29334)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7872)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Caused by: android.security.KeyStoreException: Invalid argument (internal Keystore code: -38 message: In KeystoreOperation::finish
Caused by:
0: In finish: KeyMint::finish failed.
1: Error::Km(ErrorCode(-38))) (public error code: 10 internal Keystore code: -38)
at android.security.KeyStore2.getKeyStoreException(KeyStore2.java:369)
at android.security.KeyStoreOperation.handleExceptions(KeyStoreOperation.java:78)
at android.security.KeyStoreOperation.finish(KeyStoreOperation.java:128)
at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer$MainDataStream.finish(KeyStoreCryptoOperationChunkedStreamer.java:228)
at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:181)
at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:603)
... 20 more
I had tried to save all settings for encryption and then use it for decryption, but it gave me nothing.
For the security of password we need to use hashing, not encryption/decryption. You can look up and implement algorithms like bcrypt, scrypt or Argon2.