Skip to content

Instantly share code, notes, and snippets.

@technoir42
Created April 10, 2019 10:07
Show Gist options
  • Save technoir42/3dd72f2b6f29e0772d0d6bf8e6c58fe2 to your computer and use it in GitHub Desktop.
Save technoir42/3dd72f2b6f29e0772d0d6bf8e6c58fe2 to your computer and use it in GitHub Desktop.
import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import androidx.annotation.RequiresApi
import java.nio.ByteBuffer
import java.security.InvalidKeyException
import java.security.Key
import java.security.KeyStore
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.spec.GCMParameterSpec
abstract class Cryptor {
fun encryptString(input: String, keyAlias: String): String {
val inputBytes = input.toByteArray(Charsets.UTF_8)
val encrypted = encrypt(inputBytes, keyAlias)
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
}
fun decryptString(input: String, keyAlias: String): String {
val inputBytes = Base64.decode(input, Base64.DEFAULT)
val decrypted = decrypt(inputBytes, keyAlias)
return String(decrypted, Charsets.UTF_8)
}
abstract fun encrypt(input: ByteArray, keyAlias: String): ByteArray
abstract fun decrypt(input: ByteArray, keyAlias: String): ByteArray
companion object {
fun create(): Cryptor {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Api23Cryptor()
} else {
NoCryptor()
}
}
}
}
private class NoCryptor : Cryptor() {
override fun encrypt(input: ByteArray, keyAlias: String): ByteArray = input
override fun decrypt(input: ByteArray, keyAlias: String): ByteArray = input
}
@RequiresApi(Build.VERSION_CODES.M)
private class Api23Cryptor : Cryptor() {
private val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER)
init {
keyStore.load(null)
}
override fun encrypt(input: ByteArray, keyAlias: String): ByteArray {
val iv = generateIv(12)
var secretKey = keyStore.getKey(keyAlias, null) ?: generateSecretKey(keyAlias)
val cipherText = try {
val cipher = getCipher(Cipher.ENCRYPT_MODE, secretKey, iv)
cipher.doFinal(input)
} catch (e: InvalidKeyException) {
keyStore.deleteEntry(keyAlias)
secretKey = generateSecretKey(keyAlias)
val cipher = getCipher(Cipher.ENCRYPT_MODE, secretKey, iv)
cipher.doFinal(input)
}
val result = ByteBuffer.allocate(4 + iv.size + cipherText.size)
result.putInt(iv.size)
result.put(iv)
result.put(cipherText)
return result.array()
}
override fun decrypt(input: ByteArray, keyAlias: String): ByteArray {
val secretKey = keyStore.getKey(keyAlias, null)
?: throw IllegalStateException("Secret key with alias '$keyAlias' doesn't exist")
val buffer = ByteBuffer.wrap(input)
val ivLength = buffer.int
if (ivLength != 12 && ivLength != 16) {
throw IllegalArgumentException("Invalid IV length: $ivLength")
}
val iv = ByteArray(ivLength)
buffer.get(iv)
val cipherText = ByteArray(buffer.remaining())
buffer.get(cipherText)
try {
val cipher = getCipher(Cipher.DECRYPT_MODE, secretKey, iv)
return cipher.doFinal(cipherText)
} catch (e: InvalidKeyException) {
keyStore.deleteEntry(keyAlias)
throw e
}
}
private fun getCipher(mode: Int, secretKey: Key, iv: ByteArray): Cipher {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(mode, secretKey, GCMParameterSpec(128, iv))
return cipher
}
private fun generateIv(length: Int): ByteArray {
val iv = ByteArray(length)
SecureRandom().nextBytes(iv)
return iv
}
private fun generateSecretKey(alias: String): Key {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES)
keyGenerator.init(KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(false)
.build())
return keyGenerator.generateKey()
}
companion object {
private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment