Last active
July 20, 2023 00:24
-
-
Save abalta/8509bdcccc11924529bc595f399b7bcf to your computer and use it in GitHub Desktop.
AndroidKeystore Kotlin Usage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
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
Why is there a
keyName!!
at line 87 when it's nullable?