-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathAndroidKeyStore.kt
More file actions
90 lines (73 loc) · 3.13 KB
/
Copy pathAndroidKeyStore.kt
File metadata and controls
90 lines (73 loc) · 3.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package to.bitkit.data.keychain
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.security.keystore.StrongBoxUnavailableException
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
class AndroidKeyStore(
private val alias: String,
private val password: CharArray? = null,
) {
private val type = "AndroidKeyStore"
private val algorithm = KeyProperties.KEY_ALGORITHM_AES
private val blockMode = KeyProperties.BLOCK_MODE_GCM
private val padding = KeyProperties.ENCRYPTION_PADDING_NONE
private val transformation = "$algorithm/$blockMode/$padding"
private val ivLength = 12 // GCM typically uses a 12-byte IV
private val keyStore by lazy { KeyStore.getInstance(type).apply { load(null) } }
init {
generateKey()
}
private fun generateKey(alias: String = this.alias) {
if (!keyStore.containsAlias(alias)) {
try {
val generator = KeyGenerator.getInstance(algorithm, type)
generator.init(buildSpec(isStrongboxBacked = true))
generator.generateKey()
} catch (_: StrongBoxUnavailableException) {
val generator = KeyGenerator.getInstance(algorithm, type)
generator.init(buildSpec(isStrongboxBacked = false))
generator.generateKey()
}
}
}
private fun buildSpec(isStrongboxBacked: Boolean): KeyGenParameterSpec {
val spec = KeyGenParameterSpec
.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(blockMode)
.setEncryptionPaddings(padding)
.setRandomizedEncryptionRequired(true)
.setKeySize(256)
.setIsStrongBoxBacked(isStrongboxBacked)
.build()
return spec
}
fun encrypt(data: ByteArray): ByteArray {
val secretKey = keyStore.getKey(alias, password) as SecretKey
val cipher = Cipher.getInstance(transformation).apply { init(Cipher.ENCRYPT_MODE, secretKey) }
val ciphertext = cipher.doFinal(data)
val iv = cipher.iv
check(iv.size == ivLength) { "Unexpected IV length: ${iv.size} ≠ $ivLength" }
// Combine the IV and encrypted data into a single byte array
return iv + ciphertext
}
fun decrypt(data: ByteArray): ByteArray {
val secretKey = keyStore.getKey(alias, password) as SecretKey
// Extract the IV from the beginning of the blob
val iv = data.sliceArray(0 until ivLength)
val actualEncryptedData = data.sliceArray(ivLength until data.size)
val spec = GCMParameterSpec(128, iv)
val cipher = Cipher.getInstance(transformation).apply { init(Cipher.DECRYPT_MODE, secretKey, spec) }
val decryptedDataBytes = cipher.doFinal(actualEncryptedData)
return decryptedDataBytes
}
fun resetEncryptionKey() {
if (keyStore.containsAlias(alias)) {
keyStore.deleteEntry(alias)
}
generateKey()
}
}