Skip to content

Flow 25 – External Key Interoperability

Demonstrates how to perform cryptographic operations using externally-provided public keys without importing them into the AnkaSecure server keystore. This is critical for B2B integration scenarios where you receive a partner's public key and need to encrypt data or verify signatures without registering their key in your system.

Real-World Use Cases:

  • Partner integration: Encrypt data with partner's public key for transmission
  • Signature verification: Verify documents signed by external parties
  • One-time operations: Cryptographic operations without key persistence
  • Distributed trust: Public key distribution via out-of-band channels

Utility Functions vs. Server-Side Operations:

Feature Utility Functions Server-Side Operations
Key storage Caller provides public key Key stored in server keystore
Use case External partners, one-time ops Internal operations, repeated use
Key management Caller manages key lifecycle Server manages expiry, rotation

Steps:

  1. Generate ML-KEM-768 key (simulating external partner)
  2. Export partner's public encryption key
  3. Encrypt file using only public key (no server-side key registration)
  4. Generate ML-DSA-87 signing key (simulating external signer)
  5. Sign file with server key
  6. Verify signature using only signer's public key (utility function)

Key Algorithms:

  • ML-KEM-768: NIST FIPS 203 post-quantum key encapsulation for encryption
  • ML-DSA-87: NIST FIPS 204 post-quantum digital signature

API Endpoints:

  • POST /api/key-management/keys (generate keys)
  • GET /api/migration/keys/{kid} (export public keys)
  • POST /api/interoperability/encrypt (utility encryption with external public key)
  • POST /api/crypto/stream/sign (server-side signing)
  • POST /api/interoperability/verify (utility verification with external public key)

When to use:

  • B2B integration where you receive partner public keys via secure channels (email, API)
  • One-time signature verification from external PKI systems
  • Encrypting data for external recipients without permanent key storage
  • Cross-organization cryptographic collaboration without trust infrastructure

Maps to CLI commands (NEW in v3.0.0):

  • verify-external - Verify signatures with external public keys
  • encrypt-external - Encrypt with external public keys

Dependency — this example imports co.ankatech.ankasecure.sdk.examples.ExampleUtil. If you have not copied that class yet, see example_util.md.


Complete Java Implementation

Source: src/main/java/co/ankatech/ankasecure/sdk/examples/ExampleScenario25.java

package co.ankatech.ankasecure.sdk.examples;

import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import co.ankatech.ankasecure.sdk.model.*;
import co.ankatech.ankasecure.sdk.util.FileIO;

import java.nio.file.Path;
import java.util.List;
import java.util.Properties;

import static co.ankatech.ankasecure.sdk.examples.ExampleUtil.*;

/**
 * <h2>Scenario 25: External Key Interoperability</h2>
 * <p>
 * Demonstrates how to perform cryptographic operations using externally-provided public keys
 * without importing them into the AnkaSecure server keystore. This is critical for B2B
 * integration scenarios where you receive a partner's public key and need to encrypt data
 * or verify signatures without registering their key in your system.
 * </p>
 *
 * <h3>Real-World Use Cases:</h3>
 * <ul>
 *   <li><strong>Partner integration:</strong> Encrypt data with partner's public key for transmission</li>
 *   <li><strong>Signature verification:</strong> Verify documents signed by external parties</li>
 *   <li><strong>One-time operations:</strong> Cryptographic operations without key persistence</li>
 *   <li><strong>Distributed trust:</strong> Public key distribution via out-of-band channels</li>
 * </ul>
 *
 * <h3>Utility Functions vs. Server-Side Operations:</h3>
 * <table border="1">
 *   <tr>
 *     <th>Feature</th>
 *     <th>Utility Functions</th>
 *     <th>Server-Side Operations</th>
 *   </tr>
 *   <tr>
 *     <td>Key storage</td>
 *     <td>Caller provides public key</td>
 *     <td>Key stored in server keystore</td>
 *   </tr>
 *   <tr>
 *     <td>Use case</td>
 *     <td>External partners, one-time ops</td>
 *     <td>Internal operations, repeated use</td>
 *   </tr>
 *   <tr>
 *     <td>Key management</td>
 *     <td>Caller manages key lifecycle</td>
 *     <td>Server manages expiry, rotation</td>
 *   </tr>
 * </table>
 *
 * <h3>Steps:</h3>
 * <ol>
 *   <li>Generate ML-KEM-768 key (simulating external partner)</li>
 *   <li>Export partner's public encryption key</li>
 *   <li>Encrypt file using only public key (no server-side key registration)</li>
 *   <li>Generate ML-DSA-87 signing key (simulating external signer)</li>
 *   <li>Sign file with server key</li>
 *   <li>Verify signature using only signer's public key (utility function)</li>
 * </ol>
 *
 * <h3>Key Algorithms:</h3>
 * <ul>
 *   <li><strong>ML-KEM-768:</strong> Post-quantum key encapsulation for encryption</li>
 *   <li><strong>ML-DSA-87:</strong> Post-quantum digital signature</li>
 * </ul>
 *
 * <h3>API Endpoints:</h3>
 * <ul>
 *   <li>POST /api/key-management/keys (generate keys)</li>
 *   <li>GET /api/migration/keys/{kid} (export public keys)</li>
 *   <li>POST /api/interoperability/encrypt (utility encryption with external public key)</li>
 *   <li>POST /api/crypto/stream/sign (server-side signing)</li>
 *   <li>POST /api/interoperability/verify (utility verification with external public key)</li>
 * </ul>
 *
 * @since 3.0.0
 */
