Skip to content

feat: add secure-storage module (Tink + Android Keystore)#33

Merged
maniramezan merged 4 commits into
mainfrom
feat/secure-storage-module
Jun 8, 2026
Merged

feat: add secure-storage module (Tink + Android Keystore)#33
maniramezan merged 4 commits into
mainfrom
feat/secure-storage-module

Conversation

@maniramezan

@maniramezan maniramezan commented Jun 8, 2026

Copy link
Copy Markdown
Owner

What

Adds a new published module io.github.maniramezan.compose:secure-storage — an encrypted key-value store backed by Google Tink AEAD with a master key sealed in the Android Keystore. It's the Android equivalent of iOS Keychain for small secrets (tokens, session metadata, feature flags) that must survive process death.

Replaces the need for the deprecated androidx.security:security-crypto (EncryptedSharedPreferences, sunset April 2024).

API

val storage = SecureStorage.create(context)          // Keystore-backed
storage.putString("token", value)
val value = storage.getString("token")               // null if absent/undecryptable
  • SecureStorage interface + AeadSecureStorage (AES256-GCM; the entry key is bound as associated data, so ciphertext can't be replayed under another key).
  • A KeyValueStore seam keeps the crypto unit-testable without the Android framework or Keystore.

Consumer testing

Consumers depend on the SecureStorage interface (not create()) and inject InMemorySecureStorage in tests — public, dependency-free, pure-JVM, no Keystore/Robolectric required:

val storage: SecureStorage = InMemorySecureStorage()
val repo = TokenRepository(storage)        // class under test
repo.save("token-123")
assertEquals("token-123", storage.getString("auth.token"))

Tests (16, all green)

  • AeadSecureStorageTest (8) — round-trip, encrypted-at-rest, overwrite, remove, clear, tamper→null, key-binding (software AEAD + in-memory store).
  • InMemorySecureStorageTest (5) — test-double contract + defensive byte-array copies.
  • SharedPreferencesKeyValueStoreTest (3) — persistence seam via Robolectric (@Config(sdk = 35)).
  • Not unit-tested: SecureStorage.create() Keystore wiring — belongs in an instrumented test, not a fake.

Wiring

  • settings.gradle.kts include, version catalog (Tink 1.21.0), vanniktech POM metadata (secure-storage/gradle.properties).
  • Added to the binary-compatibility-validator ignore list, consistent with the other published modules.

Release

First consumer is the Novalingo Android app (Firebase auth session-hint persistence). Landing this feat: lets release-please cut 0.7.0 and publish to Maven Central.

Verification (local, mirrors CI)

  • ./gradlew check
  • :catalog:assembleDebug :sample:assembleDebug :baselineprofile:assembleDebug
  • mkdocs build --strict

Adds io.github.maniramezan.compose:secure-storage, an encrypted key-value
store backed by Google Tink AEAD with a master key sealed in the Android
Keystore — the Android equivalent of iOS Keychain for storing small secrets
(tokens, session metadata, flags) that must survive process death.

- Public SecureStorage interface + AeadSecureStorage (AES256-GCM, key bound as
  associated data) + Keystore-backed factory via SecureStorage.create().
- KeyValueStore seam keeps crypto unit-testable without the framework/Keystore;
  AeadSecureStorageTest covers round-trip, encrypted-at-rest, tamper, and
  key-binding using a software AEAD.
- Wired into settings, version catalog (Tink 1.21.0), vanniktech POM, and the
  binary-compatibility ignore list (matches the other published modules).
Make the library testable by consumers without the Android Keystore:

- Add InMemorySecureStorage, a public, dependency-free SecureStorage that
  consumers can use as a test double (or in previews) on a plain JVM. Document
  it from the SecureStorage interface KDoc.
- Test the double's contract (round-trip, overwrite, remove/clear, defensive
  byte-array copies).
- Add a Robolectric test for SharedPreferencesKeyValueStore (the persistence
  seam), pinned to @config(sdk = 35) like the other Robolectric tests.
- Expand KDoc on the SecureStorage public surface: security model (what it
  protects and what it doesn't), threading/lifetime, per-method @param/@return
  semantics, null-on-undecryptable read contract, defensive byte copies, and
  @throws/@return on create(). Verified link-clean via Dokka.
- Add a Secure Storage docs-site page (install, quick start, how it works,
  multiple stores, error handling, testing with InMemorySecureStorage, API
  summary) and wire it into the mkdocs nav.
- List the secure-storage artifact in Getting Started.
Record the decision to ship secure-storage as a standalone, non-Compose
published module backed by Tink + Android Keystore (over deprecated
EncryptedSharedPreferences), with a narrow SecureStorage interface and an
InMemorySecureStorage test double. Links to ADR 0002 (module boundaries) and
ADR 0003 (testing strategy); registered in the ADR index and mkdocs nav.
@maniramezan maniramezan merged commit 84002f2 into main Jun 8, 2026
2 checks passed
@maniramezan maniramezan deleted the feat/secure-storage-module branch June 8, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant