> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Session storage

# Session Storage

This guide covers securely storing and restoring Privacy Boost sessions using Android Keystore, expanding on the persistence options in [Key Management](/sdk/concepts/key-management#persistence--protection).

## Why Keystore?

Session data includes sensitive cryptographic keys. Android Keystore provides:

* Hardware-backed encryption (TEE/Secure Element)
* Key material never leaves secure hardware
* Biometric authentication support
* Protection against extraction

## EncryptedSharedPreferences

The simplest approach using AndroidX Security:

### Setup

Add dependency:

```kotlin theme={null}
dependencies {
    implementation("androidx.security:security-crypto:1.1.0-alpha06")
}
```

### Implementation

```kotlin theme={null}
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.google.gson.Gson

class SessionStorage(context: Context) {

    private val masterKey = MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

    private val sharedPrefs = EncryptedSharedPreferences.create(
        context,
        "privacy_boost_session",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    private val gson = Gson()

    fun saveSession(session: ExportedSession) {
        val json = gson.toJson(session)
        sharedPrefs.edit()
            .putString(KEY_SESSION, json)
            .apply()
    }

    fun loadSession(): ExportedSession? {
        val json = sharedPrefs.getString(KEY_SESSION, null) ?: return null
        return try {
            gson.fromJson(json, ExportedSession::class.java)
        } catch (e: Exception) {
            null
        }
    }

    fun deleteSession() {
        sharedPrefs.edit()
            .remove(KEY_SESSION)
            .apply()
    }

    fun hasSession(): Boolean {
        return sharedPrefs.contains(KEY_SESSION)
    }

    companion object {
        private const val KEY_SESSION = "session_data"
    }
}
```

## Biometric Authentication

Add biometric protection for session access:

### Setup

Add dependencies:

```kotlin theme={null}
dependencies {
    implementation("androidx.biometric:biometric:1.1.0")
}
```

### BiometricSessionStorage

```kotlin theme={null}
import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec

class BiometricSessionStorage(private val context: Context) {

    private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
    private val gson = Gson()

    // MARK: - Biometric Availability

    fun isBiometricAvailable(): Boolean {
        val biometricManager = BiometricManager.from(context)
        return when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
            BiometricManager.BIOMETRIC_SUCCESS -> true
            else -> false
        }
    }

    // MARK: - Save with Biometrics

    fun saveSession(
        activity: FragmentActivity,
        session: ExportedSession,
        onSuccess: () -> Unit,
        onError: (String) -> Unit
    ) {
        val executor = ContextCompat.getMainExecutor(context)

        val callback = object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                try {
                    val cipher = result.cryptoObject?.cipher
                        ?: throw Exception("No cipher available")

                    val json = gson.toJson(session)
                    val encrypted = cipher.doFinal(json.toByteArray())
                    val iv = cipher.iv

                    // Store encrypted data and IV
                    getPrefs().edit()
                        .putString(KEY_ENCRYPTED_SESSION, encrypted.toBase64())
                        .putString(KEY_IV, iv.toBase64())
                        .apply()

                    onSuccess()
                } catch (e: Exception) {
                    onError(e.message ?: "Encryption failed")
                }
            }

            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                onError(errString.toString())
            }

            override fun onAuthenticationFailed() {
                // Called when biometric is valid but not recognized
            }
        }

        val prompt = BiometricPrompt(activity, executor, callback)

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Save Session")
            .setSubtitle("Authenticate to save your Privacy Boost session")
            .setNegativeButtonText("Cancel")
            .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
            .build()

        // Get cipher for encryption
        val cipher = getCipherForEncryption()
        prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
    }

    // MARK: - Load with Biometrics

    fun loadSession(
        activity: FragmentActivity,
        onSuccess: (ExportedSession) -> Unit,
        onError: (String) -> Unit
    ) {
        val prefs = getPrefs()
        val encryptedBase64 = prefs.getString(KEY_ENCRYPTED_SESSION, null)
        val ivBase64 = prefs.getString(KEY_IV, null)

        if (encryptedBase64 == null || ivBase64 == null) {
            onError("No session found")
            return
        }

        val executor = ContextCompat.getMainExecutor(context)

        val callback = object : BiometricPrompt.AuthenticationCallback() {
            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                try {
                    val cipher = result.cryptoObject?.cipher
                        ?: throw Exception("No cipher available")

                    val encrypted = encryptedBase64.fromBase64()
                    val decrypted = cipher.doFinal(encrypted)
                    val json = String(decrypted)

                    val session = gson.fromJson(json, ExportedSession::class.java)
                    onSuccess(session)
                } catch (e: Exception) {
                    onError(e.message ?: "Decryption failed")
                }
            }

            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                onError(errString.toString())
            }
        }

        val prompt = BiometricPrompt(activity, executor, callback)

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Access Session")
            .setSubtitle("Authenticate to access your Privacy Boost session")
            .setNegativeButtonText("Cancel")
            .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
            .build()

        // Get cipher for decryption
        val iv = ivBase64.fromBase64()
        val cipher = getCipherForDecryption(iv)
        prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
    }

    // MARK: - Key Management

    private fun getOrCreateKey(): SecretKey {
        keyStore.getKey(KEY_ALIAS, null)?.let {
            return it as SecretKey
        }

        val keyGenerator = KeyGenerator.getInstance(
            KeyProperties.KEY_ALGORITHM_AES,
            "AndroidKeyStore"
        )

        val keySpec = KeyGenParameterSpec.Builder(
            KEY_ALIAS,
            KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
        )
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setKeySize(256)
            .setUserAuthenticationRequired(true)
            .setUserAuthenticationParameters(
                0, // Timeout (0 = every use)
                KeyProperties.AUTH_BIOMETRIC_STRONG
            )
            .setInvalidatedByBiometricEnrollment(true)
            .build()

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

    private fun getCipherForEncryption(): Cipher {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey())
        return cipher
    }

    private fun getCipherForDecryption(iv: ByteArray): Cipher {
        val cipher = Cipher.getInstance("AES/GCM/NoPadding")
        val spec = GCMParameterSpec(128, iv)
        cipher.init(Cipher.DECRYPT_MODE, getOrCreateKey(), spec)
        return cipher
    }

    private fun getPrefs() = context.getSharedPreferences(
        "privacy_boost_biometric",
        Context.MODE_PRIVATE
    )

    fun deleteSession() {
        getPrefs().edit().clear().apply()
        keyStore.deleteEntry(KEY_ALIAS)
    }

    fun hasSession(): Boolean {
        return getPrefs().contains(KEY_ENCRYPTED_SESSION)
    }

    companion object {
        private const val KEY_ALIAS = "privacy_boost_session_key"
        private const val KEY_ENCRYPTED_SESSION = "encrypted_session"
        private const val KEY_IV = "iv"
    }
}

// Extension functions
private fun ByteArray.toBase64(): String =
    android.util.Base64.encodeToString(this, android.util.Base64.NO_WRAP)

private fun String.fromBase64(): ByteArray =
    android.util.Base64.decode(this, android.util.Base64.NO_WRAP)
```

## Usage in Repository

```kotlin theme={null}
class PrivacyBoostRepository(
    private val sdk: PrivacyBoost,
    private val sessionStorage: SessionStorage,
    private val biometricStorage: BiometricSessionStorage
) {

    suspend fun restoreSession(): Boolean = withContext(Dispatchers.IO) {
        // Try regular storage first
        sessionStorage.loadSession()?.let { session ->
            return@withContext try {
                sdk.importSession(session)
            } catch (e: Exception) {
                sessionStorage.deleteSession()
                false
            }
        }
        false
    }

    fun restoreSessionWithBiometrics(
        activity: FragmentActivity,
        onSuccess: () -> Unit,
        onError: (String) -> Unit
    ) {
        if (!biometricStorage.isBiometricAvailable()) {
            onError("Biometrics not available")
            return
        }

        biometricStorage.loadSession(
            activity = activity,
            onSuccess = { session ->
                try {
                    val success = sdk.importSession(session)
                    if (success) onSuccess() else onError("Session expired")
                } catch (e: Exception) {
                    onError(e.message ?: "Import failed")
                }
            },
            onError = onError
        )
    }

    fun saveCurrentSession(useBiometrics: Boolean = false, activity: FragmentActivity? = null) {
        val session = sdk.exportSession() ?: return

        if (useBiometrics && activity != null && biometricStorage.isBiometricAvailable()) {
            biometricStorage.saveSession(
                activity = activity,
                session = session,
                onSuccess = { /* Saved */ },
                onError = { /* Fall back to regular storage */
                    sessionStorage.saveSession(session)
                }
            )
        } else {
            sessionStorage.saveSession(session)
        }
    }

    fun clearSession() {
        sessionStorage.deleteSession()
        biometricStorage.deleteSession()
    }
}
```

## ViewModel Integration

```kotlin theme={null}
@HiltViewModel
class AuthViewModel @Inject constructor(
    private val repository: PrivacyBoostRepository
) : ViewModel() {

    private val _sessionRestored = MutableStateFlow(false)
    val sessionRestored: StateFlow<Boolean> = _sessionRestored

    fun tryRestoreSession() {
        viewModelScope.launch {
            _sessionRestored.value = repository.restoreSession()
        }
    }

    fun restoreWithBiometrics(activity: FragmentActivity) {
        repository.restoreSessionWithBiometrics(
            activity = activity,
            onSuccess = { _sessionRestored.value = true },
            onError = { /* Show error */ }
        )
    }

    fun saveSession(useBiometrics: Boolean, activity: FragmentActivity?) {
        repository.saveCurrentSession(useBiometrics, activity)
    }
}
```

## Security Best Practices

1. **Use BIOMETRIC\_STRONG** - Requires Class 3 biometrics
2. **Set invalidatedByBiometricEnrollment** - Invalidate key if new biometric enrolled
3. **No timeout** - Require auth every time for sensitive data
4. **Handle key invalidation** - Clear session if key is invalidated
5. **Don't store unencrypted** - Always encrypt sensitive data

## Next Steps

* [API Reference](../api-reference) - Complete API documentation
* [Android Getting Started](../getting-started) - Jetpack Compose integration