public final class ExampleScenario25 {

    private static final Path TEMP_DIR = Path.of("temp_files");

    private ExampleScenario25() {
        /* static only */
    }

    public static void main(String[] args) {
        System.out.println("===== SCENARIO 25: EXTERNAL KEY INTEROPERABILITY =====");
        System.out.println("Purpose: Demonstrate cryptographic operations with external public keys");
        System.out.println("Pattern: Utility functions for partner integration");
        System.out.println();

        try {
            ensureTempDir(TEMP_DIR);
            Properties props = loadProperties();
            AnkaSecureSdk sdk = authenticate(props);
            runScenario(sdk);

            System.out.println("===== SCENARIO 25 END =====");

        } catch (Exception ex) {
            fatal("Scenario 25 failed", ex);
        }
    }

    private static void runScenario(AnkaSecureSdk sdk) throws Exception {

        // ============================================================
        // PHASE 1: Encryption Interoperability
        // ============================================================

        System.out.println("╔═══════════════════════════════════════════════════════════════╗");
        System.out.println("║  PHASE 1: ENCRYPTION INTEROPERABILITY                         ║");
        System.out.println("║  (Encrypt with external public key, decrypt with server key)  ║");
        System.out.println("╚═══════════════════════════════════════════════════════════════╝");
        System.out.println();

        demonstrateEncryptionInterop(sdk);

        // ============================================================
        // PHASE 2: Signature Verification Interoperability
        // ============================================================

        System.out.println("╔═══════════════════════════════════════════════════════════════╗");
        System.out.println("║  PHASE 2: SIGNATURE VERIFICATION INTEROPERABILITY             ║");
        System.out.println("║  (Sign with server key, verify with external public key)      ║");
        System.out.println("╚═══════════════════════════════════════════════════════════════╝");
        System.out.println();

        demonstrateSignatureInterop(sdk);

        // ============================================================
        // FINAL STATUS
        // ============================================================

        System.out.println("╔═══════════════════════════════════════════════════════════════╗");
        System.out.println("║               ✅ SCENARIO 25 SUCCESSFUL                        ║");
        System.out.println("║                                                               ║");
        System.out.println("║  External key interoperability validated:                     ║");
        System.out.println("║  • Utility encryption with partner public key works           ║");
        System.out.println("║  • Utility verification with signer public key works          ║");
        System.out.println("║  • No server-side key registration required                   ║");
        System.out.println("╚═══════════════════════════════════════════════════════════════╝");
    }

