PQC Hybrid Cryptography
Hybrid cryptography combines classical (pre-quantum) algorithms with post-quantum algorithms to provide defense-in-depth during the transition to quantum-resistant cryptography. This approach is recommended by NIST and other security organizations because:
-
Defense in depth: If either algorithm is broken, the other still provides protection
-
Backward compatibility: Systems can validate the classical signature even if they don’t support PQC
-
"Harvest now, decrypt later" protection: Prevents adversaries from storing encrypted data now to decrypt with future quantum computers
Hybrid Signature Operations
The component supports hybrid signatures that combine a classical signature (e.g., ECDSA, Ed25519, RSA) with a PQC signature (e.g., ML-DSA).
Supported Classical Signature Algorithms:
-
ECDSA_P256- ECDSA with NIST P-256 curve (SHA-256) -
ECDSA_P384- ECDSA with NIST P-384 curve (SHA-384) -
ECDSA_P521- ECDSA with NIST P-521 curve (SHA-512) -
ED25519- Edwards curve Ed25519 -
ED448- Edwards curve Ed448 -
RSA_2048- RSA 2048-bit (SHA-256) -
RSA_3072- RSA 3072-bit (SHA-384) -
RSA_4096- RSA 4096-bit (SHA-512)
Hybrid Signature Operations:
-
hybridSign- Create a hybrid signature using both classical and PQC algorithms -
hybridVerify- Verify a hybrid signature (both components must be valid)
Example - Hybrid Signature with ECDSA P-256 + ML-DSA:
-
Java
-
XML
-
YAML
from("direct:sign")
.to("pqc:hybrid?operation=hybridSign"
+ "&signatureAlgorithm=MLDSA"
+ "&classicalSignatureAlgorithm=ECDSA_P256")
.to("mock:signed");
from("direct:verify")
.to("pqc:hybrid?operation=hybridVerify"
+ "&signatureAlgorithm=MLDSA"
+ "&classicalSignatureAlgorithm=ECDSA_P256")
.to("mock:verified"); <route>
<from uri="direct:sign"/>
<to uri="pqc:hybrid?operation=hybridSign&signatureAlgorithm=MLDSA&classicalSignatureAlgorithm=ECDSA_P256"/>
<to uri="mock:signed"/>
</route>
<route>
<from uri="direct:verify"/>
<to uri="pqc:hybrid?operation=hybridVerify&signatureAlgorithm=MLDSA&classicalSignatureAlgorithm=ECDSA_P256"/>
<to uri="mock:verified"/>
</route> - route:
from:
uri: direct:sign
steps:
- to:
uri: pqc:hybrid
parameters:
operation: hybridSign
signatureAlgorithm: MLDSA
classicalSignatureAlgorithm: ECDSA_P256
- to:
uri: mock:signed
- route:
from:
uri: direct:verify
steps:
- to:
uri: pqc:hybrid
parameters:
operation: hybridVerify
signatureAlgorithm: MLDSA
classicalSignatureAlgorithm: ECDSA_P256
- to:
uri: mock:verified The same pattern applies for other classical algorithms. For example, to use Ed25519 instead of ECDSA P-256, change classicalSignatureAlgorithm=ECDSA_P256 to classicalSignatureAlgorithm=ED25519.
Hybrid Signature Wire Format:
Since Camel 4.19, hybrid signatures use wire format v2 which includes magic bytes, version, and algorithm identifiers (see Wire Format v2 and Algorithm Identification). The v1 format [4 bytes: classical sig length][classical sig][pqc sig] is still accepted on input for backward compatibility.
Headers set by hybrid signature operations:
-
CamelPQCHybridSignature- The complete hybrid signature -
CamelPQCClassicalSignature- The classical signature component -
CamelPQCPqcSignature- The PQC signature component -
CamelPQCHybridVerification- Verification result (true if both pass)
Hybrid KEM Operations
The component supports hybrid Key Encapsulation Mechanisms that combine classical key agreement (e.g., ECDH, X25519) with PQC KEM (e.g., ML-KEM). The shared secrets from both algorithms are combined using HKDF.
Supported Classical KEM Algorithms:
-
ECDH_P256- ECDH with NIST P-256 curve -
ECDH_P384- ECDH with NIST P-384 curve -
ECDH_P521- ECDH with NIST P-521 curve -
X25519- X25519 key agreement (recommended) -
X448- X448 key agreement
Hybrid KEM Operations:
-
hybridGenerateSecretKeyEncapsulation- Generate hybrid encapsulation and shared secret -
hybridExtractSecretKeyEncapsulation- Extract shared secret from hybrid encapsulation -
hybridExtractSecretKeyFromEncapsulation- Extract the secret key for downstream use
Supported KDF Algorithms for Hybrid KEM:
-
HKDF-SHA256(default) -
HKDF-SHA384 -
HKDF-SHA512
Example - Hybrid KEM with X25519 + ML-KEM:
-
Java
-
XML
-
YAML
from("direct:encapsulate")
.to("pqc:hybridkem?operation=hybridGenerateSecretKeyEncapsulation"
+ "&keyEncapsulationAlgorithm=MLKEM"
+ "&classicalKEMAlgorithm=X25519"
+ "&symmetricKeyAlgorithm=AES"
+ "&symmetricKeyLength=256")
.to("mock:encapsulated");
from("direct:extract")
.to("pqc:hybridkem?operation=hybridExtractSecretKeyEncapsulation"
+ "&keyEncapsulationAlgorithm=MLKEM"
+ "&classicalKEMAlgorithm=X25519"
+ "&symmetricKeyAlgorithm=AES"
+ "&symmetricKeyLength=256")
.to("mock:extracted"); <route>
<from uri="direct:encapsulate"/>
<to uri="pqc:hybridkem?operation=hybridGenerateSecretKeyEncapsulation&keyEncapsulationAlgorithm=MLKEM&classicalKEMAlgorithm=X25519&symmetricKeyAlgorithm=AES&symmetricKeyLength=256"/>
<to uri="mock:encapsulated"/>
</route>
<route>
<from uri="direct:extract"/>
<to uri="pqc:hybridkem?operation=hybridExtractSecretKeyEncapsulation&keyEncapsulationAlgorithm=MLKEM&classicalKEMAlgorithm=X25519&symmetricKeyAlgorithm=AES&symmetricKeyLength=256"/>
<to uri="mock:extracted"/>
</route> - route:
from:
uri: direct:encapsulate
steps:
- to:
uri: pqc:hybridkem
parameters:
operation: hybridGenerateSecretKeyEncapsulation
keyEncapsulationAlgorithm: MLKEM
classicalKEMAlgorithm: X25519
symmetricKeyAlgorithm: AES
symmetricKeyLength: 256
- to:
uri: mock:encapsulated
- route:
from:
uri: direct:extract
steps:
- to:
uri: pqc:hybridkem
parameters:
operation: hybridExtractSecretKeyEncapsulation
keyEncapsulationAlgorithm: MLKEM
classicalKEMAlgorithm: X25519
symmetricKeyAlgorithm: AES
symmetricKeyLength: 256
- to:
uri: mock:extracted The same pattern applies for other classical algorithms. For example, to use ECDH P-256 instead of X25519, change classicalKEMAlgorithm=X25519 to classicalKEMAlgorithm=ECDH_P256. You can also specify a different KDF via hybridKdfAlgorithm=HKDF-SHA256.
Hybrid KEM Wire Format:
Since Camel 4.19, hybrid encapsulations use wire format v2 which includes magic bytes, version, and algorithm identifiers (see Wire Format v2 and Algorithm Identification). The v1 format [4 bytes: classical encap length][classical encap][pqc encap] is still accepted on input for backward compatibility.
Headers set by hybrid KEM operations:
-
CamelPQCHybridEncapsulation- The complete hybrid encapsulation -
CamelPQCClassicalEncapsulation- The classical encapsulation component -
CamelPQCPqcEncapsulation- The PQC encapsulation component -
CamelPQCHybridSecretKey- The combined shared secret key
Recommended Algorithm Combinations
Based on NIST recommendations for the transition period:
For Signatures:
| Classical Algorithm | PQC Algorithm | Security Level | Use Case |
|---|---|---|---|
ECDSA_P256 | MLDSA (ML-DSA-65) | 128-bit | General purpose, TLS |
ED25519 | MLDSA (ML-DSA-65) | 128-bit | High-performance signing |
ECDSA_P384 | MLDSA (ML-DSA-87) | 192-bit | Higher security requirements |
RSA_3072 | MLDSA (ML-DSA-65) | 128-bit | Legacy system compatibility |
For Key Encapsulation:
| Classical Algorithm | PQC Algorithm | Security Level | Use Case |
|---|---|---|---|
X25519 | MLKEM (ML-KEM-768) | 128-bit | TLS 1.3, general purpose (recommended) |
ECDH_P256 | MLKEM (ML-KEM-768) | 128-bit | Legacy ECDH compatibility |
X448 | MLKEM (ML-KEM-1024) | 192-bit | Higher security requirements |
Security Considerations
-
Both algorithms must succeed: For hybrid verification, both the classical and PQC signatures must be valid. For hybrid KEM, both shared secrets are combined.
-
Key management: You need to manage two sets of keys (classical and PQC). Consider using the Key Lifecycle Manager to handle both.
-
Performance: Hybrid operations are slower than single-algorithm operations because they perform two cryptographic operations. ML-DSA signatures are larger than classical signatures.
-
Wire format compatibility: The hybrid signature and encapsulation formats are specific to this implementation. Ensure both parties use compatible formats.
-
Algorithm agility: Design your system to allow algorithm upgrades as standards evolve.
Wire Format v2 and Algorithm Identification
Starting with Camel 4.19, the hybrid signature, hybrid KEM, and PQC DataFormat wire formats include self-describing algorithm metadata. This makes the binary output portable and verifiable without out-of-band knowledge of which algorithms were used.
PQCAlgorithmId
The PQCAlgorithmId enum maps every supported algorithm — classical signature, PQC signature, classical KEM, PQC KEM, and symmetric — to a unique 16-bit identifier used in the wire format header.
Algorithm ID ranges:
| Range | Category | Examples |
|---|---|---|
| Classical signature | SHA256withECDSA ( |
| Post-quantum signature | ML-DSA ( |
| Classical KEM / key agreement | EC ( |
| Post-quantum KEM | ML-KEM ( |
| Symmetric encryption | AES ( |
Lookup by JCA name:
// Resolve from a JCA algorithm string (case-insensitive)
PQCAlgorithmId algId = PQCAlgorithmId.fromJcaName("ML-DSA"); // ML_DSA (0x0201)
PQCAlgorithmId kemId = PQCAlgorithmId.fromJcaName("ML-KEM"); // ML_KEM (0x0401)
PQCAlgorithmId aesId = PQCAlgorithmId.fromJcaName("AES"); // AES (0x0501)
// Returns UNKNOWN (0x0000) for unrecognized names
PQCAlgorithmId unknown = PQCAlgorithmId.fromJcaName("FutureAlg"); // UNKNOWN Lookup by wire format ID:
// Resolve from a 16-bit identifier read from the wire
PQCAlgorithmId alg = PQCAlgorithmId.fromId(0x0201); // ML_DSA
int id = alg.getId(); // 0x0201
String jca = alg.getJcaName(); // "ML-DSA" Inspecting algorithm metadata from a parsed hybrid output:
// After parsing a v2 hybrid signature
HybridSignature.HybridSignatureComponents components = HybridSignature.parse(hybridSig);
// v2 components carry algorithm identifiers
PQCAlgorithmId classical = components.classicalAlgorithmId(); // e.g. SHA256_WITH_ECDSA
PQCAlgorithmId pqc = components.pqcAlgorithmId(); // e.g. ML_DSA
int version = components.version(); // 2
// v1 components (backward-compatible) return null algorithm IDs and version 1 Wire Format v2 Layout
All three output types (hybrid signature, hybrid KEM, PQC DataFormat) share the same 8-byte header:
[2 bytes: magic 0x50 0x51 ("PQ")]
[1 byte: version -- 0x02]
[1 byte: format type]
[2 bytes: first algorithm ID]
[2 bytes: second algorithm ID]
[4 bytes: first payload length]
[N bytes: first payload]
[M bytes: second payload] Format types:
| Value | Type | First algorithm ID | Second algorithm ID |
|---|---|---|---|
| Hybrid signature | Classical signature algorithm | PQC signature algorithm |
| Hybrid KEM | Classical KEM algorithm | PQC KEM algorithm |
| KEM DataFormat | KEM algorithm | Symmetric algorithm |
Backward Compatibility
Wire format v2 is fully backward-compatible with v1:
-
Writing —
sign(),encapsulate(), andmarshal()always produce v2 output. -
Reading —
parse(),verify(),extract(), andunmarshal()auto-detect the format by checking the first two bytes for the magic bytes0x50 0x51. If absent, they fall back to v1 parsing. -
Safety — The magic bytes
0x50 0x51, interpreted as the first two bytes of a big-endian 32-bit integer, produce values above 1.3 billion. No valid v1 payload has a classical signature or encapsulation of that size, so false positives are impossible in practice.