Use EncryptedSharedPreferences for HMAC key storage

Replace plain-text SharedPreferences with AES256-GCM encrypted prefs
via androidx.security:security-crypto. Adds PrefsHelper utility to
centralize encrypted prefs access across MainActivity, SettingsActivity
and UsbKeyReceiver.

Reviewed in codereview.md (recommendation #2).
This commit is contained in:
eddy 2026-07-26 16:09:10 +02:00
commit b5d42d53ba
5 changed files with 42 additions and 10 deletions

View file

@ -42,4 +42,5 @@ dependencies {
implementation 'io.github.dkaukov:esp32-flash-lib:1.1.17' implementation 'io.github.dkaukov:esp32-flash-lib:1.1.17'
implementation 'com.eclipsesource.minimal-json:minimal-json:0.9.5' implementation 'com.eclipsesource.minimal-json:minimal-json:0.9.5'
implementation 'org.slf4j:slf4j-nop:2.0.16' implementation 'org.slf4j:slf4j-nop:2.0.16'
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
} }

View file

@ -164,8 +164,8 @@ class MainActivity : AppCompatActivity() {
} }
private fun loadHmacKey() { private fun loadHmacKey() {
val prefs = getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE) val prefs = PrefsHelper.getPrefs(this)
val hexKey = prefs.getString(SettingsActivity.PREF_KEY, null) val hexKey = prefs.getString(PrefsHelper.PREF_KEY, null)
hmacKey = hexKey?.let { hexStringToBytes(it) } hmacKey = hexKey?.let { hexStringToBytes(it) }
Log.d(TAG, "HMAC key loaded: ${if (hmacKey != null) "${hmacKey!!.size} bytes" else "none"}") Log.d(TAG, "HMAC key loaded: ${if (hmacKey != null) "${hmacKey!!.size} bytes" else "none"}")
} }

View file

@ -0,0 +1,33 @@
package com.eddy.atomrelay
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
object PrefsHelper {
private const val TAG = "PrefsHelper"
const val PREFS_NAME = "relay_prefs"
const val PREF_KEY = "hmac_key"
fun getPrefs(context: Context): SharedPreferences {
return try {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
EncryptedSharedPreferences.create(
context,
PREFS_NAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
} catch (e: Exception) {
Log.e(TAG, "Failed to create EncryptedSharedPreferences", e)
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
}
}
}

View file

@ -27,8 +27,6 @@ import java.security.SecureRandom
class SettingsActivity : AppCompatActivity() { class SettingsActivity : AppCompatActivity() {
companion object { companion object {
const val PREFS_NAME = "relay_prefs"
const val PREF_KEY = "hmac_key"
const val RESULT_SCAN = 100 const val RESULT_SCAN = 100
private const val TAG = "SettingsActivity" private const val TAG = "SettingsActivity"
private const val ESP_VENDOR_ID = 0x303a private const val ESP_VENDOR_ID = 0x303a
@ -158,8 +156,8 @@ class SettingsActivity : AppCompatActivity() {
handler.post { handler.post {
when (result) { when (result) {
is KeyResult.Success -> { is KeyResult.Success -> {
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) val prefs = PrefsHelper.getPrefs(this@SettingsActivity)
prefs.edit().putString(PREF_KEY, result.hexKey).apply() prefs.edit().putString(PrefsHelper.PREF_KEY, result.hexKey).apply()
Toast.makeText(this, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show() Toast.makeText(this, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show()
cleanupAction() cleanupAction()
setResult(RESULT_SCAN) setResult(RESULT_SCAN)

View file

@ -211,8 +211,8 @@ class UsbKeyReceiver : BroadcastReceiver() {
val hexKey = keyLine.removePrefix("HMAC_KEY:").trim() val hexKey = keyLine.removePrefix("HMAC_KEY:").trim()
if (hexKey.length == 64 && hexKey.all { it in "0123456789ABCDEFabcdef" }) { if (hexKey.length == 64 && hexKey.all { it in "0123456789ABCDEFabcdef" }) {
val upperKey = hexKey.uppercase() val upperKey = hexKey.uppercase()
val prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE) val prefs = PrefsHelper.getPrefs(context)
prefs.edit().putString(SettingsActivity.PREF_KEY, upperKey).apply() prefs.edit().putString(PrefsHelper.PREF_KEY, upperKey).apply()
Log.d(TAG, "HMAC key saved (${upperKey.length} chars)") Log.d(TAG, "HMAC key saved (${upperKey.length} chars)")
handler.post { handler.post {
Toast.makeText(context, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show() Toast.makeText(context, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show()
@ -248,8 +248,8 @@ class UsbKeyReceiver : BroadcastReceiver() {
if (okSb.contains("OK")) { if (okSb.contains("OK")) {
Log.d(TAG, "Key provisioned successfully, saving to prefs") Log.d(TAG, "Key provisioned successfully, saving to prefs")
val prefs = context.getSharedPreferences(SettingsActivity.PREFS_NAME, Context.MODE_PRIVATE) val prefs = PrefsHelper.getPrefs(context)
prefs.edit().putString(SettingsActivity.PREF_KEY, newKey).apply() prefs.edit().putString(PrefsHelper.PREF_KEY, newKey).apply()
handler.post { handler.post {
Toast.makeText(context, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show() Toast.makeText(context, "Beveiligingssleutel gelezen", Toast.LENGTH_LONG).show()
} }