Skip to content

Hybrid Encryption with Kyber and AES --- Implementation Overview

Scope: this page explains the generic KEM + AES-GCM hybrid pattern. ANKASecure's wire format is JWE (RFC 7516) — compact for SIMPLE keys, General JSON for composite keys — not the byte layout below, and the private key is unwrapped under the tenant KEK inside the service, never handed to the caller.


📌 1. High‑Level Behavior

This implementation follows a standard hybrid encryption pattern:

  1. The Kyber public key is retrieved from a keystore using a kid (key identifier).

  2. A fresh, ephemeral AES‑256 key is generated for the current encryption operation.

  3. The plaintext is encrypted with AES/GCM using that ephemeral AES key.

  4. The AES key is wrapped (encapsulated) with the Kyber public key.

Output layout:

[wrappedKey length (2 bytes)] [wrappedKey (AES key wrapped with Kyber)]
[IV length (1 byte)]          [IV (for AES-GCM)]
[ciphertext (AES-GCM encrypted data)]

📌 2. Meaning of wrappedKey

wrappedKey is the ephemeral AES key used to encrypt the data, after being encapsulated with the Kyber public key. It is not the Kyber public key itself.

During decryption, the receiver uses the Kyber private key to unwrap (unwrap) this value and recover the original AES key.


📌 3. Detailed Flow

Encryption Path

  • Retrieve Kyber public key from the keystore via kid.

  • Generate a new AES‑256 key (ephemeral, one per encryption).

  • Encrypt the payload with AES/GCM using that AES key.

  • Wrap the AES key with the Kyber public key.

  • Serialize the result as: wrappedKeyLen + wrappedKey + ivLen + iv + ciphertext.

Decryption Path

  1. Read wrappedKey from the serialized input.

  2. Use the Kyber private key to unwrap it and obtain the AES key.

  3. Initialize AES/GCM with the recovered AES key and the IV.

  4. Decrypt the ciphertext.


📌 4. Why the AES Key Is Never Sent in the Clear

  • The AES key is generated locally and then encapsulated using the Kyber public key.

  • Only the holder of the corresponding Kyber private key can recover (unwrap) that AES key.

  • As a result, the AES key never travels unprotected.


📌 5. Common Source of Confusion

A frequent misunderstanding is believing the AES key must be transmitted or stored explicitly. In this design, it is always transported wrapped with Kyber, ensuring only the intended recipient can retrieve it.

This operation is typically implemented with a call similar to:

byte[] wrappedKey = wrapSecretKey(publicKey, aesKey);

📌 6. Reference Decryption Logic (Pseudo-code)

// 1. Retrieve Kyber private key
PrivateKey privateKyberKey = keyManagementService.retrievePrivateKeyForDecryption(kid);

// 2. Extract wrappedKey from the serialized payload
byte[] wrappedKey = readWrappedKeyFromInput(...);

// 3. Unwrap (decapsulate) the AES key
SecretKey aesEphemeral = unwrapSecretKey(privateKyberKey, wrappedKey);

// 4. Decrypt using AES/GCM
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
aesCipher.init(Cipher.DECRYPT_MODE, aesEphemeral, ivSpec);
byte[] plaintext = aesCipher.doFinal(ciphertext);

✅ 7. End‑to‑End Summary

Step Operation Description
1 Generate AES‑256 key Fresh, ephemeral per message
2 Encrypt with AES/GCM Data protected symmetrically
3 Wrap AES key Encapsulated with Kyber public key
4 Serialize output [wrappedKey + IV + ciphertext] (+ lengths)
5 Unwrap AES key Kyber private key recovers the AES key
6 Decrypt payload AES/GCM with recovered key

📌 Final Notes

If issues appear during decryption, verify the sequence:

  1. Read **wrappedKey**.

  2. Unwrap it with the Kyber private key.

  3. Decrypt with AES/GCM using the recovered key.

For further clarification (e.g., parsing/serialization helpers, specific code listings, or Markdown flavor adjustments), extend this document or provide the relevant code sections.