    /**
     * Demonstrates encryption using external public key.
     */
    private static void demonstrateEncryptionInterop(AnkaSecureSdk sdk) throws Exception {

        System.out.println("[Step 1/3] Generating ML-KEM-768 key (simulating external partner)...");

        String kemKid = "sc25_external_kem768_" + System.currentTimeMillis();
        sdk.generateKey(new GenerateKeySpec()
                .setKid(kemKid)
                .setKty("ML-KEM")
                .setAlg("ML-KEM-768")
                .setKeyOps(List.of("encrypt", "decrypt")));

        System.out.println("           Partner key ID: " + kemKid);
        System.out.println();

        System.out.println("[Step 2/3] Exporting partner's public key...");

        ExportedKeySpec kemExport = sdk.exportKey(kemKid);
        String kemPubB64 = kemExport.getPublicKey();

        // Save to file (simulates receiving from partner via email, API, etc.)
        Path kemPubFile = TEMP_DIR.resolve("scenario25_partner_kem_public.b64");
        FileIO.writeUtf8(kemPubFile, kemPubB64);

        System.out.println("           Public key saved: " + kemPubFile);
        System.out.println("           (In production: received via secure channel)");
        System.out.println();

        System.out.println("[Step 3/3] Encrypting with utility function (using public key only)...");

        Path plainFile = TEMP_DIR.resolve("scenario25_plain.txt");
        FileIO.writeUtf8(plainFile, "Scenario-25: External key interoperability demo.");

        Path utilEncFile = TEMP_DIR.resolve("scenario25_utility_encrypted.enc");

        // CRITICAL: This encryption uses ONLY the public key, no server-side key storage
        EncryptResult encMeta = sdk.encryptFileUtilityStream("ML-KEM", "ML-KEM-768", kemPubB64, plainFile, utilEncFile);

        System.out.println("           ✅ Encrypted with utility function");
        System.out.println("           Output: " + utilEncFile);
        System.out.println("           Key used: Partner's public key (not in server keystore)");
        System.out.println("           Algorithm: " + encMeta.getAlgorithmUsed());
        System.out.println();
        System.out.println("           Note: Partner would decrypt this file with their private key");
        System.out.println("           in their own AnkaSecure instance (simulated by KID: " + kemKid + ")");
        System.out.println();
    }

    /**
     * Demonstrates signature verification using external public key.
     */
    private static void demonstrateSignatureInterop(AnkaSecureSdk sdk) throws Exception {

        System.out.println("[Step 4/6] Generating ML-DSA-87 signing key (simulating external signer)...");

        String signKid = "sc25_external_mlsa87_" + System.currentTimeMillis();
        sdk.generateKey(new GenerateKeySpec()
                .setKid(signKid)
                .setKty("ML-DSA")
                .setAlg("ML-DSA-87")
                .setKeyOps(List.of("sign", "verify")));

        System.out.println("           Signer key ID: " + signKid);
        System.out.println();

        System.out.println("[Step 5/6] Signing with server key...");

        Path plainFile = TEMP_DIR.resolve("scenario25_plain.txt");
        Path signedFile = TEMP_DIR.resolve("scenario25_signed.jws");

        SignResult signMeta = sdk.signFileStream(signKid, plainFile, signedFile);

        System.out.println("           Signature created");
        printSignMeta(signMeta);
        System.out.println();

        System.out.println("[Step 6/6] Verifying signature with utility function (using public key only)...");

        // Export signer's public key
        ExportedKeySpec signExport = sdk.exportKey(signKid);
        String signPubB64 = signExport.getPublicKey();

        // Save to file (simulates receiving from signer)
        Path signPubFile = TEMP_DIR.resolve("scenario25_signer_mlsa_public.b64");
        FileIO.writeUtf8(signPubFile, signPubB64);

        System.out.println("           Public key exported: " + signPubFile);
        System.out.println("           (In production: received from signer for verification)");
        System.out.println();

        // CRITICAL: This verification uses ONLY the public key, no server-side key storage
        // Read detached JWS file and Base64 encode it for utility method
        String signedJwsB64 = java.util.Base64.getEncoder().encodeToString(
                java.nio.file.Files.readAllBytes(signedFile));

        // Verify the detached signature against the original payload
        VerifySignatureResult verifyMeta = sdk.verifySignatureUtilityStream(
                "ML-DSA", "ML-DSA-87", signPubB64, signedJwsB64, plainFile);

        System.out.println("           Signature verified with utility function");
        System.out.println("           Signature valid: " + verifyMeta.isValid());
        System.out.println("           Key used: Signer's public key (not in server keystore)");

        if (verifyMeta.isValid()) {
            System.out.println("           ✅ Validation: Signature is VALID");
            System.out.println("           Utility verification works correctly!");
        } else {
            System.out.println("           ❌ Validation: Signature verification FAILED");
        }
        System.out.println();
    }
}

