A secure key cannot make a photographed QR fresh.

In Practice

10 min read

Hardware-backed signing can prove which mobile key produced an offline QR. It cannot, by itself, prove that the image was created for this verifier, this place or this moment.

An offline verifier can validate a signature without contacting an issuer. That is useful, but it creates an easy category error. If a mobile device signs a QR token with a hardware-backed private key, the signature can establish that the token came from the enrolled key and was not altered. It does not establish that the token was generated for the terminal now scanning it.

Visual replay exploits that gap. An attacker records a legitimate QR as a screenshot, photograph or video frame and presents it later. The same capture may be tried at the original verifier or at another disconnected verifier. The signature remains valid in every copy because the signed bytes remain valid.

The protocol therefore has to separate three properties:

  1. Key custody: the private key is difficult to extract and can be constrained to authorized operations.
  2. Authentication intent: the holder explicitly authorizes this signing operation.
  3. Transaction freshness: the signed response is bound to a value that this verifier generated for this attempt.

Secure hardware helps with the first property and can support the second. The verifier protocol must supply the third.

Make the verifier ask a new question

The strongest offline pattern is a local challenge-response exchange. The verifier first creates a random challenge and shows it as a QR code, or sends it over NFC or Bluetooth Low Energy. The holder device reads that challenge, constructs a response, signs it, and displays a second QR for the verifier.

A compact challenge can contain:

Field Purpose
Protocol version Prevents ambiguous parsing and downgrade behavior
Verifier identifier Binds the response to the intended verifier or verifier group
Random nonce Makes this transaction distinct from previous transactions
Request digest Commits to the requested attributes and policy
Local session identifier Links the response to the verifier’s pending session
Optional expiry Bounds delay, but does not replace the nonce

The challenge should itself be authenticated with a verifier signing key, or with a MAC when the holder and verifier domain share a provisioned secret. Otherwise a malicious scanner can claim another verifier’s identifier and solicit a response for a context the holder cannot authenticate.

The nonce should come from a cryptographically secure random generator and have at least 128 bits of entropy. The verifier keeps it in a pending state and accepts it once. On success, cancellation or timeout, it removes the pending challenge. A captured response then fails because a later session has a different nonce.

The holder signs the challenge fields together with the credential response. Deterministically encoded CBOR avoids alternate byte representations, and a COSE_Sign1 structure provides a standard signature envelope. The protected input should cover the protocol version, verifier identifier, nonce, request digest, credential or holder-key reference, disclosed claims and any holder state. A signature over only the claims leaves the freshness data open to substitution. The signed object can remain inside the HPKE ciphertext from Part 1, preserving confidentiality to the verifier.

This exchange is not zero round trip. It has one verifier-to-holder message and one holder-to-verifier message, even if both are visual and both devices remain completely offline. Calling a single displayed QR both non-interactive and challenge-bound hides a contradiction: a holder cannot sign a challenge it has not received.

What Android hardware can enforce

On Android, an application can generate an EC signing key through the AndroidKeyStore provider and request setIsStrongBoxBacked(true) when the device advertises FEATURE_STRONGBOX_KEYSTORE. StrongBox supports a constrained algorithm set that includes ECDSA and ECDH on P-256. It does not imply that an X25519 or Ed25519 key is available inside StrongBox. If the requested configuration is unsupported, Android raises StrongBoxUnavailableException; a deployment must decide whether a TEE-backed fallback meets policy.

KeyInfo.getSecurityLevel() can report whether a local key is protected at the TRUSTED_ENVIRONMENT or STRONGBOX level. That local report is useful for diagnostics, but a remote or offline verifier should not trust a claim supplied by the same application it is evaluating. Android key attestation addresses enrollment: the application creates the key with an attestation challenge, obtains the certificate chain, and a trusted service validates the chain, security level, authorization list, root and revocation status. The resulting public key and validated properties can then be bound into an issuer-signed enrollment record distributed to offline verifiers.

Transaction signing can use SHA256withECDSA. A key can also require user authentication, including authorization for each operation through BiometricPrompt, depending on the key configuration. This raises confidence that malware did not silently request a signature. It still does not make an old signature expire.

Android also exposes setMaxUsageCount() from API level 31. This is a limited-use key authorization, not an application-controlled monotonic counter. Each successful private-key operation decrements the remaining uses and the key is deleted after exhaustion. Hardware enforcement varies: some devices enforce only a single use, some support larger limits, and others enforce the count in software. KeyInfo.getRemainingUsageCount() is explicitly a best-effort observation. Reusing this feature as a transaction sequence number would confuse key lifetime with protocol state and would fail across the installed device base.

What Apple hardware can enforce

On Apple platforms, CryptoKit exposes SecureEnclave.P256.Signing.PrivateKey for P-256 signing. The Security framework offers the equivalent lower-level construction with a Secure Enclave token and access-control flags. Keychain access control can require private-key usage plus user presence or a qualifying biometric policy. The public key can be enrolled so offline verifiers can validate signatures.

The public app API does not expose a general-purpose Secure Enclave monotonic counter that an application can increment and insert into each signature. Apple documents internal anti-replay protection for Secure Enclave memory and counter lockboxes used for passcode-protected data. Those platform mechanisms protect Secure Enclave state; they are not counters allocated to an arbitrary application protocol. A normal integer saved by the app, even if authenticated under a Secure Enclave key, can still be restored with an older copy unless a separate rollback-resistant service prevents it.

