Key Identifier (KID) System Explained
Target Audience: Developers, Integration Engineers, Operations Engineers
Reading Time: 10 minutes
Prerequisites: Basic understanding of key management, multi-tenancy
Executive Summary
ANKASecure separates a cryptographic identity into three explicit concepts:
- Stable KID — the long-lived identity an application references (
customer-data-key). Immutable for the life of the identity. - Material Version — one physical key blob in the lineage of that identity. Versions increment as the identity rotates; the wire-visible version is the
ank_kvclaim. - Algorithm — the cryptographic algorithm bound to a single material version. It can evolve between versions (RSA → ML-KEM) within an invariant purpose.
The application only ever knows the Stable KID. The platform manages the lineage of materials and the algorithm of each version internally. This is what makes a crypto-agility migration transparent: the client never rediscovers a rotated key and never sees a composite component.
This is a structural differentiator versus AWS KMS, GCP Cloud KMS, Azure Key Vault, HashiCorp Vault Transit, and Thales CipherTrust — all of which fix the algorithm at key creation.
The three-concept model
┌─────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER (what the client knows) │
│ │
│ Stable KID: "customer-data-key" purpose: ENCRYPT_DECRYPT │
└─────────────────────────────────────────────────────────────┘
│ lineage of material versions
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ v1 │ │ v2 │ │ v3 │ (ank_kv on the wire)
│ RSA-OAEP│ │ML-KEM768│ │ML-KEM1024│
│ REVOKED │ │ RETIRED │ │ PRIMARY │ (MaterialStatus)
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
material_uuid material_uuid material_uuid (internal, never on wire)
| Concept | Visible to client? | Mutable? | Carries |
|---|---|---|---|
| Stable KID | YES (used exclusively) | Immutable | purpose, status, pointer to PRIMARY material |
| Material Version | Only as the ank_kv integer in JOSE tokens it receives | New versions added by rotation | algorithm, wrapped private key, status |
| Algorithm | Indirectly (in the JOSE alg of returned tokens) | Per material version | NIST standard, key ops |
| material_uuid | NEVER | — | internal PK only |
Stable KID (external identity)
Characteristics:
- User-defined string (e.g.
customer-data-key), human-readable. - Scoped to tenant — the same Stable KID can exist in multiple tenants.
- Does not reveal the algorithm (crypto-agility).
- Carries an invariant purpose:
ENCRYPT_DECRYPTorSIGN_VERIFY. - Format:
VARCHAR(255), regex^(?!\.+$)[A-Za-z0-9._-]{1,255}$: letters, digits and. _ -. The colon (:) is not permitted, because no identifier on the wire ever carries a:classical/:pqcsuffix. An identifier made only of dots (.,..) is refused with a 400 naming the field: as a segment of/keys/{kid}/...it is an RFC 3986 dot segment, which clients remove before sending, so every per-key call would reach the parent path. The bound is 255, the same number as the column and as the server's ownMAX_KID_LENGTH.
Examples:
| Stable KID | Purpose |
|---|---|
payment-encryption | ENCRYPT_DECRYPT |
document-signing | SIGN_VERIFY |
regulatory-eu-hybrid-attestation | SIGN_VERIFY (composite) |
Purpose
The purpose is fixed at creation and constrains which capabilities and algorithm families apply for the whole life of the identity:
| Purpose | Capabilities | Algorithm families |
|---|---|---|
ENCRYPT_DECRYPT | ENCRYPT, DECRYPT, REENCRYPT | ML-KEM, RSA-OAEP, HQC, X25519/X448, AES-GCM, ChaCha20-Poly1305, composite KEM combine |
SIGN_VERIFY | SIGN, VERIFY, RESIGN | ML-DSA, SLH-DSA, FALCON, Ed25519/Ed448, ECDSA, HMAC, SM2, XMSS/LMS, composite signature |
When the algorithm has unambiguous key ops (e.g. ML-KEM, ML-DSA), the server derives the purpose automatically. When the algorithm is ambiguous (RSA, EC — both encrypt/decrypt and sign/verify), the creation request MUST specify purpose explicitly, otherwise it is rejected with HTTP 422 purpose-required.
Material Version (physical key blob)
Rotation creates a new material version under the same Stable KID — never a new identifier. Each version:
- has its own
versioninteger (monotonic, gaps allowed after destruction), - has its own algorithm (constrained by the purpose),
- has its own HSM-KEK-wrapped private key,
- has its own MaterialStatus* (
PENDING_PRIMARY/PRIMARY/RETIRED/REVOKED/PENDING_DESTRUCTION/DESTROYED).
Exactly one material is PRIMARY while the alias is active. Encrypt/sign always resolve to that PRIMARY material; decrypt/verify resolve to the version named by the token's ank_kv.
Algorithm abstraction & crypto-agility
Because the algorithm is a property of a material version rather than the identity, a migration is just a rotation to a new version with a different algorithm in the same purpose:
customer-data-key (ENCRYPT_DECRYPT)
v1 RSA-OAEP → REVOKED (legacy, historical decrypt still works until destroyed)
v2 ML-KEM-768 → RETIRED (superseded, historical decrypt works)
v3 ML-KEM-1024 → PRIMARY (new encrypt/decrypt)
The application keeps encrypting against customer-data-key. New ciphertext carries ank_kv: 3; old ciphertext carries ank_kv: 1 or 2 and stays decryptable until the corresponding material is destroyed.
Tenant isolation
Every resolution is scoped by tenant_id (from the JWT claim). The uniqueness constraint UNIQUE (tenant_id, stable_kid), which covers destroyed keys too so that a Key ID is never reused within its tenant, allows the same Stable KID string to exist independently in different tenants with no collision.
Internal UUID
Each material blob is stored under an opaque material_uuid. This UUID:
- is never exposed on the wire,
- is bound into the AEAD additional authenticated data (AAD) so a blob cannot be substituted across materials,
- is the join key between
key_materialsandkey_material_componentsfor composite keys.
See Stable KID / Material Version Resolution* for the resolution mechanics (Redis hot path, PostgreSQL cold path).
Composite identities are opaque to the client
A composite key (P-256+ML-KEM-768, brainpoolP384r1+ML-DSA-87) lives inside one material version. Its components are not separate identities and never appear on the wire as sub-kids. A composite JOSE token carries the same Stable KID in every signature/recipient protected header; server-side dispatch is by index. See Composite Hybrid Keys API*.
Target Audience: Developers, Integration Engineers, Operations Engineers