Skip to content

fix(android): resolve "Biometric crypto object unavailable" on Android 9/10 - #88

Merged
riderx merged 3 commits into
mainfrom
copilot/fix-biometric-crypto-issue
Apr 6, 2026
Merged

fix(android): resolve "Biometric crypto object unavailable" on Android 9/10#88
riderx merged 3 commits into
mainfrom
copilot/fix-biometric-crypto-issue

Conversation

Copilot AI commented Mar 26, 2026

Copy link
Copy Markdown

On Android 9/10 (API 28-29), BiometricPrompt.CryptoObject creation silently fails because the AndroidKeyStore key was generated with setUserAuthenticationValidityDurationSeconds(1) — a time-based policy requiring prior authentication before the key is usable. Calling cipher.init() before authentication throws UserNotAuthenticatedException (a subclass of InvalidKeyException), which wasn't caught, causing createCryptoObject() to return null and surfacing as "Biometric crypto object unavailable".

Key changes

  • Per-operation auth on API < 30: Changed setUserAuthenticationValidityDurationSeconds(1)(-1) in both generateSecretKey() and getOrCreateCredentialKey(). Value -1 means per-operation authentication — cipher.init() succeeds before auth and the biometric event unlocks the cipher, which is the required pattern for CryptoObject binding.
// Before (broken on Android 9/10)
builder.setUserAuthenticationValidityDurationSeconds(1);