There is also a provisioning asymmetry. Android defines a general key-attestation certificate extension for hardware-backed keys. Apple’s App Attest establishes application integrity through a server-oriented flow. It should not be treated as a portable, offline attestation certificate for every arbitrary Secure Enclave signing key. An offline Apple deployment therefore normally relies on a trusted enrollment service to bind the application-generated public key to an issued credential or device record before the verifier goes offline.

Counters reduce some replays, not all replays

Suppose the holder signs a global counter c and increments it after every display. Verifier A stores the highest accepted value for that holder. A replay with the same or lower value is rejected at A. Verifier B, which has never seen the holder and cannot synchronize with A, has no evidence that c was already consumed. The captured token can be accepted once at every isolated verifier.

A per-verifier counter narrows disclosure and avoids unrelated verifiers racing the same sequence, but it requires the holder to maintain durable state for each verifier domain. If that state is rolled back from a backup, the holder may legitimately regenerate an old counter value. Signing the counter protects it from editing, not from restoration.

Time windows have the same structural limit. A timestamp rejects very old captures, but every copy remains usable during the acceptance window. Clock rollback on the holder, clock drift at the verifier and long offline periods complicate policy. Android notes that secure hardware usually does not enforce key-validity time because it normally lacks an independent secure real-time clock. StrongBox includes a secure timer, but that does not create a public application API for signed protocol time. A timestamp is supporting evidence, not a substitute for a verifier nonce.

Mechanism Same verifier Different disconnected verifier Principal limit
Hardware-backed signature Does not stop replay Does not stop replay Proves key use, not freshness
Short time window Narrows replay period Narrows replay period Copies work inside the window
Holder counter plus local cache Rejects seen values Usually accepts first local use Verifiers do not share state
One-time verifier challenge Rejects an old response Each verifier asks a distinct question Requires a return message
Synchronized spent-token service Rejects consumed tokens Rejects after synchronization Not strictly offline at decision time

The remaining attack is a live relay

A fresh challenge stops stored screenshots. It does not prove distance. An attacker can relay Verifier A’s challenge to a legitimate holder elsewhere, obtain a freshly signed response, and relay that response back before timeout. Camera-to-camera forwarding can be fast enough to remain inside any practical visual acceptance window.

Mitigations change the threat model rather than eliminate the physics. Bind the response to a verifier identity authenticated during provisioning. Display the requested action on the holder and require per-operation user authorization. Include a human-readable transaction summary in both devices. Use NFC or another short-range channel when physical proximity is a requirement, while recognizing that relay attacks also exist for radio protocols. Distance bounding requires specialized timing and hardware; a QR camera path is not a dependable ranging instrument.

A deployable offline profile

A field implementation can use the following boundary:

  1. During enrollment, generate the signing key in the strongest supported hardware, validate Android attestation where available, and bind the public key into an issuer-signed record. On Apple platforms, perform the equivalent trusted enrollment without claiming a portable Secure Enclave attestation that the API does not provide.
  2. Provision each verifier with issuer trust anchors, algorithm policy, enrollment records or certificate paths, and sufficiently fresh revocation material before it disconnects.
  3. For every transaction, generate a new 128-bit or larger nonce and retain it only as a pending, single-use challenge.
  4. Bind verifier identity, request digest, nonce and disclosed credential data into one canonical signed object. Require holder authorization when the risk warrants it.
  5. Record a hash of the accepted nonce and key reference, not an unnecessary copy of all disclosed attributes. Reconcile event logs when connectivity returns. Reconciliation can reveal double use across terminals, but it cannot retroactively prevent an offline decision.
  6. Treat counters and timestamps as secondary signals. Reject rollback where local durable state permits, but do not advertise fleet-wide replay prevention unless verifiers share spent-token state or each performs challenge-response.

The design result is simple. Hardware-backed keys make copying the private key harder. A verifier-generated challenge makes copying the picture useless after the session. Across disconnected verifier terminals, a signed counter alone cannot create global knowledge. The system must either ask a fresh local question, synchronize state, or accept a measurable replay window.

Context and scope

This analysis abstracts the replay threat model of an offline field implementation. The commissioning organisation, credential profile, hardware fleet and operating locations are omitted. Platform capability statements are limited to the cited public interfaces and security documentation.

References

  1. Android Developers, Android Keystore system
  2. Android Developers, Verify hardware-backed key pairs with key attestation
  3. Android Developers, KeyGenParameterSpec.Builder
  4. Android Developers, KeyInfo
  5. Apple Developer Documentation, SecureEnclave.P256.Signing.PrivateKey
  6. Apple Developer Documentation, Protecting keys with the Secure Enclave
  7. Apple Platform Security, The Secure Enclave
  8. Apple Developer Documentation, Establishing your app’s integrity with App Attest
  9. NIST SP 800-63B, Authentication and Authenticator Management
  10. IETF RFC 8949, Concise Binary Object Representation
  11. IETF RFC 9052, CBOR Object Signing and Encryption

Related Articles

Open rugged credential scanner showing its NFC coil, camera module and printed radio antenna

The payload must fit the channel.

A compact signed object needs one deterministic binary form and three explicit transport profiles. QR, NFC and Bluetooth LE fail in different ways when signature and key material cross their byte budgets.

Glass fiber strands converging through a machined metal optical coupler on a pale workbench

A selective proof is not automatically smaller.

When attributes require independent signatures, BBS can replace N signatures with one multi-message signature, but its selective-disclosure proof grows with every hidden message. The useful crossover depends on what the comparison actually carries.

Close-up of an open hardware security module, loose chip packages, a precision caliper and a fiber lead on a pale metrology bench

The signature is not the key.

A byte-level comparison of RSA, elliptic-curve, pairing-based and post-quantum signatures shows when recurring signature traffic overtakes key provisioning and storage.