Keys that cannot be extracted
Generated inside StrongBox, the TEE or the Secure Enclave. Dart only ever holds an opaque key id and the public JWK.
What never leaves the device
A Flutter plugin that generates ECDSA P-256 keys inside AndroidKeyStore or the Apple Secure Enclave and signs there. The private key never enters Dart or app memory.
Generated inside StrongBox, the TEE or the Secure Enclave. Dart only ever holds an opaque key id and the public JWK.
What never leaves the device
signCompactJws() wraps a JSON payload in a fixed ES256 header and returns header.payload.signature, with the platform's DER signature already converted to the 64-byte form JWS requires.
Verify on your server
A silent ambient key for every request, and a user-present key that demands a biometric or the device credential on every use.
The two-key model
Every key reports where it really lives. Web, Windows and Linux fall back to a pure-Dart signer that says it is software, never hardware.
Platform notes
import 'package:flutter_sign_keypair/flutter_sign_keypair.dart';
final signer = SignKeypair();
// Once, at enrolment: both keys, or neither.
final keys = await signer.generateDeviceKeys();
await registerDeviceWithBackend(
ambient: keys.ambient.publicKey.toJson(), // {crv, kty, x, y}
userPresent: keys.userPresent.publicKey.toJson(),
);
// On every authenticated request: the ambient key, which never prompts.
final jws = await signer.signCompactJws(payload: <String, dynamic>{
'timestamp_ms': DateTime.now().millisecondsSinceEpoch,
'method': 'POST',
'path': '/v1/payments/transfer',
});registerDeviceWithBackend stands for your own enrolment call. The plugin creates keys and signs with them; registering, checking and revoking keys is your backend's job.
A device-bound credential is only as strong as the place its private key lives. A pure-Dart signer has to load the raw private scalar into the Dart heap for every signature, where a debugger, a heap dump or a memory-disclosure bug can read it. This plugin moves both the key and the signing operation into the platform keystore, so there is nothing in the process to read: on Android the keystore hands back a key handle whose getEncoded() is null, and on both platforms the secure element computes the signature.
| Platform | Implementation | Backing it can report |
|---|---|---|
| Android | AndroidKeyStore + java.security.Signature | strongBox, tee, software |
| iOS | Security framework, Secure Enclave when available | secureEnclave, keychain |
| macOS | The same Swift source as iOS | secureEnclave, keychain |
| Web | Pure-Dart pointycastle fallback | software |
| Windows / Linux | Pure-Dart fallback; no native implementation | software |
Only strongBox, secureEnclave and tee count as hardware-backed. See Platform notes & limitations for the details of each.
A fork, with credit
flutter_sign_keypair is a renamed fork of webank_secure_signer from ADORSYS-GIS/webank-mobile, released under the MIT License. See About for the details.