2.0.0
Major release: KMP refactor, new macOS and Kotlin/JS targets, biometrics extracted into its own module, and significant performance work on encrypted reads/writes.
The changes listed below are in addition to the work shipped in 2.0.0-RC1 and 2.0.0-RC2 — see those sections for the complete overview of what the entire 2.0.0 encompasses, including these releases.
Highlights
- Faster encrypted reads and writes. A new per-datastore master-key envelope (v2) eliminates Keystore/Keychain IPC on every encrypted read and write. Biggest wins on stores with many encrypted entries.
- New default memory policy:
LAZY_PLAIN_TEXT. Cheap cold start (no bulk decrypt), O(1) reads after first access. ReplacesENCRYPTEDas the default on Android, iOS, macOS, and JVM. - New platforms. Native macOS (
macosX64,macosArm64) and Kotlin/JS (IR) across all modules. - Biometrics is now its own module.
KSafeBiometricsships as the optional:ksafe-biometricsartifact — apps without biometrics no longer pull inandroidx.biometric. - Migrated to AGP 9.2 + Gradle 9.4. All three modules now use the unified KMP library plugin.
Added
-
KSafeMemoryPolicy.LAZY_PLAIN_TEXT— new default on Android, iOS, macOS, JVM. Keeps ciphertext on cold start, decrypts each key on first read, then caches plaintext for the process lifetime. Web staysPLAIN_TEXT(WebCrypto is async-only). -
Native macOS targets (
macosX64,macosArm64) across:ksafe,:ksafe-compose,:ksafe-biometrics(#26, thanks @tomasjablonskis). Uses the same Keychain + CryptoKit + Secure Enclave path as iOS via sharedappleMain. Intel Macs without a T2 fall back to plain Keychain. -
SecurityCheckershort-circuits on macOS — the iOS jailbreak heuristics would otherwise flag every Mac as rooted. -
macosTestsource set — 73 new tests plus the full commonKSafeTestsuite. -
allowDeviceCredentialFallbackonverifyBiometric/verifyBiometricDirect(#29, thanks @Trucodisparo). New optionalBoolean(defaulttrue). Setfalseto restrict to biometrics only — no PIN/password/pattern fallback. JVM/JS/WasmJS ignore it.val ok = KSafeBiometrics.verifyBiometric( reason = "Confirm payment", allowDeviceCredentialFallback = false )
Fixed
- Compatibility with
dev.whyoleg.cryptography0.6.0 (#27, #30, via #28, thanks @HarukeyUA @chirag38-unity). Resolves the runtimeIrLinkageErroron iOS when a consumer app pulled cryptography-kotlin 0.6.0 transitively. - Critical: Secure Enclave key destruction during 1.x → 2.0 migration (latent in RC1 and RC2). A startup-ordering race could let the orphan-cleanup sweep run against an empty DataStore snapshot and irreversibly destroy Secure Enclave EC private keys.
KSafeCorenow waits for the firstsnapshotFlowemission before migrating; orphan cleanup refuses to delete when DataStore is empty but Keychain has entries. Pinned byKSafeCoreStartupOrderingTest. - macOS biometrics now work on every Mac. Switched to
LAPolicyDeviceOwnerAuthenticationon macOS — falls back to login password or Apple Watch on Macs without Touch ID (Mac mini, many Intel Macs). iOS unchanged. verifyBiometric(suspend) now dispatchesevaluatePolicyon Main on Apple platforms, matchingverifyBiometricDirect.
Changed
- Default memory policy is now
LAZY_PLAIN_TEXT(wasENCRYPTED) on Android, iOS, macOS, JVM. Apps that need ciphertext-at-rest semantics must opt in explicitly withKSafeMemoryPolicy.ENCRYPTEDorENCRYPTED_WITH_TIMED_CACHE. Web's forcedPLAIN_TEXTis unchanged. PLAIN_TEXTis now discouraged in KDoc. Its eager-decrypt-everything cold start is O(n) in encrypted keys and can push first-read latency into ANR territory on large Android stores.LAZY_PLAIN_TEXTmatches its steady-state read performance with a much cheaper start.PLAIN_TEXTis still supported.IosKeychainEncryption→AppleKeychainEncryption(and surroundingIos*→Apple*renames) to reflect shared iOS + macOS use.@PublishedApi internal; only consumer code that references these symbols directly is affected.- macOS Keychain prompt — doc-only. Factory KDoc now flags that unsandboxed Mac apps see a system password prompt on first Keychain access (suppressed by signing with a Keychain access group entitlement).
Performance
- v2 envelope routes every
KSafeProtection.DEFAULTencrypted write through one of two AES-256 master keys per datastore (a relaxed-accessibility variant and arequireUnlockedDevice = truevariant), unwrapped once at construction and cached in-process. After warm-up, encrypt and decrypt are pure-CPU AES-GCM — no Keystore/Keychain IPC for the lifetime of the process.KSafeProtection.HARDWARE_ISOLATEDwrites still get a per-entry key (StrongBox / Secure Enclave isolation is the point). Existingv1and legacy entries continue to read through the per-entry path unchanged — no migration, no rewrite. Entries written by 2.0 cannot be read by 1.x. - Parallel batch encrypt — encrypted writes in a batch deduplicate by key and run concurrently with a
Semaphore(8)cap.ENCRYPTEDmemory policy no longer pays a write-time penalty overPLAIN_TEXT. - Parallel cold-start decrypt —
updateCacheandcleanupOrphanedCiphertextnow decrypt concurrently. Cold-start time on a 1500-key encrypted store drops from ~27 ms to under 1 ms. detectProtectionshort-circuit trusts 2.0 metadata authoritatively when present, saving an allocation and a map lookup per unencrypted read.AndroidKeystoreEncryptionmicro-optimisations — lazy companion-levelKeyStore, zero-copy GCM decrypt, single-allocation encrypt buffer, collapsedcontainsAlias+getKey/deleteEntryIPC round-trips.AppleKeychainEncryptionkey-byte cache — repeat encrypt/decrypt on the same key short-circuits bothSecItemCopyMatchingand the SESecKeyCreateDecryptedDataECIES unwrap. Brings Apple in line with the per-alias caches Android and JVM already had.- Suspend
put/deletenow go through the write coalescer. 500 concurrent suspend writes show 5–27× lower per-op latency depending on encryption mode. hasAnyEncryptedKeyatomic flag lets plain-only stores skip theprotectionMaplookup on every read.- Refreshed benchmarks in
docs/BENCHMARKS.md— median of 4 runs on a Galaxy S24. Suspend-API cells now exercise concurrent coroutines, reflecting real-world usage.
Tooling
- AGP
8.13.1→9.2.1, Gradle8.14.4→9.4.1.:ksafemigrated fromcom.android.librarytocom.android.kotlin.multiplatform.library, aligning all three modules.androidInstrumentedTestsource set renamed toandroidDeviceTest. Removed obsoletegradle.propertiesflags now defaulted in AGP 9 (android.useAndroidX,android.nonTransitiveRClass,kotlin.kmp.isolated-projects.support).
Validation
:ksafe:macosArm64Test— 118 tests (73 new + 45 common).:ksafe:iosSimulatorArm64Test— 127 tests, no regression.- Android instrumented tests — 64/64 on emulator and physical Galaxy S24.
- All
linkDebugFramework*and cross-target compile tasks pass cleanly. - End-to-end exercised via KSafeDemo on all six targets.
Full Changelog: 2.0.0-RC2...2.0.0