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 (for example, adopted via SoftwareSecureSigner.importKey, or generated by a predecessor of this package) 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 package 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 package 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 package gives you to build a migration on ​

  • generateKey(requireHardware: true) — fails loudly rather than silently returning a software key, so you can tell whether a given device is even capable of the upgrade before starting a ceremony.
  • SecureKey.backing / isHardwareBacked — honest reporting, so you can instrument what fraction of your fleet is already hardware-backed before deciding whether a migration is worth forcing.
  • SoftwareSecureSigner.importKey — adopts an existing raw scalar into the software signer, reporting KeyBacking.software truthfully. This is a migration-compatibility path onto this package's API, not a hardware path: it lets a device already holding a key move onto this package without changing that key or re-enrolling. It cannot and does not put that key into a secure element.

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 isHardwareBacked 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 isHardwareBacked, 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 package deliberately does not pick one for you.

Released under the MIT License. A fork of webank_secure_signer.