Skip to content

PQC Transport Handshake in the AnkaSecure SDK

Feature: PQC Transport Layer Algorithms: ML-KEM-512/768/1024 Default: ML-KEM-768 Status: Optional (configurable)

This document describes the SDK-specific implementation of the Post-Quantum Cryptography (PQC) transport handshake, including the security model and protocol specification.

Technical Documentation

This page describes how the PQC transport layer works within the SDK. It contains descriptions of the encryption/decryption process but does not require code modification by SDK consumers.

For SDK users: The PQC transport is transparent — enable it via configuration and it works automatically.


Overview

The AnkaSecure SDK implements a transparent PQC transport layer that automatically encrypts API payloads using quantum-resistant cryptography. This provides defense-in-depth on top of TLS 1.3.

Key Benefits:

  • Quantum Resistance: Uses ML-KEM algorithms designed to resist quantum attacks
  • Defense-in-Depth: Adds encryption layer beyond TLS 1.3
  • Transparent to Developers: No code changes required — enable via configuration
  • Configurable: Algorithm, TTL, and endpoints adjustable via properties
  • Session Reuse: Handshake overhead amortized across multiple requests
  • Thread-Safe: Concurrent requests handled safely with session locking

How to Enable:

Add to your SDK configuration file (cli.properties or sdk-config.properties):

# Enable PQC transport layer (default: true)
pqc.transport.enabled=true

# Algorithm selection (default: ML-KEM-768)
pqc.kem.algorithm=ML-KEM-768

# Session TTL in seconds (default: 14400 = 4 hours)
pqc.session.ttl.seconds=14400

No code changes required — the SDK automatically encrypts all eligible API requests.


SDK Architecture

flowchart TB
    subgraph App["Application Code<br/>(uses AnkaSecureSdk)"]
    end
    subgraph Client["AnkaSecureOpenApiClient<br/>– applies auth token<br/>– applies PQC interceptor"]
    end
    subgraph Interceptor["PqcTransportInterceptor<br/>– performs handshake<br/>– encrypts request bodies<br/>– decrypts responses"]
    end
    subgraph Handshake["Server PQC Handshake<br/>– encapsulates AES key<br/>– returns {sessionId,…}"]
    end
    subgraph Protected["Protected REST APIs<br/>– decrypt request payload<br/>– process business logic<br/>– encrypt response payload"]
    end

    App -->|invoke SDK methods| Client
    Client -->|HTTP calls via OkHttp| Interceptor
    Interceptor -->|POST /api/pqc-handshake/init unmodified| Handshake
    Handshake -->|subsequent requests carry<br/>X-PQC-Session| Protected

SDK Components

1. PqcTransportConfig

Configuration reader for PQC transport settings.

Purpose: Loads and validates PQC transport configuration from properties file

Properties:

Property Type Default Description
pqc.transport.enabled boolean true Enable/disable PQC transport layer
pqc.handshake.endpoint string /api/pqc-handshake/init Handshake endpoint path
pqc.session.ttl.seconds int 14400 Session cache TTL (4 hours = 14,400 seconds)
pqc.kem.algorithm string ML-KEM-768 KEM algorithm (ML-KEM-512/768/1024)

Validation:

  • If pqc.transport.enabled=false, all PQC operations are disabled
  • Invalid algorithm names fall back to ML-KEM-768 (default)
  • TTL must be positive integer (minimum 60 seconds recommended)

2. PqcSessionManager

Orchestrates the PQC handshake and manages session lifecycle.

Purpose: Establishes and caches PQC transport sessions for encrypting API payloads

Handshake Process:

  1. Generate Key Pair: Creates ML-KEM public/private key pair client-side
  2. Send Public Key: Calls handshake endpoint with Base64-encoded public key
  3. Receive Encapsulated Key: Server returns KEM ciphertext containing AES session key
  4. Decapsulate: Client uses private key to extract AES key
  5. Cache Session: Stores session ID, AES key, and creation timestamp

Session Management:

  • Active Session: Cached session used for all subsequent requests
  • Session Expiry: Based on TTL configuration (default 4 hours)
  • Invalidation: Explicitly invalidated during authentication to force fresh handshake
  • Reuse: Same session used for multiple requests to avoid handshake overhead

Thread Safety:

Uses ReentrantLock to prevent concurrent handshakes when multiple threads request sessions simultaneously. Only one handshake occurs at a time; other threads wait and reuse the newly established session.

3. PqcTransportInterceptor

OkHttp interceptor that transparently encrypts/decrypts HTTP request/response bodies.

Purpose: Automatically applies PQC transport encryption to eligible API calls

Interception Flow:

