rename example
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package com.bitcointxoko.gudariwallet.security
|
||||
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
import android.util.Base64
|
||||
|
||||
object SecretManager {
|
||||
private const val KEY_ALIAS = "lnbits_api_key"
|
||||
private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
|
||||
private const val TRANSFORMATION = "AES/GCM/NoPadding"
|
||||
private const val GCM_TAG_LENGTH = 128
|
||||
|
||||
/** Generate (or retrieve) the AES key in the Keystore */
|
||||
private fun getOrCreateKey(): SecretKey {
|
||||
val keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER).apply { load(null) }
|
||||
keyStore.getKey(KEY_ALIAS, null)?.let { return it as SecretKey }
|
||||
|
||||
val keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER)
|
||||
keyGen.init(
|
||||
KeyGenParameterSpec.Builder(
|
||||
KEY_ALIAS,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
||||
)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setKeySize(256)
|
||||
// Require device lock screen to be set up
|
||||
.setUserAuthenticationRequired(false)
|
||||
.build()
|
||||
)
|
||||
return keyGen.generateKey()
|
||||
}
|
||||
|
||||
/** Encrypt a plaintext string; returns Base64(IV + ciphertext) */
|
||||
fun encrypt(plaintext: String): String {
|
||||
val cipher = Cipher.getInstance(TRANSFORMATION)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey())
|
||||
val iv = cipher.iv
|
||||
val ciphertext = cipher.doFinal(plaintext.toByteArray(Charsets.UTF_8))
|
||||
val combined = iv + ciphertext
|
||||
return Base64.encodeToString(combined, Base64.DEFAULT)
|
||||
}
|
||||
|
||||
/** Decrypt a Base64(IV + ciphertext) string */
|
||||
fun decrypt(encoded: String): String {
|
||||
val combined = Base64.decode(encoded, Base64.DEFAULT)
|
||||
val iv = combined.copyOfRange(0, 12)
|
||||
val ciphertext = combined.copyOfRange(12, combined.size)
|
||||
val cipher = Cipher.getInstance(TRANSFORMATION)
|
||||
cipher.init(Cipher.DECRYPT_MODE, getOrCreateKey(), GCMParameterSpec(GCM_TAG_LENGTH, iv))
|
||||
return String(cipher.doFinal(ciphertext), Charsets.UTF_8)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.bitcointxoko.gudariwallet.security
|
||||
|
||||
import android.content.Context
|
||||
import com.bitcointxoko.gudariwallet.util.WalletConstants
|
||||
|
||||
|
||||
// Interface — lets you swap in a FakeSecretStore in tests
|
||||
interface SecretStore {
|
||||
val invoiceKey: String
|
||||
val adminKey: String
|
||||
val baseUrl: String
|
||||
val isOnboarded: Boolean
|
||||
fun saveCredentials(baseUrl: String, invoiceKey: String, adminKey: String)
|
||||
|
||||
}
|
||||
|
||||
class EncryptedSecretStore(context: Context) : SecretStore {
|
||||
|
||||
private val prefs = context.getSharedPreferences(WalletConstants.PREFS_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
override val invoiceKey: String
|
||||
get() = decryptOrEmpty(WalletConstants.PREFS_INVOICE_KEY_ENC)
|
||||
|
||||
override val adminKey: String
|
||||
get() = decryptOrEmpty(WalletConstants.PREFS_ADMIN_KEY_ENC)
|
||||
|
||||
override val baseUrl: String
|
||||
get() = prefs.getString(WalletConstants.PREFS_BASE_URL, "").orEmpty()
|
||||
|
||||
override val isOnboarded: Boolean
|
||||
get() = prefs.getBoolean(WalletConstants.PREFS_ONBOARDED, false)
|
||||
|
||||
override fun saveCredentials(baseUrl: String, invoiceKey: String, adminKey: String) {
|
||||
prefs.edit()
|
||||
.putString(WalletConstants.PREFS_BASE_URL, baseUrl)
|
||||
.putString(WalletConstants.PREFS_INVOICE_KEY_ENC, SecretManager.encrypt(invoiceKey))
|
||||
.putString(WalletConstants.PREFS_ADMIN_KEY_ENC, SecretManager.encrypt(adminKey))
|
||||
.putBoolean(WalletConstants.PREFS_ONBOARDED, true)
|
||||
.apply()
|
||||
}
|
||||
private fun decryptOrEmpty(key: String): String {
|
||||
val enc = prefs.getString(key, "").orEmpty()
|
||||
if (enc.isBlank()) return ""
|
||||
return runCatching { SecretManager.decrypt(enc) }.getOrDefault("")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user