
クライアントコードの変更を一切必要とせず、Apache Kafka向けに透過的な耐量子(ML-KEM + AES-256-GCM)レコードレベル暗号化を提供するKroxyliciousフィルタープラグイン。
Kroxylicious フィルタープラグイン。透過的な 耐量子暗号 (PQC) レコードレベル暗号化を Apache Kafka 向けに提供し、 ML-KEM (FIPS 203) 鍵カプセル化と AES-256-GCM 対称暗号化を使用します。
Kafka プロデューサーとコンシューマーは コード変更が一切不要 です。Kroxylicious プロキシが トラフィックをインターセプトし、Produce 時に暗号化し、Fetch 時に自動的に復号化します。``` Producer ──plaintext──> Kroxylicious ──encrypted──> Kafka Broker Consumer <──plaintext── Kroxylicious <──encrypted── Kafka Broker
## Kafka に PQC を使う理由
古典的な鍵確立アルゴリズム(RSA、ECDH)は、将来の量子コンピュータに対して脆弱です。レコード単位の暗号化鍵を古典的な KEM で確立すると、量子敵対者はブローカー上で暗号文と共に保存されたカプセル化データからその鍵を回復できてしまいます。
このプラグインは、量子耐性のある鍵カプセル化に ML-KEM(FIPS 203)を使用し、暗号学的に関連する量子コンピュータを持つ敵対者であっても、Kafka ブローカー上の保存データを復号できないようにします。
**注記:** このフィルタは、ブローカー上の**保存データ**を保護するものであり、転送中の TLS チャネルを保護するものではありません。防御対象とその範囲外の完全な分析については、[THREAT_MODEL.md](https://github.com/oscerd/kroxylicious-pqc-filter/blob/HEAD/THREAT_MODEL.md) を参照してください。
| 標準 | アルゴリズム | このプラグインでの用途 |
|----------|-----------|------------------------|
| FIPS 203 | ML-KEM (Kyber) | 鍵カプセル化 - メッセージごとの AES 鍵を安全に確立する |
| N/A | AES-256-GCM | レコードペイロードの対称認証付き暗号化 |
| N/A | X25519 ECDH | ハイブリッドモードの多層防御のための古典的鍵合意 |
## 特長
- **透過的な暗号化/復号** - クライアント側の変更は不要
- **ML-KEM-512、ML-KEM-768(デフォルト)、ML-KEM-1024** パラメータセット
- **ハイブリッドモード**(デフォルト)- ML-KEM + X25519 ECDH を組み合わせるため、両方を破る必要がある
- **レコード単位の暗号化** - 各レコードは新しい KEM カプセル化とランダムな IV を取得する
- **トピックフィルタリング** - 正規表現パターンで暗号化するトピックを選択
- **改ざん検出** - AES-GCM 認証付き暗号化により改変された暗号文を拒否
- **意味論的セキュリティ** - 同一の平文でも異なる暗号文が生成される(IND-CCA2)
- **鍵の自動生成** - 初回起動時に ML-KEM 鍵が存在しない場合、生成して保存
- **`x-pqc-encrypted` ヘッダー** - 暗号化済みレコードを下流に知らせるためのマーカー
- **プラグ可能な鍵プロバイダー** - `KeyProvider` SPI がファイルシステム(デフォルト)と HashiCorp Vault バックエンドをサポート
## 前提条件
| 要件 | バージョン |
|-------------|---------|
| JDK | 17+(21+ 推奨) |
| Maven | 3.8+ |
| Kroxylicious | 0.19.0 |
| Apache Kafka | 3.9.x |
## クイックスタート
### 1. プラグインをビルドする```bash
git clone <this-repo>
cd kroxylicious-pqc-filter
mvn clean package -DskipTests
target/kroxylicious-pqc-filter-1.0.0-SNAPSHOT.jar にあるシャドーJAR は、Bouncy Castle をバンドルしているため、追加の依存関係なしで Kroxylicious にそのまま配置できます。
HashiCorp Vault キープロバイダーのサポートを含めるには、vault プロファイルでビルドします:```bash
mvn clean package -Pvault -DskipTests
これは `spring-vault-core` と `VaultKeyProvider` を JAR にバンドルします。
### 2. ML-KEM 鍵を生成する```bash
java -cp target/kroxylicious-pqc-filter-1.0.0-SNAPSHOT.jar \
io.kroxylicious.filter.pqc.PqcKeyGeneratorCli \
ML_KEM_768 \
/etc/kroxylicious/pqc/
I'm ready to translate chunk 9 of 33, but the input content appears to be empty. Please provide the Markdown text you'd like me to translate into Japanese.``` 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
または、設定内のキーパスを省略すると、初回起動時にフィルタが自動的にキーを生成します。
### 3. Kroxylicious を設定する
フィルタを Kroxylicious プロキシの YAML 設定に追加します:```yaml
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
Kroxylicious がアクセスできるディレクトリに JAR を配置し、それを
KROXYLICIOUS_CLASSPATH 環境変数経由でクラスパスに追加します:```bash
export KROXYLICIOUS_CLASSPATH="/opt/kroxylicious/plugins/*"
Dockerを使用する場合は、コンテナ環境で設定してください:```yaml
environment:
KROXYLICIOUS_CLASSPATH: /opt/kroxylicious/plugins/*
次にプロキシを起動します。プロデューサーとコンシューマーは、プロキシポートに接続します。 ブローカーに直接接続するのではなく。
ファイルシステム(keyProviderType: filesystem、デフォルト):
ディスク上の DER ファイルから ML-KEM 鍵を読み込みます。ファイルが存在しない場合は、
新しい鍵ペアを生成して保存します。publicKeyPath と privateKeyPath が必要です。
HashiCorp Vault(keyProviderType: vault、-Pvault ビルドが必要):
Vault KV v2 シークレットエンジンから ML-KEM 鍵を取得します。鍵は、
publicKey および privateKey フィールドに base64 エンコードされた DER として保存されます。Vault のシークレットバージョンは、
鍵ローテーションをサポートするために鍵 ID にマッピングされます。
Vault の keyProviderConfig プロパティ:
Vault 設定の例:```yaml filterDefinitions:
### ML-KEM パラメータセット
| アルゴリズム | セキュリティレベル | 公開鍵 | 秘密鍵 | 暗号文オーバーヘッド | ユースケース |
|-----------|---------------|------------|-------------|--------------------:|----------|
| ML-KEM-512 | 128ビット | 822 B | 1,730 B | ~854 B | 軽量、IoT |
| ML-KEM-768 | 192ビット | 1,206 B | 2,498 B | ~1,174 B | **推奨デフォルト** |
| ML-KEM-1024 | 256ビット | 1,590 B | 3,266 B | ~1,654 B | 機密/長期保存データ |
### 暗号化モード
**PQCのみ** (`hybridMode: false`):
ML-KEMのみを使用します。AES-256キーはML-KEM共有秘密から
`SHA-256(0x01 || "kroxylicious-pqc-v1" || secret)` を介して導出されます。
**ハイブリッド** (`hybridMode: true`, デフォルト):
ML-KEM + X25519を組み合わせます。AES-256キーは両方の秘密から
`SHA-256(0x02 || "kroxylicious-pqc-hybrid-v1" || pqcSecret || x25519Secret)` を介して導出されます。
これにより、一方のアルゴリズムが破られた場合でもセキュリティが確保されます。
## 暗号化エンベロープ形式
暗号化された各レコード値はバイナリエンベロープに置き換えられます:```
PQC-only (version 0x01):
+--------+-----------+--------------------+--------+---------------------------+
| 1 byte | 2 bytes | N bytes | 12 B | remaining |
| 0x01 | encap len | ML-KEM encapsulat. | AES IV | AES-GCM ciphertext + tag |
+--------+-----------+--------------------+--------+---------------------------+
Hybrid (version 0x02):
+--------+-----------+--------------------+---------+--------+-----------------+
| 1 byte | 2 bytes | N bytes | 32 B | 12 B | remaining |
| 0x02 | encap len | ML-KEM encapsulat. | X25519 | AES IV | AES-GCM ct+tag |
| | | | eph pub | | |
+--------+-----------+--------------------+---------+--------+-----------------+
バージョンバイトにより、復号化ツールは設定なしでモードを判別できます。
kroxylicious-pqc-filter/ src/main/java/io/kroxylicious/filter/pqc/ PqcRecordEncryptionFilterFactory.java # FilterFactory entry point (@Plugin) PqcRecordEncryptionFilter.java # ProduceRequestFilter + FetchResponseFilter PqcKeyGeneratorCli.java # CLI key generation utility config/ PqcEncryptionConfig.java # Jackson-deserialized configuration POJO crypto/ KeyProvider.java # SPI interface for pluggable key backends FileSystemKeyProvider.java # Default: loads/generates keys from disk PqcCryptoEngine.java # ML-KEM encapsulation + AES-256-GCM PqcKeyManager.java # Resolves KeyProvider via ServiceLoader src/main/java-vault/ # (vault profile only) .../crypto/ VaultKeyProvider.java # HashiCorp Vault KV v2 key provider src/main/resources/ META-INF/services/ io.kroxylicious.proxy.filter.FilterFactory # ServiceLoader: filter registration io.kroxylicious.filter.pqc.crypto.KeyProvider # ServiceLoader: key provider src/main/resources-vault/ # (vault profile only) META-INF/services/ io.kroxylicious.filter.pqc.crypto.KeyProvider # Registers both FS + Vault providers src/test/java/... # Unit tests (49 tests) src/test/java-vault/... # Vault provider tests (17 tests) examples/ standalone/ # Runs without Kafka (crypto engine demo) docker/ # Docker Compose: Kafka + Vault + Kroxylicious
### データフロー
#### Vaultに保存される内容
Vault KV v2は、ML-KEM鍵ペアを `secret/<secretPath>`(例: `secret/kroxylicious/pqc`)に保持します:
| フィールド | 内容 | 形式 |
|-------|---------|--------|
| `publicKey` | ML-KEM公開鍵(カプセル化に使用) | Base64エンコードされたX.509 DER |
| `privateKey` | ML-KEM秘密鍵(脱カプセル化に使用) | Base64エンコードされたPKCS#8 DER |
各Vaultシークレットバージョンは鍵IDとして機能し、鍵のローテーションを可能にします。新しいバージョンは新しいレコードを暗号化し、古いバージョンでもそれらで暗号化されたレコードを復号化できます。
#### 起動フロー```
┌─────────────────────────────────────────────────────────────────────┐
│ STARTUP │
│ │
│ 1. Vault starts (dev mode, port 8200) │
│ │ │
│ 2. vault-init container: │
│ │ • Reads pre-generated ML-KEM DER key files from disk │
│ │ • Base64-encodes them │
│ │ • POST /v1/secret/data/kroxylicious/pqc │
│ │ • Stores publicKey + privateKey in Vault KV v2 │
│ │ • Exits │
│ │ │
│ 3. Kafka starts (KRaft mode, port 9092) │
│ │ │
│ 4. Kroxylicious starts (port 9192): │
│ │ │
│ │ FilterFactory.initialize() │
│ │ → PqcKeyManager resolves VaultKeyProvider │
│ │ via ServiceLoader (matches keyProviderType: "vault") │
│ │ → VaultKeyProvider.configure() │
│ │ connects to Vault (token/approle/kubernetes auth) │
│ │ → Fetches ML-KEM key pair from Vault KV v2 │
│ │ (GET /v1/secret/data/kroxylicious/pqc) │
│ │ → Decodes: base64 → DER bytes → Java Key objects │
│ │ → PqcCryptoEngine initialized with key pair │
│ ▼ │
│ Proxy ready — Vault is NOT contacted again per-message │
└─────────────────────────────────────────────────────────────────────┘
Kroxylicious (:9192)
┌───────────────────────────────────────────┐
Producer ──plaintext──> │ PqcRecordEncryptionFilter │ │ .onProduceRequest() │ │ │ │ For each record in matching topic: │ │ 1. ML-KEM encapsulate (public key) │ │ → fresh shared secret │ │ → encapsulation blob │ │ 2. SHA-256(shared secret) → AES-256 key │ │ 3. AES-GCM encrypt record value │ │ 4. Build binary envelope: │ │ [ver|encap_len|encap|IV|ciphertext] │ │ 5. Add x-pqc-encrypted header │ └────────────────┬──────────────────────────┘ │ ▼ Kafka Broker (:9092) stores encrypted blob
#### フェッチフロー(復号化)```
Kafka Broker (:9092)
returns encrypted blob
│
▼
┌────────────────┴──────────────────────────┐
│ PqcRecordEncryptionFilter │
│ .onFetchResponse() │
│ │
│ For each record with x-pqc-encrypted: │
│ 1. Parse envelope → extract encap + │
│ ciphertext │
│ 2. ML-KEM decapsulate (private key) │
│ → recover shared secret │
│ 3. SHA-256(shared secret) → AES-256 key │
│ 4. AES-GCM decrypt → plaintext │
│ 5. Remove x-pqc-encrypted header │
Consumer <──plaintext── │ │
└───────────────────────────────────────────┘
Kroxylicious (:9192)
Startup: FilterFactory.initialize() -> PqcKeyManager loads/generates ML-KEM key pair -> PqcCryptoEngine created with key pair -> Topic patterns compiled -> SharedPqcContext returned
Per connection: FilterFactory.createFilter() -> New PqcRecordEncryptionFilter instance -> Shares the same PqcCryptoEngine (thread-safe)
Produce request: onProduceRequest() -> For each topic matching topicPatterns: -> For each partition: -> For each record: -> ML-KEM encapsulate (fresh shared secret + encapsulation) -> Derive AES-256 key from shared secret -> AES-GCM encrypt the record value -> Replace value with encrypted envelope -> Add x-pqc-encrypted header -> Forward to broker
Fetch response: onFetchResponse() -> For each topic matching topicPatterns: -> For each partition: -> For each record with x-pqc-encrypted header: -> Read version + encapsulation from envelope -> ML-KEM decapsulate (recover shared secret) -> Derive AES-256 key -> AES-GCM decrypt -> Replace value with plaintext -> Remove x-pqc-encrypted header -> Forward to client
### 主要クラス
**`PqcRecordEncryptionFilterFactory`** は `FilterFactory<PqcEncryptionConfig, SharedPqcContext>` を実装します。
`@Plugin(configType = PqcEncryptionConfig.class)` でアノテーションされています。
`META-INF/services/io.kroxylicious.proxy.filter.FilterFactory` を介して登録されます。
起動時に1回 (`initialize`)、クライアント接続ごとに1回 (`createFilter`) 呼び出されます。
**`PqcRecordEncryptionFilter`** は `ProduceRequestFilter` と `FetchResponseFilter` を実装します。
`onProduceRequest` をインターセプトして暗号化し、`onFetchResponse` をインターセプトして復号化します。
接続ごとに1つのインスタンスが作成されます。同期は不要です (Kroxylicious スレッドモデル)。
**`PqcCryptoEngine`** はすべての暗号操作を実行します。
鍵素材と `SecureRandom` を除き、ステートレスです。
`encrypt()` は自己記述的なエンベロープを返し、`decrypt()` はそれを解析します。
静的初期化子で Bouncy Castle プロバイダー (`BC`, `BCPQC`) を登録します。
**`PqcKeyManager`** は `ServiceLoader` を介して `KeyProvider` を解決し、
`keyProviderType` でマッチングします。すべての鍵操作を解決されたプロバイダーに委譲します。
**`KeyProvider`** は、プラグ可能な鍵ストレージバックエンドのための SPI インターフェースです。
実装は `META-INF/services` を介して検出されます。組み込みプロバイダー:
`FileSystemKeyProvider` (デフォルト) と `VaultKeyProvider` (`-Pvault` を使用)。
**`PqcEncryptionConfig`** は Jackson アノテーション付きの POJO です。
Kroxylicious プロキシの YAML の `config:` ブロックからデシリアライズされます。
バックエンド選択のために `keyProviderType` と `keyProviderConfig` をサポートします。
## ビルドとテスト```bash
# Compile
mvn compile
# Run unit tests (38 core tests)
mvn test
# Package (creates shaded JAR with Bouncy Castle bundled)
mvn package
# Build with Vault support (55 tests: 38 core + 17 vault)
mvn package -Pvault
# Install to local Maven repository
mvn install
合計: 55テスト (コア38 + vault 17)
JUnit 5.11.4、Mockito 5.15.2、AssertJ 3.27.3、Jackson Databind 2.18.3。
スタンドアロンデモで測定(JVMウォームアップ済み、1,000イテレーション、1 KBメッセージ):
| アルゴリズム | 暗号化 | 復号 |
|---|---|---|
| ML-KEM-512 | ~7,700 msg/s (0.13 ms/msg) | ~7,800 msg/s (0.13 ms/msg) |
| ML-KEM-768 | ~9,600 msg/s (0.10 ms/msg) |
メッセージあたりのオーバーヘッドは1ミリ秒未満です。一般的なKafkaワークロード(KB〜MB範囲のメッセージ)では、暗号化コストはネットワークおよびディスクI/Oと比較して無視できる程度です。
このフィルターが防御する対象と防御しない対象を網羅した完全な脅威モデルについては、THREAT_MODEL.mdを参照してください。
chmod 600)を使用してください。approleまたはkubernetes認証を優先してください。Vaultトークンをバージョン管理にコミットしないでください。null値を持つレコード(Kafkaトゥームストーン)は暗号化されずに通過します。Compression.NONEで暗号化バッチを書き込みます。Apache License 2.0。詳細はLICENSEを参照してください。
| プロパティ | 型 | 必須 | デフォルト | 説明 |
|---|
kemAlgorithm | enum | いいえ | ML_KEM_768 | ML-KEM パラメータセット。ML_KEM_512、ML_KEM_768、ML_KEM_1024 のいずれか。 |
hybridMode | boolean | いいえ | true | ML-KEM と X25519 ECDH を組み合わせて多層防御を実現します。 |
publicKeyPath | string | ファイルシステムのみ | - | ML-KEM 公開鍵(X.509 DER エンコード)へのファイルシステムパス。 |
privateKeyPath | string | ファイルシステムのみ | - | ML-KEM 秘密鍵(PKCS#8 DER エンコード)へのファイルシステムパス。 |
topicPatterns | list<string> | いいえ | [".*"] | Java 正規表現パターン。一致するトピックのレコードのみが暗号化/復号化されます。 |
keyProviderType | string | いいえ | filesystem | 鍵ストレージバックエンド。filesystem、vault のいずれか。 |
keyProviderConfig | map<string, string> | Vault のみ | {} | バックエンド固有の設定(下記の Vault セクションを参照)。 |
| プロパティ | 必須 | デフォルト | 説明 |
|---|
vaultAddress | はい | VAULT_ADDR 環境変数 | Vault サーバーの URL(例:http://vault:8200) |
vaultToken | token 認証の場合 | VAULT_TOKEN 環境変数 | Vault 認証トークン |
secretPath | はい | -- | シークレットエンジン内のパス(例:kroxylicious/pqc) |
secretEngine | いいえ | secret | KV v2 シークレットエンジンのマウント名 |
authMethod | いいえ | token | 認証方式:token、approle、kubernetes のいずれか |
roleId | approle の場合 | -- | AppRole ロール ID |
secretId | approle の場合 | -- | AppRole シークレット ID |
kubeRole | kubernetes の場合 | -- | Kubernetes 認証ロール名 |
kubeTokenPath | いいえ | /var/run/secrets/.../token | サービスアカウントトークンファイルのパス |
| テストクラス | テスト数 | 検証内容 |
|---|
PqcCryptoEngineTest | 12 | 全3種類のML-KEMバリアントにおける暗号化/復号ラウンドトリップ、null処理、空および1MBペイロード、セマンティックセキュリティ、改ざん検出、無効なバージョンの拒否、鍵生成、エンベロープバージョンバイト |
PqcEncryptionConfigTest | 9 | デフォルト値、明示的な値、null拒否、JSONデシリアライゼーション、不変性、enumプロパティ、keyProviderConfigのデシリアライゼーション |
PqcKeyManagerTest | 6 | KeyProvider解決、エンジン生成、プロバイダーへの委譲、ファイルシステムタイプのフォールバック |
FileSystemKeyProviderTest | 11 | 鍵生成、既存鍵の読み込み、デフォルト鍵ID、不明な鍵IDの拒否、nullパス検証、ServiceLoaderディスカバリ、暗号ラウンドトリップ |
VaultKeyProviderTest | 17 | 設定検証(path/address/token/approle/kubeの欠落)、Vaultからの鍵取得、バージョン付き鍵の取得、キャッシュされた鍵、無効なバージョン、シークレット内の欠落フィールド、暗号ラウンドトリップ、close動作 |
| 依存関係 | バージョン | スコープ | 目的 |
|---|
io.kroxylicious:kroxylicious-api | 0.19.0 | provided | フィルターAPIインターフェース |
org.apache.kafka:kafka-clients | 3.9.0 | provided | Kafkaプロトコルメッセージ型 |
com.fasterxml.jackson.core:jackson-annotations | 2.18.3 | provided | 設定バインディング |
org.bouncycastle:bcprov-jdk18on | 1.83 | compile | ML-KEM、AES-GCM、X25519(shaded JARに同梱) |
org.bouncycastle:bcutil-jdk18on | 1.83 | compile | Bouncy Castleユーティリティ(shaded JARに同梱) |
org.slf4j:slf4j-api | 2.0.17 | provided | ロギング |
org.springframework.vault:spring-vault-core | 3.1.2 | compile (vault profile) | Vault KV v2クライアント(-Pvaultでビルド時に同梱) |
| ~10,200 msg/s (0.10 ms/msg) |
| ML-KEM-1024 | ~8,500 msg/s (0.12 ms/msg) | ~6,900 msg/s (0.14 ms/msg) |