Skip to content

Commit 43cea31

Browse files
committed
update changelog
1 parent 7f8e0ef commit 43cea31

File tree

1 file changed

+4
-11
lines changed

1 file changed

+4
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ val ksafe = KSafe(
3434
- New `KSafeEncryption.updateKeyAccessibility()` interface method (default no-op, overridden on iOS)
3535
- Migration marker `__ksafe_access_policy__` stored in DataStore (skipped by `updateCache()` on all platforms)
3636

37+
**Error behavior when locked:** When `requireUnlockedDevice = true` and the device is locked, encrypted reads (`getDirect`, `get`, `getFlow`) and suspend writes (`put`) throw `IllegalStateException` instead of silently returning default values. `putDirect` does not throw — the background write consumer logs the error and drops the batch while staying alive for future writes. On Android, `InvalidKeyException` from `Cipher.init()` is wrapped as `IllegalStateException("device is locked")` and propagated through `resolveFromCache` and `getEncryptedFlow`. Apps can catch this exception to detect and handle locked-device scenarios.
38+
3739
#### New Memory Policy: `ENCRYPTED_WITH_TIMED_CACHE`
3840

3941
A third memory policy that balances security and performance. The primary `memoryCache` still holds ciphertext (like `ENCRYPTED`), but a secondary plaintext cache stores recently-decrypted values for a configurable TTL.
@@ -109,20 +111,11 @@ private fun startBackgroundCollector() {
109111
110112
### Fixed
111113

112-
#### Locked-device exceptions now propagate correctly
113-
114-
When `requireUnlockedDevice = true` and the device is locked, encrypted reads (`getDirect`, `get`, `getFlow`) and suspend writes (`put`) now throw `IllegalStateException` instead of silently returning default values. `putDirect` does not throw to the caller — the background write consumer logs the error and drops the batch while staying alive for future writes.
114+
#### iOS Keychain operations now check return values
115115

116-
**Android:**
117-
- `encryptWithKey()` and `decryptWithKey()` now catch `InvalidKeyException` from `Cipher.init()` (thrown when `setUnlockedDeviceRequired(true)` keys are used while locked) and wrap it as `IllegalStateException("KSafe: Cannot access Keystore key - device is locked.")`
118-
- `resolveFromCache` and `getEncryptedFlow` now re-throw `IllegalStateException` containing "device is locked" instead of swallowing it in the generic `catch (_: Exception)` handler
119-
120-
**iOS:**
121-
- `storeInKeychain()` now checks the `SecItemAdd` return value — previously it was silently ignored, meaning key storage could fail while the device was locked without any error
116+
- `storeInKeychain()` now checks the `SecItemAdd` return value — previously it was silently ignored, meaning key storage could fail without any error
122117
- `updateKeyAccessibility()` now checks the `SecItemUpdate` return value and throws on failure
123118

124-
These fixes ensure that apps using `requireUnlockedDevice = true` can reliably detect and handle locked-device scenarios (e.g., showing a "device is locked" message) instead of silently returning default values.
125-
126119
#### Suspend API no longer blocks the calling dispatcher during encryption/decryption
127120

128121
The suspend functions `put(encrypted = true)` and `get(encrypted = true)` previously called `engine.encrypt()`/`engine.decrypt()` directly on the caller's coroutine dispatcher. If called from `Dispatchers.Main` (e.g., inside `viewModelScope.launch`), the blocking AES-GCM operation would run on the main thread. On Android, first-time Keystore key generation can take 50–200ms — enough to drop frames.

0 commit comments

Comments
 (0)