Skip to content

Instantly share code, notes, and snippets.

@abalta
Last active July 20, 2023 00:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abalta/8509bdcccc11924529bc595f399b7bcf to your computer and use it in GitHub Desktop.
Save abalta/8509bdcccc11924529bc595f399b7bcf to your computer and use it in GitHub Desktop.
AndroidKeystore Kotlin Usage
package com.marsathletic.android.macfit.app.base
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.security.*
import java.security.cert.CertificateException
import javax.crypto.*
import javax.crypto.spec.IvParameterSpec
import javax.inject.Singleton
/**
* Manage cryptographic key in keystore
* requires previous user authentication to have been performed
* https://gist.github.com/msramalho/a95b1ea880fa3f3d6f70099ccf72ff62
*/
@Singleton
class Cryptography constructor(keyName: String) {
companion object {
private const val TRANSFORMATION =
"${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_CBC}/${KeyProperties.ENCRYPTION_PADDING_PKCS7}"
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
private const val SEPARATOR = ","
}
private lateinit var keyName: String
private var keyStore: KeyStore? = null
private var secretKey: SecretKey? = null
init {
this.keyName = keyName
initKeystore()
loadOrGenerateKey()
}
@Throws(
NoSuchProviderException::class,
NoSuchAlgorithmException::class,
InvalidAlgorithmParameterException::class
)
private fun loadOrGenerateKey() {
getKey()
if (secretKey == null) generateKey()
}
@Throws(
KeyStoreException::class,
CertificateException::class,
NoSuchAlgorithmException::class,
IOException::class
)
private fun initKeystore() {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE).apply {
load(null)
}
}
private fun getKey() {
try {
val secretKeyEntry = keyStore?.getEntry(keyName, null)
if (secretKeyEntry is KeyStore.SecretKeyEntry) {
secretKey = secretKeyEntry.secretKey
}
} catch (e: KeyStoreException) {
// failed to retrieve -> will generate new
e.printStackTrace()
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
} catch (e: UnrecoverableEntryException) {
e.printStackTrace()
}
}
@Throws(
NoSuchProviderException::class,
NoSuchAlgorithmException::class,
InvalidAlgorithmParameterException::class
)
private fun generateKey() {
val keyGenerator: KeyGenerator =
KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
keyName!!,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build()
keyGenerator.init(keyGenParameterSpec)
secretKey = keyGenerator.generateKey()
}
@Throws(
NoSuchPaddingException::class,
NoSuchAlgorithmException::class,
InvalidKeyException::class,
BadPaddingException::class,
IllegalBlockSizeException::class
)
fun encrypt(toEncrypt: String): String {
val cipher: Cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val iv: String = Base64.encodeToString(cipher.iv, Base64.DEFAULT)
val encrypted: String = Base64.encodeToString(
cipher.doFinal(toEncrypt.toByteArray(StandardCharsets.UTF_8)),
Base64.DEFAULT
)
return encrypted + SEPARATOR + iv
}
@Throws(
NoSuchPaddingException::class,
NoSuchAlgorithmException::class,
InvalidAlgorithmParameterException::class,
InvalidKeyException::class,
BadPaddingException::class,
IllegalBlockSizeException::class
)
fun decrypt(toDecrypt: String): String {
val parts = toDecrypt.split(SEPARATOR).toTypedArray()
if (parts.size != 2) throw AssertionError("String to decrypt must be of the form: 'BASE64_DATA" + SEPARATOR + "BASE64_IV'")
val encrypted: ByteArray = Base64.decode(parts[0], Base64.DEFAULT)
val iv: ByteArray = Base64.decode(parts[1], Base64.DEFAULT)
val cipher: Cipher = Cipher.getInstance(TRANSFORMATION)
val spec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
return String(cipher.doFinal(encrypted), StandardCharsets.UTF_8)
}
}
@vincent-paing
Copy link

Why is there a keyName!! at line 87 when it's nullable?

@abalta
Copy link
Author

abalta commented Jul 4, 2022

Yeah, you're right. It doesn't need to be nullable. I edited it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment