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}"
