Skip to content

Migrating existing devices to hardware-backed keys ​

The constraint that forces the decision ​

A hardware-backed key cannot be imported. It must be generated inside the secure element — that is the entire security property. AndroidKeyStore and the Apple Secure Enclave both refuse to ingest an existing private scalar, and rightly so: a key that could be imported was, by definition, extractable at some point.

So there is no "upgrade the storage of the existing key" path. Concretely:

generate inside the element  →  new private key  →  new public key
                                                 →  the backend's registered key is stale
                                                 →  re-enrolment

Any device that already holds a key generated outside a secure element — a WebCrypto key from a browser build, a key from the desktop software fallback, or one generated by a predecessor of this plugin — has a public key registered with your backend. Moving that device to a hardware-backed key means generating a new key pair and re-registering the new public key; there is no way to move the existing one into hardware in place.

Why that is not a decision this plugin makes for you ​

Re-enrolment is application-specific: it may need a second factor, a fresh device-binding ceremony, or nothing more than a silent key rotation, depending on how your backend authenticates devices. Whatever it needs, it is not something this plugin can do invisibly — the moment a device's public key changes, the previous key stops being able to authenticate as that device until the new one is registered.

For a security-sensitive credential this is worth treating deliberately rather than automatically, because a migration path that runs on every device at once converts a gradual risk (some devices are on a weaker key) into a synchronised one (every device re-enrols simultaneously, and anything that goes wrong in the ceremony goes wrong for everyone at once).

What this plugin gives you to build a migration on ​

  • capabilities() — probe before you start. It answers whether a key generated on this device right now would be hardware-backed, without creating one.
  • generateKey({ requireHardware: true }) — fails loudly rather than silently returning a software key, so a ceremony cannot half-succeed into a weaker key than it promised.
  • SecureKey.backing / hardwareBacked — honest reporting, so you can instrument what fraction of your fleet is already hardware-backed before deciding whether a migration is worth forcing.

There is deliberately no import path. The Flutter predecessor of this plugin had one, for adopting a scalar out of an app's existing secure storage into its software signer; it is not carried over here, because a Tauri app reaching this plugin is not migrating off an in-process Dart key, and an import API that can only ever produce a software key is a foot-gun that reads like a migration tool.

Common shapes a caller might build on top of this, roughly in order of how disruptive they are to a user:

  1. New installs only. New enrolments get a hardware key; existing devices keep whatever they have until they re-enrol for an unrelated reason (new device, reinstall, key rotation). No forced ceremony, but the fleet only improves as fast as devices turn over.
  2. Offer, don't force. Surface hardwareBacked in a settings screen and let the user trigger re-enrolment when it suits them.
  3. Gate a new capability on hardware backing. A newly introduced high-risk operation can require hardwareBacked, giving users a concrete reason to re-enrol at a moment of their choosing rather than on a forced schedule.
  4. Migrate on next launch. The most complete outcome and the most disruptive one: every existing device hits a re-enrolment ceremony at once. If you choose this, budget for a resumable ceremony (a device interrupted mid-migration must still be able to authenticate with its old key until the new one is confirmed registered), a way to halt the rollout, and a tested rollback.

Which of these is right depends entirely on your backend's authentication model and your users' tolerance for a re-enrolment prompt — this plugin deliberately does not pick one for you.

A note specific to the web and desktop targets ​

A browser or desktop build cannot produce a hardware key at all, so for those targets there is no migration to run: capabilities() reports software permanently, and generateKey({ requireHardware: true }) will always fail. A migration designed around "prompt every device to re-enrol onto hardware" needs a branch for those users that does something else — most simply, leaves them on the software key and records that fact, rather than looping them through a ceremony that cannot succeed.

Released under the MIT License.