Skip to content

Flow 28 – Export and Share Public Keys for Secure Communication

Demonstrates the complete workflow for establishing secure bidirectional communication between two parties (organizations, systems, or partners) by exporting and sharing public keys. This is the foundation of B2B integration and distributed trust models.

Real-World Scenarios:

  • B2B integration: Two companies exchange public keys to communicate securely
  • Supplier onboarding: New partner receives your public key for encrypted invoices
  • API security: Distribute public keys for partner verification of API responses
  • Document signing: Share signing public key for contract verification

Public Key Distribution Methods:

  • Email: Attached as .pem or .b64 file (verify fingerprint out-of-band)
  • API endpoint: Publicly accessible endpoint returning JWK Set
  • QR code: Encoded public key for mobile apps
  • Certificate authority: Publish to trusted directory service

Steps:

  1. Generate local ML-KEM-768 encryption key
  2. Export local public encryption key (save as Base64)
  3. Generate local ML-DSA-87 signing key
  4. Export local public signing key (save as Base64)
  5. Generate partner's encryption and signing keys (simulation)
  6. Export partner's public keys
  7. Create outbound message for partner
  8. Sign message with our signing key
  9. Encrypt for partner using only their public key (utility function)
  10. Verify partner's signed message using only their public key (utility function)

Security Best Practices:

  • Fingerprint verification: Always verify public key fingerprints out-of-band (phone, SMS)
  • Key rotation: Periodically rotate keys and redistribute new public keys
  • Trust on first use (TOFU): Pin public key after first verified exchange
  • Format choice: Use JWK (JSON Web Key) for modern systems, PEM for legacy

