· 6 years ago · Apr 20, 2019, 09:34 AM
1import android.os.Build
2import android.security.keystore.KeyGenParameterSpec
3import android.security.keystore.KeyProperties
4import android.util.Base64
5import androidx.annotation.RequiresApi
6import java.nio.ByteBuffer
7import java.security.InvalidKeyException
8import java.security.Key
9import java.security.KeyStore
10import java.security.SecureRandom
11import javax.crypto.Cipher
12import javax.crypto.KeyGenerator
13import javax.crypto.spec.GCMParameterSpec
14
15abstract class Cryptor {
16 fun encryptString(input: String, keyAlias: String): String {
17 val inputBytes = input.toByteArray(Charsets.UTF_8)
18 val encrypted = encrypt(inputBytes, keyAlias)
19 return Base64.encodeToString(encrypted, Base64.NO_WRAP)
20 }
21
22 fun decryptString(input: String, keyAlias: String): String {
23 val inputBytes = Base64.decode(input, Base64.DEFAULT)
24 val decrypted = decrypt(inputBytes, keyAlias)
25 return String(decrypted, Charsets.UTF_8)
26 }
27
28 abstract fun encrypt(input: ByteArray, keyAlias: String): ByteArray
29
30 abstract fun decrypt(input: ByteArray, keyAlias: String): ByteArray
31
32 companion object {
33 fun create(): Cryptor {
34 return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
35 Api23Cryptor()
36 } else {
37 NoCryptor()
38 }
39 }
40 }
41}
42
43private class NoCryptor : Cryptor() {
44 override fun encrypt(input: ByteArray, keyAlias: String): ByteArray = input
45
46 override fun decrypt(input: ByteArray, keyAlias: String): ByteArray = input
47}
48
49@RequiresApi(Build.VERSION_CODES.M)
50private class Api23Cryptor : Cryptor() {
51 private val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER)
52
53 init {
54 keyStore.load(null)
55 }
56
57 override fun encrypt(input: ByteArray, keyAlias: String): ByteArray {
58 val iv = generateIv(12)
59 var secretKey = keyStore.getKey(keyAlias, null) ?: generateSecretKey(keyAlias)
60
61 val cipherText = try {
62 val cipher = getCipher(Cipher.ENCRYPT_MODE, secretKey, iv)
63 cipher.doFinal(input)
64 } catch (e: InvalidKeyException) {
65 keyStore.deleteEntry(keyAlias)
66 secretKey = generateSecretKey(keyAlias)
67
68 val cipher = getCipher(Cipher.ENCRYPT_MODE, secretKey, iv)
69 cipher.doFinal(input)
70 }
71
72 val result = ByteBuffer.allocate(4 + iv.size + cipherText.size)
73 result.putInt(iv.size)
74 result.put(iv)
75 result.put(cipherText)
76 return result.array()
77 }
78
79 override fun decrypt(input: ByteArray, keyAlias: String): ByteArray {
80 val secretKey = keyStore.getKey(keyAlias, null)
81 ?: throw IllegalStateException("Secret key with alias '$keyAlias' doesn't exist")
82
83 val buffer = ByteBuffer.wrap(input)
84 val ivLength = buffer.int
85 if (ivLength != 12 && ivLength != 16) {
86 throw IllegalArgumentException("Invalid IV length: $ivLength")
87 }
88 val iv = ByteArray(ivLength)
89 buffer.get(iv)
90
91 val cipherText = ByteArray(buffer.remaining())
92 buffer.get(cipherText)
93
94 try {
95 val cipher = getCipher(Cipher.DECRYPT_MODE, secretKey, iv)
96 return cipher.doFinal(cipherText)
97 } catch (e: InvalidKeyException) {
98 keyStore.deleteEntry(keyAlias)
99 throw e
100 }
101 }
102
103 private fun getCipher(mode: Int, secretKey: Key, iv: ByteArray): Cipher {
104 val cipher = Cipher.getInstance("AES/GCM/NoPadding")
105 cipher.init(mode, secretKey, GCMParameterSpec(128, iv))
106 return cipher
107 }
108
109 private fun generateIv(length: Int): ByteArray {
110 val iv = ByteArray(length)
111 SecureRandom().nextBytes(iv)
112 return iv
113 }
114
115 private fun generateSecretKey(alias: String): Key {
116 val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES)
117 keyGenerator.init(KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
118 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
119 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
120 .setRandomizedEncryptionRequired(false)
121 .build())
122 return keyGenerator.generateKey()
123 }
124
125 companion object {
126 private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
127 }
128}