// After
builder.setUserAuthenticationValidityDurationSeconds(-1); // per-operation, works with CryptoObject
  • Broader exception handling: Widened catch (KeyPermanentlyInvalidatedException)catch (InvalidKeyException) in all cipher.init() call sites. Covers both KeyPermanentlyInvalidatedException (enrollment changed) and UserNotAuthenticatedException (migration from time-based keys), with the same recovery: delete the stale key and regenerate.

  • Decrypt migration path: createCredentialDecryptCryptoObject() now explicitly handles InvalidKeyException by deleting the incompatible key and its associated encrypted data, unblocking re-enrollment via setSecureCredentials rather than permanently failing.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • dl.google.com
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -Xmx1536m -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant -cp /home/REDACTED/.gradle/wrapper/dists/gradle-8.14.3-all/10utluxaxniiv4wxiphsi49nj/gradle-8.14.3/lib/gradle-daemon-main-8.14.3.jar (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>bug: Biometric crypto object unavailable</issue_title>
<issue_description>## Bug Report

Capacitor Version

💊   Capacitor Doctor  💊 

Latest Dependencies:

  @capacitor/cli: 8.2.0
  @capacitor/core: 8.2.0
  @capacitor/android: 8.2.0
  @capacitor/ios: 8.2.0

Installed Dependencies:

  @capacitor/core: 8.2.0
  @capacitor/ios: 8.2.0
  @capacitor/android: 8.2.0
  @capacitor/cli: 8.2.0

[success] Android looking great! 👌
[success] iOS looking great! 👌

Plugin Version

💊   Capgo Doctor  💊
│
●   App Name: mPay
│
●   App ID: com.map.app
│
●   App Version: 2.3.3
│
●   Web Dir: dist/app/browser
│
●   OS: darwin Darwin Kernel Version 25.4.0: Thu Mar 19 19:30:44 PDT 2026; root:xnu-12377.101.15~1/RELEASE_ARM64_T6000
│
●   Node: v22.12.0
│
●   Installed Dependencies:
│
●     @capgo/cli: 7.88.9
│
●     @capgo/capacitor-native-biometric: 8.4.2
│
●     @capgo/capacitor-updater: 8.42.5
│
◇  Latest Dependencies:
│
●     @capgo/cli: 7.88.9
│
●     @capgo/capacitor-native-biometric: 8.4.2
│
●     @capgo/capacitor-updater: 8.45.0
│
▲  🚨 Some dependencies are not up to date
│
■  Error: Some dependencies are not up to date

context(s)

ManualModel: false
AutoMode: true
CapgoCloud: false
OnPremise: true

Platform(s)

Android 9
Android 10

Current Behavior

the error Biometric crypto object unavailable occurs when calling NativeBiometric.verifyIdentity()

Expected Behavior

show the Biometric authentication dialog

Code Reproduction

Other Technical Details

npm --version output: 10.9.0

node --version output: v22.12.0

pod --version output (iOS issues only):

Additional Context

</issue_description>

Comments on the Issue (you are @copilot in this section)


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Summary by CodeRabbit

Bug Fixes

  • Enhanced biometric authentication reliability by improving invalid key detection and recovery—keys are automatically deleted and recreated when validation fails.
  • Updated authentication behavior for Android devices running pre-Android R versions.
  • Strengthened credential encryption and decryption error handling to prevent operation failures.

On Android 9/10 (API 28-29), setUserAuthenticationValidityDurationSeconds(1)
creates a time-based key requiring prior authentication. Calling cipher.init()
before biometric auth throws UserNotAuthenticatedException (subclass of
InvalidKeyException), which wasn't caught, causing createCryptoObject() to
return null and triggering "Biometric crypto object unavailable".

Fix: use -1 (per-operation auth) so cipher.init() succeeds before auth and
BiometricPrompt CryptoObject binding works correctly. Also broaden exception
handling to catch InvalidKeyException for migration of existing time-based keys.

Co-authored-by: riderx <4084527+riderx@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Cap-go/capacitor-native-biometric/sessions/980fc756-e2c7-4548-9159-1c944ce62a09
Copilot AI changed the title [WIP] Fix biometric crypto object unavailability fix(android): resolve "Biometric crypto object unavailable" on Android 9/10 Mar 26, 2026
Copilot AI requested a review from riderx March 26, 2026 11:21
@riderx

riderx commented Mar 26, 2026

Copy link
Copy Markdown
Member

@copilot your build fail

Prettier requires Java imports to be in alphabetical order within their
groups. The import was placed between Android keystore imports instead of
in the java.security.* section, causing the CI prettier check to fail.

Co-authored-by: riderx <4084527+riderx@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Cap-go/capacitor-native-biometric/sessions/23028fed-4ada-45be-bdef-17ca32669e89

Copilot AI commented Mar 26, 2026

Copy link
Copy Markdown
Author

@copilot your build fail

Fixed in f72af11. The import java.security.InvalidKeyException; was placed between Android keystore imports instead of in the java.security.* group, failing the Prettier check. Moved it to the correct alphabetical position.

@riderx
riderx marked this pull request as ready for review April 4, 2026 23:34
@riderx

riderx commented Apr 4, 2026

Copy link
Copy Markdown
Member

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Apr 4, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Apr 4, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 673bd768-db04-4302-a907-f40d7fa231f7

📥 Commits

Reviewing files that changed from the base of the PR and between 8e61bff and f72af11.

📒 Files selected for processing (1)
  • android/src/main/java/ee/forgr/biometric/AuthActivity.java

📝 Walkthrough

Walkthrough

Fixed cryptographic key handling in Android biometric authentication by catching InvalidKeyException during cipher initialization and credential operations, and changed authentication validity duration from time-based to per-operation mode for pre-Android R devices.

Changes

Cohort / File(s) Summary
Biometric Crypto Key Exception Handling
android/src/main/java/ee/forgr/biometric/AuthActivity.java
Updated exception handling in cipher creation and credential crypto object methods to catch InvalidKeyException instead of KeyPermanentlyInvalidatedException. On exception, deletes invalid keys and recreates them. Changed pre-Android R key generation to use per-operation authentication (setUserAuthenticationValidityDurationSeconds(-1)) instead of time-based windows.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A crypto key that wouldn't play,
Threw exceptions every single day,
Now we catch them, fresh and new,
Delete and rebuild—that's what we do!
Per-op auth makes Android shine so bright,
Biometric flows are working right! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and specifically addresses the main issue fixed: resolving a biometric crypto object unavailability problem on Android 9/10, matching the primary change in the code.
Linked Issues check ✅ Passed The code changes fully address issue #87's requirements: per-operation authentication for Android <30 (setUserAuthenticationValidityDurationSeconds(-1)), broadened InvalidKeyException handling to catch UserNotAuthenticatedException, and migration handling for existing keys.
Out of Scope Changes check ✅ Passed All changes are within scope: exception handling improvements, authentication policy adjustments, and key migration logic directly align with fixing biometric crypto object unavailability on Android 9/10.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch copilot/fix-biometric-crypto-issue

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

bug: Biometric crypto object unavailable

2 participants