This is an automated email from the ASF dual-hosted git repository.
acosentino pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git
The following commit(s) were added to refs/heads/main by this push:
new cbe8003 Added an example of PQC KEM File Transfer (#43)
cbe8003 is described below
commit cbe80033eff9e3fad70eb23631d0e77bd36c3fe3
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Oct 14 12:17:28 2025 +0200
Added an example of PQC KEM File Transfer (#43)
* Added an example of PQC KEM File Transfer
Signed-off-by: Andrea Cosentino <[email protected]>
* Added an example of PQC KEM File Transfer
Signed-off-by: Andrea Cosentino <[email protected]>
---------
Signed-off-by: Andrea Cosentino <[email protected]>
---
pqc-secure-file-transfer/README.adoc | 373 +++++++++++++++++++++
pqc-secure-file-transfer/application.properties | 27 ++
.../pqc-secure-file-transfer.yaml | 244 ++++++++++++++
3 files changed, 644 insertions(+)
diff --git a/pqc-secure-file-transfer/README.adoc
b/pqc-secure-file-transfer/README.adoc
new file mode 100644
index 0000000..23ed818
--- /dev/null
+++ b/pqc-secure-file-transfer/README.adoc
@@ -0,0 +1,373 @@
+= Post-Quantum Secure File Transfer with ML-KEM
+
+This example demonstrates secure file transfer using Post-Quantum Cryptography
(PQC) Key Encapsulation Mechanism (KEM) with Apache Camel.
+
+== Overview
+
+This application implements a quantum-resistant secure file transfer system
using:
+
+* **ML-KEM-512** (Module-Lattice Key Encapsulation Mechanism) -
NIST-standardized post-quantum algorithm
+* **AES** - Symmetric encryption for file content
+* **Hybrid Approach** - KEM for secure key exchange + AES for efficient file
encryption
+
+== How It Works
+
+=== Key Encapsulation Mechanism (KEM)
+
+Traditional key exchange methods (RSA, ECDH) are vulnerable to quantum
computer attacks. KEM provides quantum-resistant key exchange:
+
+1. **Key Generation** - Generate ML-KEM public/private key pair
+2. **Encapsulation** - Use public key to generate a shared secret and its
encapsulation (ciphertext)
+3. **Decapsulation** - Use private key to extract the shared secret from
encapsulation
+
+=== File Transfer Flow
+
+**Encryption (Sender Side):**
+
+1. Watch `input/` directory for new files
+2. Generate a random AES key using ML-KEM encapsulation
+3. Encrypt file content with AES
+4. Combine encapsulation + encrypted content
+5. Save to `encrypted/` directory with `.pqenc` extension
+
+**Decryption (Receiver Side):**
+
+1. Watch `encrypted/` directory for `.pqenc` files
+2. Parse encapsulation and encrypted content
+3. Decapsulate to extract AES key using ML-KEM private key
+4. Decrypt file content with AES
+5. Save decrypted file to `decrypted/` directory
+
+== Features
+
+* **Quantum-Resistant** - Uses NIST-standardized ML-KEM algorithm
+* **Efficient** - Hybrid approach: PQC for key exchange, AES for data
encryption
+* **Automatic** - File watcher automatically processes new files
+* **Simple Format** - Encapsulation + encrypted data in single file
+* **REST API** - Monitor system status via HTTP endpoint
+
+== Prerequisites
+
+* JBang installed (https://www.jbang.dev)
+* Java 11 or later
+
+== Project Structure
+
+[source,text]
+----
+pqc-secure-file-transfer/
+├── pqc-secure-file-transfer.yaml # YAML configuration (beans + routes)
+├── application.properties # Configuration file
+├── README.adoc # This file
+├── input/ # Place files here to encrypt
+├── encrypted/ # Encrypted files (.pqenc)
+└── decrypted/ # Decrypted files
+----
+
+== Running the Example
+
+Start the application:
+
+[source,sh]
+----
+$ jbang -Dcamel.jbang.version=4.16.0-SNAPSHOT camel@apache/camel run \
+ --dep=camel:pqc \
+ --dep=camel:crypto \
+ --dep=org.bouncycastle:bcprov-jdk18on:1.82 \
+ --properties=application.properties \
+ pqc-secure-file-transfer.yaml
+----
+
+**Note:** The BouncyCastle dependency (`bcprov-jdk18on:1.82`) is required for
the Groovy scripts to compile successfully. This provides both the standard
cryptographic providers and the PQC classes (including ML-KEM) used in the
example.
+
+The application will:
+
+1. Register BouncyCastle PQC providers
+2. Generate ML-KEM-512 key pair
+3. Start file watchers on `input/` and `encrypted/` directories
+4. Start REST API on port 8080
+
+== Usage
+
+=== Encrypt a File
+
+1. Create the input directory if it doesn't exist:
+
+[source,sh]
+----
+$ mkdir -p input
+----
+
+2. Place a file in the `input/` directory:
+
+[source,sh]
+----
+$ echo "This is a secret message protected by quantum-resistant cryptography!"
> input/secret.txt
+----
+
+3. The file will be automatically encrypted and saved to
`encrypted/secret.txt.pqenc`
+
+4. Check the logs:
+
+[source,text]
+----
+Processing file: secret.txt
+Generating secret key encapsulation with ML-KEM...
+Secret key encapsulated.
+Secret key extracted for AES encryption
+File encrypted with AES.
+Encrypted file saved: secret.txt.pqenc
+----
+
+=== Decrypt a File
+
+The encrypted file is automatically decrypted:
+
+1. The system watches `encrypted/` directory
+2. Detects `secret.txt.pqenc`
+3. Decapsulates the AES key using ML-KEM private key
+4. Decrypts the file content
+5. Saves to `decrypted/secret.txt`
+
+Check the logs:
+
+[source,text]
+----
+Decrypting file: secret.txt.pqenc
+Parsed encapsulation from encrypted file
+Secret key decapsulated for AES decryption
+File decrypted successfully.
+Decrypted file saved: secret.txt
+----
+
+=== Verify Decryption
+
+[source,sh]
+----
+$ cat decrypted/secret.txt
+This is a secret message protected by quantum-resistant cryptography!
+----
+
+=== Check System Status
+
+[source,sh]
+----
+$ curl http://localhost:8080/api/status
+----
+
+**Response:**
+[source,json]
+----
+{
+ "status": "running",
+ "algorithm": "ML-KEM-512",
+ "symmetricEncryption": "AES",
+ "inputDirectory": "input",
+ "encryptedDirectory": "encrypted",
+ "decryptedDirectory": "decrypted",
+ "message": "Post-Quantum Secure File Transfer System is operational"
+}
+----
+
+== File Format
+
+Encrypted files use a simple binary format:
+
+[source,text]
+----
+[4 bytes: encapsulation length (big-endian)]
+[N bytes: ML-KEM encapsulation]
+[M bytes: AES encrypted content]
+----
+
+* **Encapsulation** - Contains the encrypted AES key (768 bytes for ML-KEM-512)
+* **Encrypted Content** - File content encrypted with AES (128-bit key)
+
+== Implementation Details
+
+=== Bean Configuration
+
+**1. Security Initialization** - Registers BouncyCastle providers:
+
+[source,yaml]
+----
+- beans:
+ - name: initSecurity
+ type: java.lang.Object
+ scriptLanguage: groovy
+ script: |
+ if (java.security.Security.getProvider("BC") == null) {
+ java.security.Security.addProvider(new
org.bouncycastle.jce.provider.BouncyCastleProvider())
+ }
+ if (java.security.Security.getProvider("BCPQC") == null) {
+ java.security.Security.addProvider(new
org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider())
+ }
+ return new Object()
+----
+
+=== Routes
+
+**1. initialize-kem-keypair** - Generates ML-KEM-512 key pair on startup
+
+**2. sender-encrypt-files** - Watches `input/` and encrypts files using KEM +
AES
+
+**3. receiver-decrypt-files** - Watches `encrypted/` and decrypts `.pqenc`
files
+
+**4. status-api** - GET `/api/status` - Returns system status
+
+=== Key Operations
+
+**Generate Secret Key Encapsulation:**
+
+[source,yaml]
+----
+- toD:
"pqc:encrypt?operation=generateSecretKeyEncapsulation&symmetricKeyAlgorithm=AES&keyEncapsulationAlgorithm=MLKEM&keyPair=#kemKeyPair"
+----
+
+**Extract Encapsulation:**
+
+[source,yaml]
+----
+- toD:
"pqc:encrypt?operation=extractSecretKeyEncapsulation&symmetricKeyAlgorithm=AES&keyEncapsulationAlgorithm=MLKEM"
+----
+
+**Extract Secret Key:**
+
+[source,yaml]
+----
+- toD:
"pqc:encrypt?operation=extractSecretKeyFromEncapsulation&symmetricKeyAlgorithm=AES&keyEncapsulationAlgorithm=MLKEM&keyPair=#kemKeyPair"
+----
+
+== Security Considerations
+
+=== Current Implementation
+
+* **Development Setup** - Keys are generated in memory and not persisted
+* **Single KeyPair** - Same key pair used for all files (demonstration only)
+* **No Authentication** - No verification of sender/receiver identity
+
+=== Production Recommendations
+
+**1. Key Management**
+
+- Store keys in HashiCorp Vault or AWS Secrets Manager
+- Use different key pairs for different security domains
+- Implement key rotation policies
+- Back up private keys securely
+
+**2. Enhanced Security**
+
+- Add digital signatures for authentication (combine with PQC signatures)
+- Implement sender/receiver identity verification
+- Add message authentication codes (MACs) for integrity verification
+- Consider upgrading to authenticated encryption modes like AES-GCM
+
+**3. File Handling**
+
+- Validate file sizes before processing
+- Implement virus scanning
+- Add checksums for integrity verification
+- Secure delete original files after encryption
+
+**4. Network Security**
+
+- Use TLS for file transfer over network
+- Implement rate limiting
+- Add access controls and authentication
+
+== Advanced Usage
+
+=== Custom Directories
+
+Edit `application.properties`:
+
+[source,properties]
+----
+input.directory=/secure/upload
+encrypted.directory=/secure/encrypted
+decrypted.directory=/secure/decrypted
+----
+
+=== Different KEM Algorithms
+
+The example uses ML-KEM-512. Other options:
+
+* **ML-KEM-768** - Higher security level (recommended for most use cases)
+* **ML-KEM-1024** - Highest security level
+
+Modify the initialization script:
+
+[source,yaml]
+----
+kpg.initialize(MLKEMParameterSpec.ml_kem_768, new SecureRandom())
+----
+
+=== Batch Processing
+
+To process multiple files:
+
+[source,sh]
+----
+$ for file in file1.txt file2.pdf file3.doc; do
+ cp "$file" input/
+ sleep 2
+done
+----
+
+== Performance
+
+**ML-KEM-512 Performance (approximate):**
+
+* Key Generation: ~0.1ms
+* Encapsulation: ~0.1ms
+* Decapsulation: ~0.1ms
+* Encapsulation Size: 768 bytes
+
+**AES Performance:**
+
+* Encryption: ~100-200 MB/s (depends on CPU and mode)
+* Decryption: ~100-200 MB/s (depends on CPU and mode)
+* Key Size: 128 bits (generated via ML-KEM)
+
+== Comparison with Traditional Encryption
+
+[cols="1,2,2"]
+|===
+|Aspect |Traditional (RSA/ECDH) |This Example (ML-KEM)
+
+|Quantum Resistance
+|❌ Vulnerable
+|✅ Resistant
+
+|Key Exchange
+|RSA-2048 or ECDH-256
+|ML-KEM-512
+
+|Encapsulation Size
+|256 bytes (RSA-2048)
+|768 bytes (ML-KEM-512)
+
+|Performance
+|~10ms (RSA)
+|~0.1ms (ML-KEM)
+
+|Standardization
+|✅ NIST FIPS
+|✅ NIST FIPS 203
+|===
+
+== References
+
+* **ML-KEM** - NIST FIPS 203 (https://csrc.nist.gov/pubs/fips/203/final)
+* **Camel PQC Component** -
/home/oscerd/workspace/apache-camel/camel/components/camel-pqc/
+* **BouncyCastle PQC** - https://www.bouncycastle.org/
+
+== Help and Contributions
+
+If you hit any problem using Camel or have some feedback, then please
+https://camel.apache.org/community/support/[let us know].
+
+We also love contributors, so
+https://camel.apache.org/community/contributing/[get involved] :-)
+
+The Camel riders!
diff --git a/pqc-secure-file-transfer/application.properties
b/pqc-secure-file-transfer/application.properties
new file mode 100644
index 0000000..f6e5c65
--- /dev/null
+++ b/pqc-secure-file-transfer/application.properties
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Application Configuration
+camel.main.name = PQCSecureFileTransfer
+
+# Directory Configuration
+input.directory=input
+encrypted.directory=encrypted
+decrypted.directory=decrypted
+
+# HTTP Server Configuration
+camel.server.port=8080
diff --git a/pqc-secure-file-transfer/pqc-secure-file-transfer.yaml
b/pqc-secure-file-transfer/pqc-secure-file-transfer.yaml
new file mode 100644
index 0000000..9b062f4
--- /dev/null
+++ b/pqc-secure-file-transfer/pqc-secure-file-transfer.yaml
@@ -0,0 +1,244 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Post-Quantum Secure File Transfer using KEM (Key Encapsulation Mechanism)
+# This example demonstrates secure file transfer using ML-KEM for
quantum-resistant encryption
+
+# Bean Definitions
+- beans:
+ # Register BouncyCastle providers
+ - name: initSecurity
+ type: java.lang.Object
+ scriptLanguage: groovy
+ script: |
+ if (java.security.Security.getProvider("BC") == null) {
+ java.security.Security.addProvider(new
org.bouncycastle.jce.provider.BouncyCastleProvider())
+ }
+ if (java.security.Security.getProvider("BCPQC") == null) {
+ java.security.Security.addProvider(new
org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider())
+ }
+ return new Object()
+
+# Route Definitions
+
+# Route 1: Initialize KEM KeyPair on startup
+- route:
+ id: initialize-kem-keypair
+ from:
+ uri: timer:init
+ parameters:
+ repeatCount: 1
+ steps:
+ - log: "Initializing ML-KEM key pair for secure file transfer..."
+ - script:
+ groovy: |
+ import org.bouncycastle.jcajce.spec.MLKEMParameterSpec
+ import java.security.KeyPairGenerator
+ import java.security.SecureRandom
+
+ // Generate ML-KEM-512 key pair
+ def kpg = KeyPairGenerator.getInstance("ML-KEM", "BC")
+ kpg.initialize(MLKEMParameterSpec.ml_kem_512, new SecureRandom())
+ def keyPair = kpg.generateKeyPair()
+
+ // Register in Camel registry
+ camelContext.registry.bind("kemKeyPair", keyPair)
+
+ exchange.message.setBody("ML-KEM key pair initialized")
+ - log: "ML-KEM key pair initialized successfully"
+
+# Route 2: Sender - Encrypt files using KEM
+- route:
+ id: sender-encrypt-files
+ from:
+ uri: file:{{input.directory}}
+ parameters:
+ noop: true
+ idempotent: true
+ steps:
+ - log: "Processing file: ${header.CamelFileName}"
+ - setProperty:
+ name: originalFileName
+ simple: "${header.CamelFileName}"
+ - setProperty:
+ name: originalFileContent
+ simple: "${body}"
+
+ # Step 1: Generate secret key and encapsulation using ML-KEM
+ - log: "Generating secret key encapsulation with ML-KEM..."
+ - toD:
"pqc:encrypt?operation=generateSecretKeyEncapsulation&symmetricKeyAlgorithm=AES&keyEncapsulationAlgorithm=MLKEM&keyPair=#kemKeyPair"
+ - script:
+ groovy: |
+ import org.bouncycastle.jcajce.SecretKeyWithEncapsulation
+ // Save the SecretKeyWithEncapsulation and extract encapsulation
bytes
+ def secretKeyWithEnc =
exchange.message.getBody(SecretKeyWithEncapsulation.class)
+ def encapBytes = secretKeyWithEnc.getEncapsulation()
+ exchange.setProperty("encapsulationBytes", encapBytes)
+ // Keep SecretKeyWithEncapsulation in body for next step
+
+ # Step 2: Extract secret key from encapsulation
+ - toD:
"pqc:encrypt?operation=extractSecretKeyFromEncapsulation&symmetricKeyAlgorithm=AES&keyEncapsulationAlgorithm=MLKEM&keyPair=#kemKeyPair&storeExtractedSecretKeyAsHeader=true"
+ - setHeader:
+ name: CamelCryptoKey
+ simple: "${header.CamelPQCSecretKey}"
+ - log: "Secret key extracted for AES encryption"
+
+ # Step 4: Encrypt the file content with AES using the secret key
+ - setBody:
+ simple: "${exchangeProperty.originalFileContent}"
+ - marshal:
+ crypto:
+ algorithm: "AES"
+ - script:
+ groovy: |
+ // Save encrypted content for later combination
+ exchange.setProperty("encryptedContent",
exchange.message.getBody(byte[].class))
+ - log: "File encrypted with AES."
+
+ # Step 5: Combine encapsulation and encrypted content
+ - script:
+ groovy: |
+ import java.nio.ByteBuffer
+
+ // Create a combined format: [encapsulation_length(4
bytes)][encapsulation][encrypted_content]
+ def encapBytes = exchange.getProperty("encapsulationBytes",
byte[].class)
+ def encryptedBytes = exchange.getProperty("encryptedContent",
byte[].class)
+
+ def output = new ByteArrayOutputStream()
+ // Write encapsulation length (4 bytes, big-endian)
+ def lengthBytes =
ByteBuffer.allocate(4).putInt(encapBytes.length).array()
+ output.write(lengthBytes)
+ // Write encapsulation
+ output.write(encapBytes)
+ // Write encrypted content
+ output.write(encryptedBytes)
+
+ exchange.message.setBody(output.toByteArray())
+
+ # Step 6: Write to encrypted directory
+ - setHeader:
+ name: CamelFileName
+ simple: "${exchangeProperty.originalFileName}.pqenc"
+ - to:
+ uri: file:{{encrypted.directory}}
+ - log: "Encrypted file saved: ${header.CamelFileName}"
+
+# Route 3: Receiver - Decrypt files using KEM
+- route:
+ id: receiver-decrypt-files
+ from:
+ uri: file:{{encrypted.directory}}
+ parameters:
+ include: ".*\\.pqenc"
+ noop: true
+ idempotent: true
+ steps:
+ - log: "Decrypting file: ${header.CamelFileName}"
+ - setProperty:
+ name: encryptedFileName
+ simple: "${header.CamelFileName}"
+
+ # Step 1: Parse combined file format
+ - script:
+ groovy: |
+ import java.nio.ByteBuffer
+ import java.util.Arrays
+
+ def combinedBytes = exchange.message.getBody(byte[].class)
+
+ // Read encapsulation length (first 4 bytes)
+ def lengthBytes = Arrays.copyOfRange(combinedBytes, 0, 4)
+ def encapLength = ByteBuffer.wrap(lengthBytes).getInt()
+
+ // Extract encapsulation
+ def encapBytes = Arrays.copyOfRange(combinedBytes, 4, 4 +
encapLength)
+
+ // Extract encrypted content
+ def encryptedBytes = Arrays.copyOfRange(combinedBytes, 4 +
encapLength, combinedBytes.length)
+
+ exchange.setProperty("encapsulationBytes", encapBytes)
+ exchange.setProperty("encryptedContent", encryptedBytes)
+
+ // Set encapsulation as body for next step
+ exchange.message.setBody(encapBytes)
+ - log: "Parsed encapsulation from encrypted file"
+
+ # Step 2: Decapsulate to extract secret key using the private key
+ - script:
+ groovy: |
+ import org.bouncycastle.jcajce.SecretKeyWithEncapsulation
+ import org.bouncycastle.jcajce.spec.KEMExtractSpec
+ import javax.crypto.KeyGenerator
+ import java.security.SecureRandom
+
+ // Get the encapsulation bytes and keypair
+ def encapBytes = exchange.message.getBody(byte[].class)
+ def keyPair = camelContext.registry.lookupByName("kemKeyPair")
+
+ // Use KeyGenerator to decapsulate and extract the secret (use
128 bits, the default)
+ def keyGenerator = KeyGenerator.getInstance("ML-KEM")
+ keyGenerator.init(
+ new KEMExtractSpec(keyPair.getPrivate(), encapBytes, "AES",
128),
+ new SecureRandom())
+
+ // Generate the secret key
+ def secretKeyWithEnc = (SecretKeyWithEncapsulation)
keyGenerator.generateKey()
+ def secretKey = secretKeyWithEnc
+
+ // Store in header for CryptoDataFormat
+ exchange.message.setHeader("CamelCryptoKey", secretKey)
+ - log: "Secret key decapsulated for AES decryption"
+
+ # Step 3: Decrypt the file content with AES
+ - setBody:
+ simple: "${exchangeProperty.encryptedContent}"
+ - unmarshal:
+ crypto:
+ algorithm: "AES"
+ - log: "File decrypted successfully."
+
+ # Step 4: Write to decrypted directory
+ - script:
+ groovy: |
+ // Remove .pqenc extension from filename
+ def fileName = exchange.getProperty("encryptedFileName")
+ def decryptedFileName = fileName.replaceAll("\\.pqenc\$", "")
+ exchange.message.setHeader("CamelFileName", decryptedFileName)
+ - to:
+ uri: file:{{decrypted.directory}}
+ - log: "Decrypted file saved: ${header.CamelFileName}"
+
+# Route 4: Status API - Monitor transfer statistics
+- route:
+ id: status-api
+ from:
+ uri: platform-http:/api/status
+ steps:
+ - log: "Status check requested"
+ - setBody:
+ simple: |
+ {
+ "status": "running",
+ "algorithm": "ML-KEM-512",
+ "symmetricEncryption": "AES",
+ "inputDirectory": "{{input.directory}}",
+ "encryptedDirectory": "{{encrypted.directory}}",
+ "decryptedDirectory": "{{decrypted.directory}}",
+ "message": "Post-Quantum Secure File Transfer System is
operational"
+ }
+ - setHeader:
+ name: Content-Type
+ constant: "application/json"
+ - log: "Status: ${body}"