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

1. Fresh Install Flow
  • Action: Delete app, reinstall, create new wallet
    Expected logs:
    "Created new encryption key" - KeychainCrypto
    "Saved encryption key to <path>/.keychain_encryption_key" - KeychainCrypto
    "Encrypted data (X bytes → Y bytes)" - KeychainCrypto (for mnemonic save)
    "Saved bip39_mnemonic_0" - Keychain
    "bip39_mnemonic_0 loaded and decrypted from keychain" - Keychain (when loaded)

  • Verification:

  • Verify encryption key file created at <App Group>/<network>/.keychain_encryption_key

  • Verify keychain items are encrypted (not plaintext in raw keychain query)

  • Verify wallet functions normally

2. Restore Wallet Flow
  • Action: Delete app, reinstall, restore from mnemonic

  • Expected logs:
    "Created new encryption key" - KeychainCrypto
    "Encrypted data (X bytes → Y bytes)" - KeychainCrypto (for each item)
    "Saved bip39_mnemonic_0" - Keychain
    "Saved bip39_passphrase_0" - Keychain (if passphrase used)
    "bip39_mnemonic_0 loaded and decrypted from keychain" - Keychain

  • Verification:

  • Verify encryption key file created

  • Verify restored data encrypted in keychain

  • Verify wallet functions normally

3. App Wipe Flow
  • Action: Go to Settings → Backup or restore → Reset and Restore -> Reset Wallet

  • Expected logs:
    "Deleted bip39_mnemonic_0 from keychain" - Keychain
    "Deleted bip39_passphrase_0 from keychain" - Keychain (if exists)
    "Deleted security_pin from keychain" - Keychain (if exists)
    "Deleted encryption key" - KeychainCrypto
    "Wiped App Group UserDefaults" - AppReset

  • Verification:

  • Verify encryption key file deleted

  • Verify keychain wiped (no bip39 items)

  • Verify App Group UserDefaults wiped

  • Verify onboarding shown

4. Simulated Uninstall/Reinstall (Orphaned Keychain)
  • Action:
  1. Create wallet normally
  2. Manually delete encryption key file from App Group (<App Group>/<network>/.keychain_encryption_key)
  3. Force quit and relaunch app
  • Expected logs:
    "Detected orphaned keychain state - keychain exists but encryption key missing. Forcing fresh start." - AppScene (WARN level)
    "Deleted bip39_mnemonic_0 from keychain" - Keychain
    "Deleted bip39_passphrase_0 from keychain" - Keychain (if exists)
    "Orphaned keychain wiped. App will show onboarding." - AppScene

  • Verification:

  • Verify orphaned keychain detected and wiped

  • Verify onboarding shown (fresh start)

5. Existing User Migration (Master → Encryption Branch)
  • Action:
  1. Build with old code (master), create wallet
  2. Switch to encryption branch, build and launch app
  • Expected logs on first launch:
    "Detected legacy unencrypted keychain - migration will proceed normally" - AppScene (INFO level)
    "bip39_mnemonic_0 appears to be legacy unencrypted data, returning as-is" - Keychain (WARN level)
    No encryption key creation yet (key created on first save)

  • Intermediate Verification:

  • Verify wallet still accessible (mnemonic loads successfully)

  • Action: Create new keychain entry (e.g., set PIN or add passphrase)

  • Expected logs on first save:
    "Created new encryption key" - KeychainCrypto
    "Saved encryption key to <path>/.keychain_encryption_key" - KeychainCrypto
    "Encrypted data (X bytes → Y bytes)" - KeychainCrypto
    "Saved security_pin" - Keychain (or whichever item was saved)

  • Final Verification:

  • Verify encryption key created after first modification

  • Verify new entry is encrypted

  • On next app launch: Old mnemonic still loads with WARN log (still plaintext); New PIN loads with decrypt log (now encrypted)

6. Migration with Passphrase
  • Action:
  1. Build with master, create wallet with 12-word mnemonic and passphrase
  2. Switch to encryption branch, build and launch
  • Expected logs:
    "Detected legacy unencrypted keychain - migration will proceed normally" - AppScene
    "bip39_mnemonic_0 appears to be legacy unencrypted data, returning as-is" - Keychain (for 24-word mnemonic)
    "bip39_passphrase_0 appears to be legacy unencrypted data, returning as-is" - Keychain (for passphrase)

  • Verification:

  • Verify 24-word mnemonic loads correctly (using validateMnemonic() validation)

  • Verify passphrase loads correctly

  • Action: Modify any keychain item (e.g., set PIN)

  • Expected logs:
    "Created new encryption key" - KeychainCrypto
    "Encrypted data" - KeychainCrypto (for new item)

  • Final Verification:

  • Verify wallet still functions with 24-word mnemonic + passphrase

Linked Issues/Tasks

Closes #293

Screenshot / Video

restore.mp4
unninstall-reinstall.mp4
migration.mp4
migration-with-passphrase.mp4

@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

@jvsena42 jvsena42 changed the title fix: block keychain persistence on app uninstall fix: prevent keychain persistence on app uninstall Jan 2, 2026
@jvsena42 jvsena42 marked this pull request as ready for review January 2, 2026 18:01
@jvsena42 jvsena42 requested review from ovitrif and pwltr January 2, 2026 18:38
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