Phase 1 - Request Processing:

  1. Check eligibility: Determine if request should be encrypted
  2. Obtain session: Get active PQC session or trigger handshake if needed
  3. Encrypt payload: Apply AES-GCM encryption to JSON request body
  4. Modify headers:
    • Change Content-Type to application/octet-stream
    • Add X-PQC-Transport: true header
    • Add X-PQC-Session: <sessionId> header
  5. Send request: Forward encrypted binary payload to server

Phase 2 - Response Processing:

  1. Read response: Receive binary encrypted response from server
  2. Decrypt payload: Apply AES-GCM decryption using session AES key
  3. Verify integrity: Validate authentication tag (prevents tampering)
  4. Restore headers:
    • Change Content-Type back to application/json
    • Remove PQC-specific headers
  5. Return response: Provide decrypted JSON to calling code

Bypass Conditions:

The interceptor skips encryption for:

  • Feature disabled: pqc.transport.enabled=false
  • Handshake endpoint: /api/pqc-handshake/init (chicken-egg problem)
  • Authentication endpoints: /api/v3/auth/** (OAuth 2.0 token and account operations)
  • Streaming endpoints: /api/crypto/stream/** (use different encryption)
  • Public endpoints: /api/v1/public/** (no auth required)
  • GET requests: No request body to encrypt
  • Empty bodies: Requests with no payload

4. AnkaSecureOpenApiClient Integration

The SDK's high-level API methods integrate seamlessly with the PQC transport layer.

Integration Points:

OkHttp Configuration:

The OpenAPI client configures OkHttp with the PqcTransportInterceptor during initialization. This ensures all HTTP requests pass through the PQC encryption layer before reaching the network.

Authentication Invalidation:

The SDK automatically invalidates PQC sessions at the start of authentication methods (authenticateApplication(), authenticateUser()). This ensures:

  • Fresh session keys for each authentication
  • No stale sessions after credential changes
  • Clean separation between authentication phases

Transparency:

All SDK methods (generateKey(), encryptFileStream(), signFile(), etc.) work identically whether PQC transport is enabled or disabled. The encryption happens at the HTTP layer, completely transparent to business logic.


Session Lifecycle

sequenceDiagram
    participant SDK as AnkaSecureSdk
    participant Inter as PqcTransportInterceptor
    participant Sess as PqcSessionManager
    participant API as AnkaSecureOpenApiClient
    participant Server

    SDK->>API: authenticateApplication()
    API->>Sess: invalidateSession()
    API->>Inter: HTTP POST /authenticate (body JSON)
    Inter->>Sess: getSession() → no session
    Sess->>API: performHandshake(pubKeyB64)
    API->>Server: POST /api/pqc-handshake/init
    Server-->>API: {ciphertextBase64, sessionId}
    API-->>Sess: decapsulate + cache AES key
    Sess-->>Inter: returns session
    Inter->>Server: encrypted request (AES-GCM)
    Server-->>Inter: encrypted response
    Inter->>SDK: decrypted JSON response

Lifecycle States:

  1. First Encrypted Call: No active session → handshake triggered
  2. Subsequent Calls (within TTL): Active session reused
  3. Session Expiry or Manual Invalidate: Next call triggers new handshake
  4. Auth Calls: SDK explicitly invalidates to ensure fresh key material

Request/Response Encryption

Request Encryption Process

When the PqcTransportInterceptor processes an outbound request:

Step 1: Encrypt Payload

The original JSON request body is encrypted using AES-GCM with the session's shared AES key. The encryption produces binary ciphertext that includes an initialization vector (IV) and authentication tag for integrity validation.

Step 2: Modify Request

The interceptor creates a new HTTP request with:

  • Content-Type: Changed from application/json to application/octet-stream
  • X-PQC-Transport: Header set to true (signals server to decrypt)
  • X-PQC-Session: Header containing the session ID for key lookup
  • Body: Binary encrypted payload (replaces original JSON)

Step 3: Send to Server

The encrypted request is sent to the server, where the PQC handshake API decrypts it using the session's AES key stored in Redis.


Response Decryption Process

When the server returns an encrypted response:

Step 1: Read Binary Response

The interceptor reads the binary response body (encrypted with the same session AES key).

Step 2: Decrypt Payload

The binary ciphertext is decrypted using AES-GCM with the session's shared AES key. The authentication tag is verified to ensure integrity and authenticity.

Step 3: Reconstruct JSON Response

The interceptor creates a new HTTP response with:

  • Content-Type: Restored to application/json
  • Headers: X-PQC-Transport and X-PQC-Session headers removed
  • Body: Decrypted JSON payload

Step 4: Return to Application

The decrypted JSON response is returned to the SDK method caller, completely transparent to the application code.


Configuration & Extension

Enabling/Disabling PQC Transport

Enable PQC transport (recommended for production):

pqc.transport.enabled=true

Disable PQC transport (for testing or debugging):

pqc.transport.enabled=false

When disabled, all API calls use standard JSON over TLS without the additional PQC encryption layer.


Algorithm Configuration

Configure the KEM algorithm used for key encapsulation:

# Available options: ML-KEM-512, ML-KEM-768, ML-KEM-1024
pqc.kem.algorithm=ML-KEM-768

Algorithm selection:

  • ML-KEM-512: Faster, smaller keys (NIST Security Level 1)
  • ML-KEM-768: Balanced (NIST Security Level 3) - recommended
  • ML-KEM-1024: Maximum security (NIST Security Level 5)

Ensure the server supports the selected algorithm.


Session TTL Tuning

Adjust session lifetime based on your requirements:

# 4 hours (default) - balanced security and performance
pqc.session.ttl.seconds=14400

# 1 hour - more frequent key rotation (higher security)
pqc.session.ttl.seconds=3600

# 8 hours - fewer handshakes (better performance)
pqc.session.ttl.seconds=28800

Considerations:

  • Shorter TTL: More frequent handshakes, better security (fresh keys)
  • Longer TTL: Fewer handshakes, better performance
  • Default (4 hours): Good balance for most applications

Endpoint Configuration

Customize the handshake endpoint if server path changes:

# Default
pqc.handshake.endpoint=/api/pqc-handshake/init

# Custom endpoint (if server relocated)
pqc.handshake.endpoint=/api/v2/pqc/init

Excluded Endpoints

The following endpoints automatically bypass PQC encryption:

Always excluded:

  • Authentication endpoints: /api/v3/auth/** (OAuth 2.0 token and account operations)
  • Handshake endpoint: /api/pqc-handshake/init
  • Streaming endpoints: /api/crypto/stream/**
  • Public endpoints: /api/v1/public/**
  • Utility endpoints: /actuator/**, /api/health/**

Excluded request types:

  • GET requests (no request body)
  • Requests with no body
  • Multipart requests (file uploads use different encryption)

These exclusions ensure the transport layer doesn't interfere with endpoints that require specific content types or have no payload to encrypt.


Error Handling & Logging

Concurrency Protection

PqcSessionManager uses ReentrantLock to prevent concurrent handshakes when multiple threads try to establish sessions simultaneously.

Error Types

Error Description Resolution
IOException: "Failed to establish PQC transport session" Handshake failed Check server connectivity, verify algorithm support
AES-GCM tag mismatch Decryption failed Session may be invalid, will trigger re-handshake
Base64 decode errors Corrupted response Network issue or server error

Logging Levels

Level Information
DEBUG Session IDs, key sizes, handshake events
INFO Encryption/decryption success
WARN Session expiration, retry attempts
ERROR Handshake failures, crypto errors

Troubleshooting Tips

"Tag mismatch" on second login

Cause: Multiple interceptor instances or stale session managers.

Resolution: Ensure only one PqcSessionManager and interceptor are registered per AnkaSecureOpenApiClient.

Large-file streaming errors

Cause: Multipart headers misconfigured.

Resolution: Verify client-side multipart builder matches controller's @RequestPart("header") and @RequestPart("file").

Debugging Handshakes

Enable debug logging:

Configure your logging framework to show DEBUG level for the SDK logger:

Logback example:

<logger name="co.ankatech.cli.dev" level="DEBUG"/>

What to inspect:

  • Session ID generation and caching
  • Base64 encoded KEM ciphertext
  • Session expiration and renewal
  • Handshake success/failure events

Verification steps:

  1. Confirm sessionId cycles correctly after invalidateSession()
  2. Check that session is reused for multiple requests
  3. Verify new handshake occurs after TTL expiry
  4. Monitor handshake frequency vs expected TTL

Upgrading KEM Algorithms

When adding support for new ML-KEM variants:

Client-side steps:

  1. Update default in cli.properties: pqc.kem.algorithm=ML-KEM-1024
  2. Test handshake with new algorithm
  3. Verify session establishment and payload encryption

Server-side steps:

  1. Update server's allowed algorithms list in configuration
  2. Deploy updated PQC Handshake API
  3. Validate OpenAPI schema matches new algorithm

Coordination:

Both client and server must support the selected algorithm. If there's a mismatch, handshake will fail with HTTP 400 Bad Request.


SDK Documentation

Getting Started:


Last Updated: April 2026

Target Audience: SDK Developers, Integration Engineers, Security Architects


© 2026 AnkaTech. All rights reserved.