Prompt and context
A multi-tenant configuration service must encrypt sensitive database values. Each tenant has an independent logical key; the platform must rotate root keys, read old ciphertext, and detect ciphertext copied across tenants or records. Use Go 1.26 crypto/hpke to design the envelope, encrypt/decrypt flow, key versions, context binding, and migration. The core skill is correct cryptographic API composition and lifecycle design, so this is a coding question.
What the interviewer evaluates
First, whether you understand the roles of HPKE KEM, KDF, and AEAD and distinguish public-key encapsulation from symmetric data encryption.
Second, whether you can choose Base, PSK, or Auth mode and explain the boundary between authentication keys and confidentiality keys.
Third, whether tenant, purpose, suite, and version are bound in info or AAD to prevent cross-context decryption.
Fourth, whether rotation, old-version reads, revocation, and re-encryption are designed without treating a version number as secret.
Fifth, whether tamper, replay, randomness, failure-information, and interoperability tests validate the implementation.
Questions to clarify first
- Is confidentiality enough, or must the sender be cryptographically authenticated?
- Who owns the recipient public key, and are KMS, HSM, and audit requirements present?
- How long do old ciphertexts remain, and is background re-encryption required?
- Are tenant and configuration-key relationships allowed to be visible metadata?
- Is cross-language decryption or offline recovery required?
- Should missing keys, unsupported versions, and authentication failures be distinguishable?
A 30-second answer
“I pin an RFC 9180 suite and Go 1.26. The service keeps each tenant’s recipient private key, while applications encapsulate with its public key. The envelope stores suite and key version; tenant and purpose are bound in info and tenant, key, and record version in AEAD AAD. Decryption selects the old private key by envelope version. New writes use the rotated version and a background job re-encrypts old data. I choose Auth only when sender proof is required. Tests cover tampering, cross-tenant swaps, replay, randomness, old versions, and cross-language vectors.”
Detailed solution
Step 1: Separate the envelope layers
HPKE establishes a shared secret with KEM, derives keys with KDF, and encrypts the message with AEAD. The envelope stores suite, key version, encapsulated material, nonce or serialized ciphertext, and public metadata. Root and recipient private keys stay in KMS or HSM, never in ciphertext or application logs.
Step 2: Choose mode and identity semantics
Base mode gives recipient public-key encryption without sender authentication. PSK adds a pre-shared key. Auth uses a sender key to authenticate the source. A platform login identity does not mean HPKE authenticated the sender; authorization and cryptographic mode are separate layers.
Step 3: Bind the context
Bind protocol, service, version, and purpose in info. Bind tenant ID, configuration key, and record version in AEAD AAD when those fields travel with the envelope and must remain intact. Decryption recomputes the same inputs, so a tenant, purpose, or version swap fails authentication.
envelope = suite_id | key_version | encapsulated_key | aad_fields | ciphertext
info = "config-envelope/v1" | tenant_scope | purpose
aad = tenant_id | config_key | record_versionStep 4: Design the key lifecycle
Each key version has creation time, state, decryption scope, and destruction policy. Publish the new public key before switching writes; reads select the old private key by envelope version. Keep old versions read-only until re-encryption and audit checks complete, then revoke them.
Step 5: Handle replay and serialization
AEAD detects tampering but does not stop a valid old ciphertext from being submitted again. If configuration has a version or monotonic sequence, the business layer checks the expected version. Use explicit length, version, and suite fields and reject unknown suites, truncation, or duplicate fields.
Step 6: Define the error boundary
Externally return a uniform decryption failure. Internally audit structured reason and key version, never private keys, plaintext, or complete ciphertext. Distinguishing missing key, unsupported format, and authentication failure helps operations, but untrusted callers should not learn tenant or key existence from timing or messages.
Step 7: Build tests and migration
Test fixed RFC vectors, randomness, single-byte tampering, cross-tenant AAD, old-version reads, concurrent rotation, post-revocation failure, and large-message chunking. Cross-language systems need shared serialization and vectors. Pre-1.26 services use a compatibility wrapper or upgrade gate; experimental APIs do not enter production by accident.
A high-quality sample answer
“I use HPKE as an envelope layer: KEM and KDF establish and derive a shared key, and AEAD protects configuration plaintext. The envelope contains suite, key version, encapsulated material, and ciphertext; tenant, purpose, key, and record version bind through info and AAD to stop cross-tenant copies. Base gives recipient confidentiality only; I choose Auth or an outer signature when sender proof is required. Rotation writes the new version first, keeps the old one read-only, re-encrypts in the background, then revokes it after verification. AEAD does not prevent replay, so the business version is checked. Private keys remain in KMS/HSM, and tests include tampering, revocation, errors, and cross-language vectors.”
Common mistakes
- Treating Base as sender authentication → the recipient cannot prove who sent it → use Auth or an outer signature.
- Putting tenant only in visible metadata → an attacker can swap ciphertext across tenants → bind it in
infoor AAD. - Deleting the old key immediately after rotation → historical data becomes unreadable → keep a read-only window and re-encrypt first.
- Assuming AEAD prevents replay → a valid old ciphertext can be submitted again → check a business version or sequence.
- Storing private keys in config or logs → the encryption boundary is broken → use KMS/HSM and least privilege.
- Inventing an unversioned binary format → migration and suite rejection become unsafe → encode explicit version, length, and suite.
- Returning detailed decryption errors → tenant or key existence may leak → uniform external errors and internal audit.
- Testing only success → tampering and old-version failures reach production → add vectors, cross-context, and revocation tests.
Follow-up questions
Follow-up 1: Why not encrypt all plaintext directly with RSA or ECDH?
HPKE explicitly composes KEM, KDF, and AEAD, which suits encapsulating a symmetric session key and handling large messages. Direct public-key encryption of arbitrary plaintext invites length, randomness, and mode misuse.
Follow-up 2: When is Auth mode appropriate?
Use Auth when the recipient needs cryptographic proof that a known sender key participated. If only recipient confidentiality is required, do not add an unmanaged authentication key.
Follow-up 3: What is the difference between info and AAD?
info participates in key derivation and defines protocol context. AAD remains visible but is integrity-protected by AEAD, making it suitable for envelope metadata that must match during decryption.
Follow-up 4: How do you re-encrypt safely?
Read the old version and write the new one in an idempotent transaction or retryable workflow. Only revoke the old key after the new ciphertext passes decrypt-and-read-back and audit checks.
Follow-up 5: How do you stop a ciphertext moving to another record?
Put tenant, configuration key, record version, and purpose in AAD, and rebuild AAD from current database values during reads. A copied envelope then fails its authentication tag.
Follow-up 6: Does HPKE provide forward secrecy?
The property depends on key lifecycle and mode. With a long-lived static recipient private key, historical ciphertext protection depends on that key. Stronger forward secrecy needs ephemeral keys, rotation, destruction, and protocol-level sessions; the API name alone does not provide it.