Part 2: Hardware-Backed Encrypted DataStore in Android

 Alright — let’s make Part 2 of this blog: a full hardware-backed Encrypted DataStore implementation for production use.

We’ll combine:

  • Jetpack DataStore (Preferences) for safe, async storage

  • Hardware-backed AES key in Android Keystore (StrongBox when available)

  • AES/GCM/NoPadding encryption with IV handling

  • Easy API for saving/reading/deleting sensitive strings



Part 2: Hardware-Backed Encrypted DataStore in Android


1. Why Combine Hardware-Backed Keys with DataStore?

From Part 1, we learned that hardware-backed encryption ensures that encryption keys never leave secure hardware and can’t be extracted.

DataStore is the modern alternative to SharedPreferences:

  • Asynchronous (no ANRs)

  • Type-safe

  • Corruption-handling

  • Flow-based API

By encrypting all values before storing them in DataStore — with a hardware-backed AES key — we get:

  • Encryption at rest + secure key storage

  • Resilience against root and file dump attacks

  • Modern, maintainable API


2. Dependencies

Add to your build.gradle:

dependencies {
    implementation "androidx.datastore:datastore-preferences:1.1.1"
}

No extra crypto libraries are needed — we’ll use Android’s built-in Keystore and javax.crypto.


3. Crypto Helper (Hardware-Backed)

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
import java.security.SecureRandom

object HardwareCrypto {
    private const val KEY_ALIAS = "app_secure_datastore_key"
    private const val ANDROID_KEYSTORE = "AndroidKeyStore"
    private const val TRANSFORMATION = "AES/GCM/NoPadding"
    private const val IV_SIZE_BYTES = 12
    private const val TAG_LENGTH_BITS = 128

    fun getOrCreateKey(): SecretKey {
        val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
        val existingKey = keyStore.getKey(KEY_ALIAS, null) as? SecretKey
        if (existingKey != null) return existingKey

        val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE)
        val spec = KeyGenParameterSpec.Builder(
            KEY_ALIAS,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setKeySize(256)
            .setIsStrongBoxBacked(true) // Use StrongBox if available
            .build()

        keyGenerator.init(spec)
        return keyGenerator.generateKey()
    }

    fun encrypt(secretKey: SecretKey, plainText: String): String {
        val cipher = Cipher.getInstance(TRANSFORMATION)
        val iv = ByteArray(IV_SIZE_BYTES).also { SecureRandom().nextBytes(it) }
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, GCMParameterSpec(TAG_LENGTH_BITS, iv))
        val cipherBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))
        return Base64.encodeToString(iv + cipherBytes, Base64.NO_WRAP)
    }

    fun decrypt(secretKey: SecretKey, encryptedBase64: String): String {
        val decoded = Base64.decode(encryptedBase64, Base64.NO_WRAP)
        val iv = decoded.copyOfRange(0, IV_SIZE_BYTES)
        val cipherBytes = decoded.copyOfRange(IV_SIZE_BYTES, decoded.size)
        val cipher = Cipher.getInstance(TRANSFORMATION)
        cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(TAG_LENGTH_BITS, iv))
        return String(cipher.doFinal(cipherBytes), Charsets.UTF_8)
    }
}

4. Encrypted DataStore Manager

import android.content.Context
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.preferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.crypto.SecretKey

private val Context.secureDataStore by preferencesDataStore(name = "secure_prefs")

class EncryptedDataStoreManager(private val context: Context) {

    private val secretKey: SecretKey by lazy { HardwareCrypto.getOrCreateKey() }

    suspend fun saveString(key: String, value: String) {
        val encrypted = HardwareCrypto.encrypt(secretKey, value)
        context.secureDataStore.edit { prefs ->
            prefs[preferencesKey<String>(key)] = encrypted
        }
    }

    fun readString(key: String): Flow<String?> {
        return context.secureDataStore.data.map { prefs ->
            prefs[preferencesKey<String>(key)]?.let {
                try { HardwareCrypto.decrypt(secretKey, it) } catch (e: Exception) { null }
            }
        }
    }

    suspend fun removeKey(key: String) {
        context.secureDataStore.edit { prefs ->
            prefs.remove(preferencesKey<String>(key))
        }
    }

    suspend fun clearAll() {
        context.secureDataStore.edit { it.clear() }
    }
}

5. Example Usage

val secureStore = EncryptedDataStoreManager(context)

// Saving
lifecycleScope.launch {
    secureStore.saveString("auth_token", "super_secret_token_123")
}

// Reading
lifecycleScope.launch {
    secureStore.readString("auth_token").collect { token ->
        println("Decrypted token: $token")
    }
}

6. Benefits of This Approach

  • Hardware-backed keys protect encryption keys at the hardware level

  • Asynchronous DataStore prevents ANRs

  • AES-256 GCM provides confidentiality + integrity verification

  • StrongBox support ensures even higher security on compatible devices

  • Simple API for engineers to integrate


7. Final Thoughts

If your app handles any sensitive data — authentication tokens, API secrets, offline cached PII — you should never store it in plain text. Combining hardware-backed keys with modern DataStore gives you an end-to-end secure storage layer that’s:

  • Modern

  • Maintainable

  • Resistant to common mobile security threats

In a security audit, this design will be a strong point in your architecture.


📢 Feedback: Did you find this article helpful? Let me know your thoughts or suggestions for improvements! Please leave a comment below. I’d love to hear from you! 👇

Happy coding! 💻

0 comments:

Post a Comment