Running This Example

# Compile
javac -cp "ankasecure-sdk-3.0.0.jar:." ExampleScenario25.java

# Run
java -cp "ankasecure-sdk-3.0.0.jar:." co.ankatech.ankasecure.sdk.examples.ExampleScenario25

Expected Output:

===== SCENARIO 25: EXTERNAL KEY INTEROPERABILITY =====
Purpose: Demonstrate cryptographic operations with external public keys
Pattern: Utility functions for partner integration

╔═══════════════════════════════════════════════════════════════╗
║  PHASE 1: ENCRYPTION INTEROPERABILITY                         ║
║  (Encrypt with external public key, decrypt with server key)  ║
╚═══════════════════════════════════════════════════════════════╝

[Step 1/3] Generating ML-KEM-768 key (simulating external partner)...
           Partner key ID: sc25_external_kem768_1735152000000

[Step 2/3] Exporting partner's public key...
           Public key saved: temp_files/scenario25_partner_kem_public.b64
           (In production: received via secure channel)

[Step 3/3] Encrypting with utility function (using public key only)...
           ✅ Encrypted with utility function
           Output: temp_files/scenario25_utility_encrypted.enc
           Key used: Partner's public key (not in server keystore)
           Algorithm: ML-KEM-768

           Note: Partner would decrypt this file with their private key
           in their own AnkaSecure instance

╔═══════════════════════════════════════════════════════════════╗
║  PHASE 2: SIGNATURE VERIFICATION INTEROPERABILITY             ║
║  (Sign with server key, verify with external public key)      ║
╚═══════════════════════════════════════════════════════════════╝

[Step 4/6] Generating ML-DSA-87 signing key (simulating external signer)...
           Signer key ID: sc25_external_mlsa87_1735152000100

[Step 5/6] Signing with server key...
           Signature created

[Step 6/6] Verifying signature with utility function (using public key only)...
           Public key exported: temp_files/scenario25_signer_mlsa_public.b64
           (In production: received from signer for verification)

           Signature verified with utility function
           Signature valid: true
           Key used: Signer's public key (not in server keystore)
           ✅ Validation: Signature is VALID
           Utility verification works correctly!

╔═══════════════════════════════════════════════════════════════╗
║               ✅ SCENARIO 25 SUCCESSFUL                        ║
║                                                               ║
║  External key interoperability validated:                     ║
║  • Utility encryption with partner public key works           ║
║  • Utility verification with signer public key works          ║
║  • No server-side key registration required                   ║
╚═══════════════════════════════════════════════════════════════╝
===== SCENARIO 25 END =====

Key Concepts

Interoperability vs Migration

This example demonstrates Interoperability operations, which are distinct from Migration operations:

Aspect Interoperability (This Flow) Migration (Flow 13, 26)
Key Storage Keys NOT stored in server Keys imported into keystore
Use Case Continuous cross-platform ops One-time legacy migration
Duration Ongoing operations One-time setup

Utility Functions

Utility functions (encryptFileUtilityStream, verifySignatureUtilityStream) enable operations with caller-supplied public keys:

  • No keystore pollution: Keys not permanently stored
  • Stateless: Each request provides the public key
  • Temporary: Ideal for one-time or infrequent operations
  • B2B friendly: Partner manages their own key lifecycle

Public Key Exchange

Secure public key distribution requires:

  1. Secure transmission: Email, HTTPS API, secure file transfer
  2. Fingerprint verification: Verify SHA-256 fingerprint via out-of-band channel (phone, SMS)
  3. Format: Base64-encoded DER or PEM format
  4. Metadata: Include algorithm, expiry, intended use

  • Flow 10 - Public-Key Utility Encrypt/Decrypt (ML-KEM-1024)
  • Flow 11 - Public-Key Utility Sign/Verify (ML-DSA-87)
  • Flow 28 - Export and Share Public Keys (bidirectional B2B)
  • Flow 26 - PKCS#7 to JOSE Migration (import keys permanently)