Skip to content

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:

  1. Stable KID — the long-lived identity an application references (customer-data-key). Immutable for the life of the identity.
  2. 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_kv claim.
  3. 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_DECRYPT or SIGN_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 / :pqc suffix. 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 own MAX_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 version integer (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_materials and key_material_components for 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

* restricted content — see higher-tier documentation