Key Algorithms:

  • ML-KEM-768: NIST FIPS 203 post-quantum key encapsulation (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/crypto/stream/sign (sign messages)
  • POST /api/interoperability/encrypt (encrypt with partner's public key)
  • POST /api/interoperability/verify (verify partner's signature with public key)

When to use:

  • Establishing secure communication channels with new business partners
  • Distributing public keys for API authentication and verification
  • Implementing public key infrastructure (PKI) for inter-organizational communication
  • Onboarding suppliers who need to send encrypted data

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

  • export-key - Export public key metadata and material (relocated to Migration section)

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/ExampleScenario28.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.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.List;
import java.util.Properties;

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

/**
 * <h2>Scenario 28: Export and Share Public Keys for Secure Communication</h2>
 * <p>
 * Demonstrates the complete workflow for establishing secure bidirectional communication between
 * two parties (organizations, systems, or partners) by exporting and sharing public keys. This is
 * the foundation of B2B integration and distributed trust models.
 * </p>
 *
 * [Full Javadoc as in source file]
 *
 * @since 3.0.0
 */
public final class ExampleScenario28 {

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

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

    public static void main(String[] args) {
        System.out.println("===== SCENARIO 28: EXPORT AND SHARE PUBLIC KEYS =====");
        System.out.println("Purpose: Demonstrate public key distribution for secure B2B communication");
        System.out.println("Pattern: Generate → Export → Share → Communicate");
        System.out.println();

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

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

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

    private static void runScenario(AnkaSecureSdk sdk) throws Exception {

        // PHASE 1: Generate and Export Own Keys
        KeyPair localKeys = generateAndExportLocalKeys(sdk);

        // PHASE 2: Simulate Partner Keys
        KeyPair partnerKeys = generateAndExportPartnerKeys(sdk);

        // PHASE 3: Send Secure Message to Partner
        Path outboundEnc = sendSecureMessageToPartner(sdk, localKeys, partnerKeys);

        // PHASE 4: Verify Message from Partner
        receiveAndVerifyMessageFromPartner(sdk, partnerKeys);

        // PHASE 5: Display Security Audit Information
        printPublicKeyFingerprints(localKeys, partnerKeys);

        // FINAL STATUS
        System.out.println("╔═══════════════════════════════════════════════════════════════╗");
        System.out.println("║               ✅ SCENARIO 28 SUCCESSFUL                        ║");
        System.out.println("║                                                               ║");
        System.out.println("║  Public key sharing and secure communication demonstrated:    ║");
        System.out.println("║  • Public keys generated and exported successfully            ║");
        System.out.println("║  • Outbound message: Signed + encrypted for partner           ║");
        System.out.println("║  • Inbound message: Signature verified with partner's key     ║");
        System.out.println("║  • Public key fingerprints available for verification         ║");
        System.out.println("║  • No server-side key registration required                   ║");
        System.out.println("╚═══════════════════════════════════════════════════════════════╝");
    }

    /**
     * Generates local keys and exports public portions.
     */
    private static KeyPair generateAndExportLocalKeys(AnkaSecureSdk sdk) throws Exception {
        System.out.println("[Step 1/4] Generating local ML-KEM-768 encryption key...");

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

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

        System.out.println("[Step 2/4] Exporting local public encryption key...");

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

        Path kemPubFile = TEMP_DIR.resolve("scenario28_local_kem_public.b64");
        FileIO.writeUtf8(kemPubFile, kemPubB64);

        System.out.println("           Public key saved: " + kemPubFile);
        System.out.println("           Format: Base64-encoded");
        System.out.println("           Distribution: Share this file with partners");
        System.out.println();

        System.out.println("[Step 3/4] Generating local ML-DSA-87 signing key...");

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

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

        System.out.println("[Step 4/4] Exporting local public signing key...");

        ExportedKeySpec signExport = sdk.exportKey(signKid);
        String signPubB64 = signExport.getPublicKey();

        Path signPubFile = TEMP_DIR.resolve("scenario28_local_mlsa_public.b64");
        FileIO.writeUtf8(signPubFile, signPubB64);

        System.out.println("           Public key saved: " + signPubFile);
        System.out.println("           Distribution: Share this file for signature verification");
        System.out.println();

        KeyPair local = new KeyPair();
        local.kemKid = kemKid;
        local.signKid = signKid;
        local.kemPubB64 = kemPubB64;
        local.signPubB64 = signPubB64;

        return local;
    }

    /**
     * Generates partner keys (simulation).
     */
    private static KeyPair generateAndExportPartnerKeys(AnkaSecureSdk sdk) throws Exception {
        System.out.println("[Step 5/7] Simulating partner's ML-KEM-768 encryption key...");

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

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

        Path kemPubFile = TEMP_DIR.resolve("scenario28_partner_kem_public.b64");
        FileIO.writeUtf8(kemPubFile, kemPubB64);

        System.out.println("           Public key received from partner: " + kemPubFile);
        System.out.println();

        System.out.println("[Step 6/7] Simulating partner's ML-DSA-87 signing key...");

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

        ExportedKeySpec signExport = sdk.exportKey(signKid);
        String signPubB64 = signExport.getPublicKey();

        Path signPubFile = TEMP_DIR.resolve("scenario28_partner_mlsa_public.b64");
        FileIO.writeUtf8(signPubFile, signPubB64);

        System.out.println("           Public key received from partner: " + signPubFile);
        System.out.println();

        KeyPair partner = new KeyPair();
        partner.kemKid = kemKid;
        partner.signKid = signKid;
        partner.kemPubB64 = kemPubB64;
        partner.signPubB64 = signPubB64;

        return partner;
    }

    /**
     * Sends secure message to partner.
     */
    private static Path sendSecureMessageToPartner(AnkaSecureSdk sdk, KeyPair localKeys, KeyPair partnerKeys) throws Exception {
        System.out.println("[Step 7/10] Creating outbound message for partner...");

        Path outboundMsg = TEMP_DIR.resolve("scenario28_outbound_message.txt");
        FileIO.writeUtf8(outboundMsg, "Secure message from us to partner - Scenario 28.");

        System.out.println("           Message: " + outboundMsg);
        System.out.println();

        System.out.println("[Step 8/10] Signing message with our signing key...");

        Path signedMsg = TEMP_DIR.resolve("scenario28_outbound_signed.jws");
        SignResult signMeta = sdk.signFileStream(localKeys.signKid, outboundMsg, signedMsg);

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

        System.out.println("[Step 9/10] Encrypting for partner with their public key (utility)...");

        Path encMsg = TEMP_DIR.resolve("scenario28_outbound_encrypted.enc");
        sdk.encryptFileUtilityStream("ML-KEM", "ML-KEM-768", partnerKeys.kemPubB64, signedMsg, encMsg);

        System.out.println("           Encrypted for partner");
        System.out.println("           Output: " + encMsg);
        System.out.println("           Note: Used partner's PUBLIC key only (no server-side key)");
        System.out.println("           Ready to send to partner via email/API/etc.");
        System.out.println();

        return encMsg;
    }

    /**
     * Demonstrates receiving and verifying a message from partner.
     */
    private static void receiveAndVerifyMessageFromPartner(AnkaSecureSdk sdk, KeyPair partnerKeys) throws Exception {
        System.out.println("[Step 10/10] Simulating signature verification from partner...");

        // Partner creates and signs a message
        Path inboundPlain = TEMP_DIR.resolve("scenario28_inbound_plaintext.txt");
        FileIO.writeUtf8(inboundPlain, "Reply from partner - Scenario 28.");

        Path inboundSigned = TEMP_DIR.resolve("scenario28_inbound_signed.jws");
        SignResult partnerSignMeta = sdk.signFileStream(partnerKeys.signKid, inboundPlain, inboundSigned);

        System.out.println("           Message signed by partner");
        System.out.println("           (In production: received signature + public key from partner)");
        System.out.println();

        // Verify partner's signature using only their public key
        String signedJwsB64 = java.util.Base64.getEncoder().encodeToString(
                java.nio.file.Files.readAllBytes(inboundSigned));

        VerifySignatureResult verifyMeta = sdk.verifySignatureUtilityStream(
                "ML-DSA", "ML-DSA-87", partnerKeys.signPubB64, signedJwsB64, inboundPlain);

        System.out.println("           ✅ Signature verified with utility function");
        System.out.println("           Signature valid: " + verifyMeta.isValid());
        System.out.println("           Note: Used partner's PUBLIC key only (not in server keystore)");

        if (verifyMeta.isValid()) {
            String content = FileIO.readUtf8(inboundPlain);
            System.out.println("           Message: \"" + content + "\"");
            System.out.println("           ✅ Authenticity confirmed: Message signed by partner");
        } else {
            System.out.println("           ❌ Signature verification FAILED");
        }
        System.out.println();
    }

    /**
     * Displays public key fingerprints for verification.
     */
    private static void printPublicKeyFingerprints(KeyPair localKeys, KeyPair partnerKeys) throws Exception {
        System.out.println("Public Key Fingerprints (SHA-256):");
        System.out.println("Used for out-of-band verification (phone call, SMS, etc.)");
        System.out.println();

        System.out.println("  OUR PUBLIC KEYS:");
        System.out.println("    Encryption (ML-KEM-768):");
        System.out.println("      " + calculateFingerprint(localKeys.kemPubB64));
        System.out.println("    Signing (ML-DSA-87):");
        System.out.println("      " + calculateFingerprint(localKeys.signPubB64));
        System.out.println();

        System.out.println("  PARTNER'S PUBLIC KEYS:");
        System.out.println("    Encryption (ML-KEM-768):");
        System.out.println("      " + calculateFingerprint(partnerKeys.kemPubB64));
        System.out.println("    Signing (ML-DSA-87):");
        System.out.println("      " + calculateFingerprint(partnerKeys.signPubB64));
        System.out.println();

        System.out.println("IMPORTANT: Always verify these fingerprints with your partner");
        System.out.println("via a trusted out-of-band channel (phone, video call, SMS)");
        System.out.println("to prevent man-in-the-middle attacks.");
        System.out.println();
    }

    /**
     * Calculates SHA-256 fingerprint of public key.
     */
    private static String calculateFingerprint(String publicKeyBase64) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64);
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(keyBytes);

        // Format as hex with colons (standard fingerprint format)
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < hash.length; i++) {
            if (i > 0 && i % 2 == 0) sb.append(":");
            sb.append(String.format("%02X", hash[i]));
        }

        return sb.toString();
    }

    /**
     * Helper class to hold key pair information.
     */
    private static class KeyPair {
        String kemKid;
        String signKid;
        String kemPubB64;
        String signPubB64;
    }
}

Running This Example

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

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

Expected Output:

===== SCENARIO 28: EXPORT AND SHARE PUBLIC KEYS =====
Purpose: Demonstrate public key distribution for secure B2B communication
Pattern: Generate → Export → Share → Communicate

╔═══════════════════════════════════════════════════════════════╗
║  PHASE 1: GENERATE AND EXPORT OWN PUBLIC KEYS                ║
╚═══════════════════════════════════════════════════════════════╝

[Step 1/4] Generating local ML-KEM-768 encryption key...
           Key ID: sc28_local_kem768_1735152000000

[Step 2/4] Exporting local public encryption key...
           Public key saved: temp_files/scenario28_local_kem_public.b64
           Format: Base64-encoded
           Distribution: Share this file with partners

[Step 3/4] Generating local ML-DSA-87 signing key...
           Key ID: sc28_local_mlsa87_1735152000100

[Step 4/4] Exporting local public signing key...
           Public key saved: temp_files/scenario28_local_mlsa_public.b64
           Distribution: Share this file for signature verification

╔═══════════════════════════════════════════════════════════════╗
║  PHASE 2: SIMULATE PARTNER KEYS                               ║
╚═══════════════════════════════════════════════════════════════╝

[Step 5/7] Simulating partner's ML-KEM-768 encryption key...
           Public key received from partner: temp_files/scenario28_partner_kem_public.b64

[Step 6/7] Simulating partner's ML-DSA-87 signing key...
           Public key received from partner: temp_files/scenario28_partner_mlsa_public.b64

╔═══════════════════════════════════════════════════════════════╗
║  PHASE 3: SEND SECURE MESSAGE TO PARTNER                      ║
╚═══════════════════════════════════════════════════════════════╝

[Step 7/10] Creating outbound message for partner...
           Message: temp_files/scenario28_outbound_message.txt

[Step 8/10] Signing message with our signing key...
           Signature created

[Step 9/10] Encrypting for partner with their public key (utility)...
           Encrypted for partner
           Output: temp_files/scenario28_outbound_encrypted.enc
           Note: Used partner's PUBLIC key only (no server-side key)
           Ready to send to partner via email/API/etc.

╔═══════════════════════════════════════════════════════════════╗
║  PHASE 4: VERIFY MESSAGE FROM PARTNER                         ║
╚═══════════════════════════════════════════════════════════════╝

[Step 10/10] Simulating signature verification from partner...
           Message signed by partner
           (In production: received signature + public key from partner)

           ✅ Signature verified with utility function
           Signature valid: true
           Note: Used partner's PUBLIC key only (not in server keystore)
           Message: "Reply from partner - Scenario 28."
           ✅ Authenticity confirmed: Message signed by partner

╔═══════════════════════════════════════════════════════════════╗
║  PHASE 5: SECURITY AUDIT INFORMATION                          ║
╚═══════════════════════════════════════════════════════════════╝

Public Key Fingerprints (SHA-256):
Used for out-of-band verification (phone call, SMS, etc.)

  OUR PUBLIC KEYS:
    Encryption (ML-KEM-768):
      A1:B2:C3:D4:E5:F6:78:90:...
    Signing (ML-DSA-87):
      1A:2B:3C:4D:5E:6F:78:89:...

  PARTNER'S PUBLIC KEYS:
    Encryption (ML-KEM-768):
      9F:8E:7D:6C:5B:4A:39:28:...
    Signing (ML-DSA-87):
      F9:E8:D7:C6:B5:A4:93:82:...

IMPORTANT: Always verify these fingerprints with your partner
via a trusted out-of-band channel (phone, video call, SMS)
to prevent man-in-the-middle attacks.

╔═══════════════════════════════════════════════════════════════╗
║               ✅ SCENARIO 28 SUCCESSFUL                        ║
║                                                               ║
║  Public key sharing and secure communication demonstrated:    ║
║  • Public keys generated and exported successfully            ║
║  • Outbound message: Signed + encrypted for partner           ║
║  • Inbound message: Signature verified with partner's key     ║
║  • Public key fingerprints available for verification         ║
║  • No server-side key registration required                   ║
╚═══════════════════════════════════════════════════════════════╝
===== SCENARIO 28 END =====

Key Concepts

Public Key Infrastructure (PKI)

This example demonstrates a lightweight PKI model:

  1. Key Generation: Each party generates encryption + signing key pairs
  2. Public Key Export: Export public portions for distribution
  3. Out-of-Band Verification: Verify fingerprints via phone/SMS to prevent MITM
  4. Secure Communication: Sign-then-encrypt for authenticity + confidentiality

Fingerprint Verification

SHA-256 fingerprints provide:

  • Human-verifiable: Can be read over phone
  • Collision-resistant: SHA-256 prevents fake key attacks
  • Standard format: Hex with colons (A1:B2:C3:...)
  • Trust anchor: First verification establishes trust

Utility Functions

This flow uses Interoperability endpoints:

  • POST /api/interoperability/encrypt - Encrypt with partner's public key (no keystore)
  • POST /api/interoperability/verify - Verify with partner's public key (no keystore)

Benefits: - No keystore pollution with one-time partner keys - Stateless operations - Perfect for dynamic partner relationships


  • Flow 25 - External Key Interoperability (encryption + verification only)
  • Flow 24 - Sign-Then-Encrypt Nested Operations (authenticated encryption pattern)
  • Flow 14 - Key Lifecycle Walkthrough (export/import patterns)