
A Kroxylicious filter plugin that provides transparent post-quantum (ML-KEM + AES-256-GCM) record-level encryption for Apache Kafka, requiring zero client code changes.
A Kroxylicious filter plugin that provides transparent Post-Quantum Cryptography (PQC) record-level encryption for Apache Kafka using ML-KEM (FIPS 203) key encapsulation with AES-256-GCM symmetric encryption.
Kafka producers and consumers require zero code changes. The Kroxylicious proxy intercepts traffic and encrypts on Produce / decrypts on Fetch automatically.
Producer ──plaintext──> Kroxylicious ──encrypted──> Kafka Broker
Consumer <──plaintext── Kroxylicious <──encrypted── Kafka Broker
Classical key establishment algorithms (RSA, ECDH) are vulnerable to future quantum computers. If per-record encryption keys are established using a classical KEM, a quantum adversary could recover those keys from the encapsulations stored alongside the ciphertext on the broker.
This plugin uses ML-KEM (FIPS 203) for quantum-resistant key encapsulation, ensuring that data at rest on the Kafka broker cannot be decrypted even by an adversary with a cryptographically relevant quantum computer.
Note: This filter protects data at rest on the broker, not the TLS channel in transit. See THREAT_MODEL.md for a full analysis of what is and is not defended against.
| Standard | Algorithm | Purpose in this plugin |
|---|---|---|
| FIPS 203 | ML-KEM (Kyber) | Key encapsulation - securely establishes a per-message AES key |
| N/A | AES-256-GCM | Symmetric authenticated encryption of the record payload |
| N/A | X25519 ECDH | Classical key agreement for hybrid mode defense-in-depth |
x-pqc-encrypted header - marks encrypted records for downstream awarenessKeyProvider SPI supports filesystem (default) and HashiCorp Vault backends| Requirement | Version |
|---|---|
| JDK | 17+ (21+ recommended) |
| Maven | 3.8+ |
| Kroxylicious | 0.19.0 |
| Apache Kafka | 3.9.x |
git clone <this-repo>
cd kroxylicious-pqc-filter
mvn clean package -DskipTests
The shaded JAR at target/kroxylicious-pqc-filter-1.0.0-SNAPSHOT.jar bundles
Bouncy Castle so it can be dropped into Kroxylicious with no extra dependencies.
To include HashiCorp Vault key provider support, build with the vault profile:
mvn clean package -Pvault -DskipTests
This bundles spring-vault-core and the VaultKeyProvider into the JAR.
java -cp target/kroxylicious-pqc-filter-1.0.0-SNAPSHOT.jar \
io.kroxylicious.filter.pqc.PqcKeyGeneratorCli \
ML_KEM_768 \
/etc/kroxylicious/pqc/
Output:
Generating ML-KEM-768 key pair...
Public key: /etc/kroxylicious/pqc/pqc-public.der
Size: 1206 bytes
Format: X.509
Private key: /etc/kroxylicious/pqc/pqc-private.der
Size: 2498 bytes
Format: PKCS#8
Alternatively, omit the key paths in configuration and the filter will generate keys automatically on first startup.
Add the filter to your Kroxylicious proxy YAML configuration:
filterDefinitions:
- name: pqc-encryption
type: PqcRecordEncryptionFilterFactory
config:
kemAlgorithm: ML_KEM_768
hybridMode: true
publicKeyPath: /etc/kroxylicious/pqc/pqc-public.der
privateKeyPath: /etc/kroxylicious/pqc/pqc-private.der
topicPatterns:
- "sensitive-.*"
- "pii-.*"
defaultFilters:
- pqc-encryption
Place the JAR in a directory accessible to Kroxylicious and add it to the
classpath via the KROXYLICIOUS_CLASSPATH environment variable:
export KROXYLICIOUS_CLASSPATH="/opt/kroxylicious/plugins/*"
When using Docker, set it in your container environment:
environment:
KROXYLICIOUS_CLASSPATH: /opt/kroxylicious/plugins/*
Then start the proxy. Producers and consumers connect to the proxy port instead of the broker directly.
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
kemAlgorithm | enum | No | ML_KEM_768 | ML-KEM parameter set. One of ML_KEM_512, ML_KEM_768, ML_KEM_1024. |
hybridMode | boolean | No | true | Combine ML-KEM with X25519 ECDH for defense-in-depth. |
publicKeyPath | string | Filesystem only | - | Filesystem path to the ML-KEM public key (X.509 DER encoded). |
privateKeyPath | string | Filesystem only | - | Filesystem path to the ML-KEM private key (PKCS#8 DER encoded). |
topicPatterns | list<string> | No | [".*"] | Java regex patterns. Only records in matching topics are encrypted/decrypted. |
keyProviderType | string | No | filesystem | Key storage backend. One of filesystem, vault. |
keyProviderConfig | map<string, string> | Vault only | {} | Backend-specific configuration (see Vault section below). |
Filesystem (keyProviderType: filesystem, default):
Loads ML-KEM keys from DER files on disk. If the files do not exist, generates
a fresh key pair and saves them. Requires publicKeyPath and privateKeyPath.
HashiCorp Vault (keyProviderType: vault, requires -Pvault build):
Fetches ML-KEM keys from a Vault KV v2 secrets engine. Keys are stored as
base64-encoded DER in publicKey and privateKey fields. Vault secret versions
map to key IDs for key rotation support.
Vault keyProviderConfig properties:
| Property | Required | Default | Description |
|---|---|---|---|
vaultAddress | Yes | VAULT_ADDR env | Vault server URL (e.g., http://vault:8200) |
vaultToken | For token auth | VAULT_TOKEN env | Vault authentication token |
secretPath | Yes | -- | Path within the secrets engine (e.g., kroxylicious/pqc) |
secretEngine | No | secret | KV v2 secrets engine mount name |
authMethod | No | token | Auth method: token, approle, or kubernetes |
roleId | For approle | -- | AppRole role ID |
secretId | For approle | -- | AppRole secret ID |
kubeRole | For kubernetes | -- | Kubernetes auth role name |
kubeTokenPath | No | /var/run/secrets/.../token | Service account token file path |
Example Vault configuration:
filterDefinitions:
- name: pqc-encryption
type: PqcRecordEncryptionFilterFactory
config:
kemAlgorithm: ML_KEM_768
hybridMode: false
keyProviderType: vault
keyProviderConfig:
vaultAddress: http://vault:8200
vaultToken: my-token
secretPath: kroxylicious/pqc
topicPatterns:
- "sensitive-.*"
| Algorithm | Security Level | Public Key | Private Key | Ciphertext Overhead | Use Case |
|---|---|---|---|---|---|
| ML-KEM-512 | 128-bit | 822 B | 1,730 B | ~854 B | Lightweight, IoT |
| ML-KEM-768 | 192-bit | 1,206 B | 2,498 B | ~1,174 B | Recommended default |
| ML-KEM-1024 | 256-bit | 1,590 B | 3,266 B | ~1,654 B | Classified / long-lived data |