add secret storage
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
@@ -19,6 +18,8 @@ kotlin {
|
||||
androidMain.dependencies {
|
||||
implementation(libs.compose.uiToolingPreview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation("androidx.datastore:datastore-preferences:1.2.1")
|
||||
implementation("androidx.datastore:datastore-preferences-core:1.2.1")
|
||||
}
|
||||
commonMain.dependencies {
|
||||
implementation(libs.compose.runtime)
|
||||
|
||||
@@ -17,6 +17,7 @@ class MainActivity : ComponentActivity() {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Get database directory
|
||||
val dbDir = File(filesDir, "nostr")
|
||||
dbDir.mkdirs()
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package su.reya.coop.coop
|
||||
|
||||
import android.os.Build
|
||||
|
||||
class AndroidPlatform {
|
||||
val name: String = "Android ${Build.VERSION.SDK_INT}"
|
||||
}
|
||||
|
||||
fun getPlatform() = AndroidPlatform()
|
||||
@@ -0,0 +1,75 @@
|
||||
package su.reya.coop.coop.storage
|
||||
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
|
||||
data class SecretEntry(
|
||||
val encrypted: String,
|
||||
val iv: String
|
||||
)
|
||||
|
||||
class SecretCrypto {
|
||||
private val keyAlias = "coop"
|
||||
private val keyStoreType = "AndroidKeyStore"
|
||||
private val transformation = "AES/GCM/NoPadding"
|
||||
|
||||
fun encrypt(content: String): SecretEntry {
|
||||
// Initialize cipher
|
||||
val cipher = Cipher.getInstance(transformation)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey())
|
||||
|
||||
// Encrypt content
|
||||
val encrypted = cipher.doFinal(content.toByteArray())
|
||||
val iv = cipher.iv
|
||||
|
||||
return SecretEntry(
|
||||
encrypted = Base64.encodeToString(encrypted, Base64.NO_WRAP),
|
||||
iv = Base64.encodeToString(iv, Base64.NO_WRAP)
|
||||
)
|
||||
}
|
||||
|
||||
fun decrypt(entry: SecretEntry): String {
|
||||
val encrypted = Base64.decode(entry.encrypted, Base64.NO_WRAP)
|
||||
val iv = Base64.decode(entry.iv, Base64.NO_WRAP)
|
||||
|
||||
// Initialize cipher
|
||||
val cipher = Cipher.getInstance(transformation)
|
||||
val spec = GCMParameterSpec(128, iv)
|
||||
cipher.init(Cipher.DECRYPT_MODE, getOrCreateKey(), spec)
|
||||
|
||||
// Decrypt content
|
||||
val plaintext = cipher.doFinal(encrypted)
|
||||
|
||||
return String(plaintext, StandardCharsets.UTF_8)
|
||||
}
|
||||
|
||||
private fun getOrCreateKey(): SecretKey {
|
||||
val keyStore = KeyStore.getInstance(keyStoreType).apply { load(null) }
|
||||
val existingKey = keyStore.getKey(keyAlias, null)
|
||||
|
||||
// Return existing key if available
|
||||
if (existingKey is SecretKey) return existingKey
|
||||
|
||||
// Construct a new key generator
|
||||
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, keyStoreType)
|
||||
|
||||
// Initialize key generation parameters
|
||||
val spec = KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setKeySize(256)
|
||||
.build()
|
||||
|
||||
// Generate a new key
|
||||
keyGenerator.init(spec)
|
||||
|
||||
return keyGenerator.generateKey()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package su.reya.coop.coop.storage
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
private val Context.dataStore by preferencesDataStore("secret_store")
|
||||
|
||||
class SecretStore(private val context: Context) {
|
||||
private val crypto = SecretCrypto()
|
||||
|
||||
suspend fun set(key: String, value: String) {
|
||||
val entry = crypto.encrypt(value)
|
||||
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[stringPreferencesKey("${key}_encrypted")] = entry.encrypted
|
||||
prefs[stringPreferencesKey("${key}_iv")] = entry.iv
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun get(key: String): String? {
|
||||
val prefs = context.dataStore.data.first()
|
||||
val encrypted = prefs[stringPreferencesKey("${key}_encrypted")] ?: return null
|
||||
val iv = prefs[stringPreferencesKey("${key}_iv")] ?: return null
|
||||
|
||||
return crypto.decrypt(SecretEntry(encrypted, iv))
|
||||
}
|
||||
|
||||
suspend fun clear(name: String) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs.remove(stringPreferencesKey("${name}_encrypted"))
|
||||
prefs.remove(stringPreferencesKey("${name}_iv"))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun has(name: String): Boolean {
|
||||
val prefs = context.dataStore.data.first()
|
||||
return prefs[stringPreferencesKey("${name}_encrypted")] != null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user