This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch pqc-ex-4
in repository https://gitbox.apache.org/repos/asf/camel-jbang-examples.git

commit a3d304487721ecf3b7c8ecd775c22bc420ef232a
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Oct 14 12:00:22 2025 +0200

    Added an example of PQC KEM File Transfer
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 pqc-secure-file-transfer/README.adoc               | 372 +++++++++++++++++++++
 pqc-secure-file-transfer/application.properties    |  27 ++
 .../pqc-secure-file-transfer.yaml                  | 244 ++++++++++++++
 3 files changed, 643 insertions(+)

diff --git a/pqc-secure-file-transfer/README.adoc 
b/pqc-secure-file-transfer/README.adoc
new file mode 100644
index 0000000..2363909
--- /dev/null
+++ b/pqc-secure-file-transfer/README.adoc
@@ -0,0 +1,372 @@
+= 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-GCM
+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-GCM
+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-GCM encrypted content]
+----
+
+* **Encapsulation** - Contains the encrypted AES key (768 bytes for ML-KEM-512)
+* **Encrypted Content** - File content encrypted with AES-GCM
+
+== 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)
+- Use authenticated encryption (already using 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-GCM Performance:**
+
+* Encryption: ~100 MB/s (depends on CPU)
+* Decryption: ~100 MB/s (depends on CPU)
+
+== 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}"

Reply via email to