package me.eternal.purrfect.security import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import android.util.Base64 import java.security.KeyStore import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.SecretKey import javax.crypto.spec.GCMParameterSpec object KeystoreAesGcm { private const val ANDROID_KEYSTORE = "AndroidKeyStore" private const val AES_MODE = "AES/GCM/NoPadding" private const val GCM_TAG_BITS = 128 private const val IV_LEN = 12 fun encrypt(alias: String, plaintext: String): String { val key = getOrCreateKey(alias) val cipher = Cipher.getInstance(AES_MODE) cipher.init(Cipher.ENCRYPT_MODE, key) val iv = cipher.iv val ciphertext = cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8)) val out = ByteArray(iv.size + ciphertext.size) System.arraycopy(iv, 0, out, 0, iv.size) System.arraycopy(ciphertext, 0, out, iv.size, ciphertext.size) return Base64.encodeToString(out, Base64.NO_WRAP) } fun decrypt(alias: String, encoded: String): String? { val raw = runCatching { Base64.decode(encoded, Base64.NO_WRAP) }.getOrNull() ?: return null if (raw.size <= IV_LEN) return null val iv = raw.copyOfRange(0, IV_LEN) val ciphertext = raw.copyOfRange(IV_LEN, raw.size) val key = getOrCreateKey(alias) val cipher = Cipher.getInstance(AES_MODE) cipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(GCM_TAG_BITS, iv)) return runCatching { cipher.doFinal(ciphertext).toString(Charsets.UTF_8) }.getOrNull() } private fun getOrCreateKey(alias: String): SecretKey { val ks = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } val existing = (ks.getEntry(alias, null) as? KeyStore.SecretKeyEntry)?.secretKey if (existing != null) return existing val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE) val spec = KeyGenParameterSpec.Builder( alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setKeySize(256) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setRandomizedEncryptionRequired(true) .build() keyGenerator.init(spec) return keyGenerator.generateKey() } }