Skip to content
Guides

Guarantee only a device can decrypt

Mint a decrypt key inside the device's chip, encrypt to its public half on your server, and only that chip opens the result, with no call to Root Herald.

server (Node)ts
const { nonce, keyChallenge } = await rh.issueKeyChallenge({
  purpose: "decrypt",
  expectedDevices: [deviceId],
});

const key = await rh.certifyKey(nonce, certification);

await saveDecryptKey(accountOf(req), key.deviceId, key);

const envelope = encryptToDevice(key, sessionToken);
client.cc
st = RootHeraldDecrypt(key, jwe, jwe_len,
                       token, jwe_len, &token_len);

Choose your client

Use it for

  • Deliver the session or refresh token as ciphertext the device must open to use it. Device-bound accounts: an exported cookie store is ciphertext.
  • Encrypt the payload of a sensitive action to the device that approved it. Step-up: the approval and the payload belong to one chip.
  • Send a per-device configuration, VPN profile or wrapped data key to an enrolled endpoint. Workforce and BYOD: the file opens on that endpoint and nowhere else.
  • Hand a game server's session key to the one machine allowed to read it. Anti-cheat: a copied client without that chip reads nothing.

What the chip guarantees

Encryption uses the public half, so anyone holding the JWK can encrypt to the device. What the chip guarantees is that only it can open the result. An envelope that opens proves which chip holds the key; it does not say how the machine booted: run an attestation challenge for that, per session or per period.

The envelope

encryptToDevice produces one JWE compact serialization: ECDH-ES for an EC P-256 key, RSA-OAEP-256 for an RSA key, A256GCM for the content in both. A fresh ephemeral key and IV are drawn per call, so the same bytes encrypt to two different envelopes. Windows and Linux keys open it; a macOS key is format: "apple-ecies", which encryptToDevice refuses by name: encrypt to it with an Apple-side encryptor.

What each side keeps

The client keeps the key blob. The chip wrapped it, so it does not load on any other machine. Any process on this one that holds the blob can open everything ever sent to the key. Your server keeps the certified key: its jwk, alg and format are what encryptToDevice reads. Root Herald keeps the public half and the key's ID, and never any private material.

Sign then encrypt

For origin and secrecy together, mint both keys on the installation: the device signs what it sends with its sign key, and your server encrypts what it returns to the decrypt key. Two keys, two JWKs, one keyId each. The sign half is Verify data came from a device.

Rotate or stop trusting a key

Mint again. The new key replaces the old one under the same keyId; replace the key you kept, and nothing encrypted to the old one opens on the device after that. To stop sending to a device, delete its key on your side.