Keys arrive in a client in one of two ways
An embedded key is a literal value compiled into the app: a byte array, a base64 string, or a constant in a native library. The app reads it at startup and uses it for every request. An embedded key cannot follow a rotation, because the only way to update it is to ship a new build. A client that copies the embedded value inherits the same limit.
A retrieved key is fetched at runtime from an endpoint, a settings response, or a key set packaged with an access token. The app holds it in memory, uses it to sign, and refetches when the value it has stops being accepted. The distinction matters before writing any code, because for the first kind the question is where the value lives in the APK, and for the second it is how the refresh path works.
What sets the rotation window
The window is the period during which both the old key and the new key are accepted. A provider introduces the new key, publishes it, and retires the old one later so that clients which refresh on their own schedule keep working through the change.
The length of that overlap usually follows the lifetime of the credentials the client already holds. A system whose tokens last an hour can publish a key with a similar overlap and expect every live client to refresh inside it. A system whose tokens last weeks can afford a longer overlap, but its clients also refresh less often, which is why a rotation can pass unnoticed until it hurts. Where the overlap and the refresh interval disagree, the failure shows up as a wave of refusals that a restart does not fix.
How a client learns about a new key
Clients learn about rotation through one of a few published mechanisms, and a client that has none of them cannot sign after the change.
- The service publishes a key set. An endpoint returns the current keys with identifiers, and the client fetches it at startup and on a schedule.
- The response names the key. A key identifier appears in a request header or a response field, so the client can select the matching key instead of trying each one.
- The hint travels inside the signature. Some schemes sign the key identifier together with the payload, which binds the request to one key and makes selection unambiguous.
- The app refreshes on a timer. The value is refetched every so often regardless of failures, which keeps the window from ever being reached.
When a key identifier travels outside the signed material it carries no security weight, because anyone can rewrite it. Its role is routing: it tells the server which key to look up, and it saves a rejected request.
What rotation does to a captured signature
A captured signature stops verifying once the key that produced it is retired, and nothing you do to the captured bytes changes that. The signature is a function of the request and the key, so without the same key the same bytes cannot be reproduced. If the capture was made through a proxy and replayed while the old key was still live, it works during the overlap and fails afterwards, which gives the misleading impression that something else changed.
Rotation separates a capture from a client. Any approach built on replaying stored signatures has a lifetime measured by the rotation window, and the schedule belongs to the server. Building a client means generating a signature for each request with the current key, which in turn means recovering the signing routine rather than the artifact it once produced.
Recovering the key material itself is a separate problem, and one that only applies when the key lives on the device. Where the app calls a code path that derives the key from a server response, the same call is available to a client that holds the session. Where the app stores a fixed secret, that secret is in the APK and has to be found. Where the key never leaves a hardware-backed store, the signing step has to be examined on the device rather than copied out of it.
Making a client survive rotation
A client that lasts has four properties, and they are all cheap to build once the scheme is known.
- It holds the key in a variable with a refresh path, rather than a constant in the source.
- It refetches on a schedule shorter than the shortest rotation window the provider has used.
- It looks up the key by identifier when a response says which key the server expected.
- It retries once after a refresh when a signature is refused, and treats the second refusal as a real failure.
The retry has to be bounded, because a refresh loop that fires on every 401 turns a wrong endpoint into a flood of requests. A single refetch and one resend is enough to cover the rotation case, and anything further is a different fault.
Two variants raise the work. A provider that rotates on a schedule only the server knows is still workable, because the refresh path handles it. A provider that hands the app a per-installation key after a registration or attestation step is harder, because the key becomes device state and the client has to reproduce the enrolment flow before it can sign anything.
Related work
Reviewed 28 September 2026 · SReverse research desk