Abrarul8541 commited on
Commit
9ea9dc9
·
verified ·
1 Parent(s): 9ab1f18

Phase 2: AES-256-GCM vault backed by Android Keystore

Browse files
app/src/main/java/io/github/abrarul/modularai/security/Vault.kt ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package io.github.abrarul.modularai.security
2
+
3
+ import android.security.keystore.KeyGenParameterSpec
4
+ import android.security.keystore.KeyProperties
5
+ import android.util.Base64
6
+ import java.security.KeyStore
7
+ import javax.crypto.Cipher
8
+ import javax.crypto.KeyGenerator
9
+ import javax.crypto.SecretKey
10
+ import javax.crypto.spec.GCMParameterSpec
11
+
12
+ /** AES-256-GCM encryption; the key never leaves the Android Keystore. */
13
+ object Vault {
14
+ private const val ALIAS = "modularai_vault_key"
15
+ private const val PROVIDER = "AndroidKeyStore"
16
+ private const val TRANSFORM = "AES/GCM/NoPadding"
17
+
18
+ private fun key(): SecretKey {
19
+ val ks = KeyStore.getInstance(PROVIDER).apply { load(null) }
20
+ (ks.getKey(ALIAS, null) as? SecretKey)?.let { return it }
21
+ val gen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, PROVIDER)
22
+ gen.init(
23
+ KeyGenParameterSpec.Builder(
24
+ ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
25
+ )
26
+ .setKeySize(256)
27
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
28
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
29
+ .build()
30
+ )
31
+ return gen.generateKey()
32
+ }
33
+
34
+ /** Returns base64(iv || ciphertext+tag). */
35
+ fun encrypt(plain: String): String {
36
+ val c = Cipher.getInstance(TRANSFORM)
37
+ c.init(Cipher.ENCRYPT_MODE, key())
38
+ val out = c.iv + c.doFinal(plain.toByteArray(Charsets.UTF_8))
39
+ return Base64.encodeToString(out, Base64.NO_WRAP)
40
+ }
41
+
42
+ fun decrypt(blob: String): String {
43
+ val raw = Base64.decode(blob, Base64.NO_WRAP)
44
+ val iv = raw.copyOfRange(0, 12)
45
+ val c = Cipher.getInstance(TRANSFORM)
46
+ c.init(Cipher.DECRYPT_MODE, key(), GCMParameterSpec(128, iv))
47
+ return String(c.doFinal(raw, 12, raw.size - 12), Charsets.UTF_8)
48
+ }
49
+
50
+ /** Deletes the key: every encrypted record becomes permanently unreadable. */
51
+ fun destroy() {
52
+ KeyStore.getInstance(PROVIDER).apply { load(null) }.deleteEntry(ALIAS)
53
+ }
54
+ }