Skip to content

Conversation

@jvsena42
Copy link
Member

@jvsena42 jvsena42 commented Jan 2, 2026

Description

The default Keychain behavior is keep the data after uninstall and there is no flag to change it. This was causing the seed and other sensitive data being persisted and recovered on app re-install.

The solution is encrypt Keychain data with a key that is deleted on app uninstall, making the persisted keychain data useless.

Also, on fresh install, the app checks for orphaned keychain data and wipes it

Manual testing scenarios

  • Fresh Install Flow
    • Delete app, reinstall, create new wallet
    • Verify encryption key file created
    • Verify keychain items are encrypted (not plaintext)
    • Verify wallet functions normally
  • Restore Wallet Flow
    • Delete app, reinstall, restore from mnemonic
    • Verify encryption key file created
    • Verify restored data encrypted in keychain
    • Verify wallet functions normally
  • App Wipe Flow
    • Go to Settings → Advanced → Wipe Wallet
    • Verify encryption key file deleted
    • Verify keychain wiped
    • Verify App Group UserDefaults wiped
    • Verify onboarding shown
  • Simulated Uninstall/Reinstall
    • Create wallet normally
    • Manually delete encryption key file from App Group
    • Force quit and relaunch app
    • Verify orphaned keychain detected and wiped
    • Verify onboarding shown (fresh start)
  • Existing User Migration
    • Build with old code, create wallet
    • Build with new code, launch app
    • Verify encryption key created on first launch
    • Verify wallet still accessible
    • Create new keychain entry (e.g., set PIN)
    • Verify new entry is encrypted
  • Migration with passphrase

Linked Issues/Tasks

Closes #293

Screenshot / Video

Insert relevant screenshot / recording

@jvsena42 jvsena42 self-assigned this Jan 2, 2026
@jvsena42 jvsena42 requested a review from Copilot January 2, 2026 16:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements encryption for keychain data to prevent persistence after app uninstall. Since iOS keeps keychain data by default after uninstall, the solution encrypts all keychain entries with a key stored in the App Group directory (which is deleted on uninstall). The app also detects and wipes orphaned keychain data on fresh installs when the encryption key is missing.

Key Changes:

  • Added KeychainCrypto utility class with AES-GCM encryption/decryption for keychain data
  • Modified keychain accessibility from AfterFirstUnlock to AfterFirstUnlockThisDeviceOnly to prevent iCloud sync
  • Implemented orphaned keychain detection and cleanup on app launch

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Bitkit/Utilities/KeychainCrypto.swift New encryption utility providing AES-GCM encryption/decryption with key management
Bitkit/Utilities/Keychain.swift Integrated encryption/decryption into save/load operations and updated accessibility attribute
Bitkit/Utilities/Errors.swift Added failedToDecrypt error case for decryption failures
Bitkit/Utilities/AppReset.swift Added encryption key deletion and App Group UserDefaults cleanup to wipe operation
Bitkit/AppScene.swift Added orphaned keychain detection and cleanup on app launch
BitkitTests/KeychainCryptoTests.swift Comprehensive test suite for encryption/decryption functionality
BitkitTests/KeychainTests.swift Added encryption integration tests and updated existing tests
BitkitTests/KeychainiCloudSyncTests.swift New test suite verifying keychain items don't sync to iCloud
Bitkit.xcodeproj/project.pbxproj Added KeychainCrypto.swift to project build configuration

Comment on lines +50 to +56
let thisDeviceOnlyAttributes = [
kSecAttrAccessibleWhenUnlocked as String,
kSecAttrAccessibleAfterFirstUnlock as String,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly as String,
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String,
]

Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thisDeviceOnlyAttributes array is declared but never used. Remove this unused variable or use it in the validation logic below.

Suggested change
let thisDeviceOnlyAttributes = [
kSecAttrAccessibleWhenUnlocked as String,
kSecAttrAccessibleAfterFirstUnlock as String,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly as String,
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String,
]

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +47
try KeychainCrypto.deleteKey()
let loadedKey = try KeychainCrypto.getOrCreateKey()
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test verifies that getOrCreateKey works after deletion but doesn't verify key persistence. The comment on line 56 acknowledges keys differ after deletion. Consider testing actual key persistence by retrieving the key without calling deleteKey() in between, which would verify the load-from-file functionality.

Copilot uses AI. Check for mistakes.
@jvsena42 jvsena42 changed the title fix: block keychain persistence on app uninstall fix: prevent keychain persistence on app uninstall Jan 2, 2026
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.

App is persisting keychain data after uninstall

2 participants