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:

  1. Defense in depth: If either algorithm is broken, the other still provides protection

  2. Backward compatibility: Systems can validate the classical signature even if they don’t support PQC

  3. "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&amp;signatureAlgorithm=MLDSA&amp;classicalSignatureAlgorithm=ECDSA_P256"/>
  <to uri="mock:signed"/>
</route>
<route>
  <from uri="direct:verify"/>
  <to uri="pqc:hybrid?operation=hybridVerify&amp;signatureAlgorithm=MLDSA&amp;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&amp;keyEncapsulationAlgorithm=MLKEM&amp;classicalKEMAlgorithm=X25519&amp;symmetricKeyAlgorithm=AES&amp;symmetricKeyLength=256"/>
  <to uri="mock:encapsulated"/>
</route>
<route>
  <from uri="direct:extract"/>
  <to uri="pqc:hybridkem?operation=hybridExtractSecretKeyEncapsulation&amp;keyEncapsulationAlgorithm=MLKEM&amp;classicalKEMAlgorithm=X25519&amp;symmetricKeyAlgorithm=AES&amp;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

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

  1. Both algorithms must succeed: For hybrid verification, both the classical and PQC signatures must be valid. For hybrid KEM, both shared secrets are combined.

  2. Key management: You need to manage two sets of keys (classical and PQC). Consider using the Key Lifecycle Manager to handle both.

  3. Performance: Hybrid operations are slower than single-algorithm operations because they perform two cryptographic operations. ML-DSA signatures are larger than classical signatures.

  4. Wire format compatibility: The hybrid signature and encapsulation formats are specific to this implementation. Ensure both parties use compatible formats.

  5. 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

0x0100-0x01FF

Classical signature

SHA256withECDSA (0x0101), Ed25519 (0x0104), SHA256withRSA (0x0106)

0x0200-0x02FF

Post-quantum signature

ML-DSA (0x0201), SLH-DSA (0x0202), LMS (0x0203), FALCON (0x0207)

0x0300-0x03FF

Classical KEM / key agreement

EC (0x0301), X25519 (0x0303), X448 (0x0305)

0x0400-0x04FF

Post-quantum KEM

ML-KEM (0x0401), BIKE (0x0402), NTRU (0x0407), KYBER (0x040A)

0x0500-0x05FF

Symmetric encryption

AES (0x0501), ARIA (0x0502), Camellia (0x0503)

Lookup by JCA name:

Java-only: resolving PQCAlgorithmId from JCA algorithm names
// 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:

Java-only: resolving PQCAlgorithmId from wire format identifier
// 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:

Java-only: parsing and inspecting v2 hybrid signature components
// 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

0x01

Hybrid signature

Classical signature algorithm

PQC signature algorithm

0x02

Hybrid KEM

Classical KEM algorithm

PQC KEM algorithm

0x03

KEM DataFormat

KEM algorithm

Symmetric algorithm

Backward Compatibility

Wire format v2 is fully backward-compatible with v1:

  • Writing — sign(), encapsulate(), and marshal() always produce v2 output.

  • Reading — parse(), verify(), extract(), and unmarshal() auto-detect the format by checking the first two bytes for the magic bytes 0x50 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.