Skip to content

Using the AnkaSecure SDK in Java

SDK Version: 3.0.0 Java Version: 17+ Artifact: AnkaSecureSDK-3.0.0.jar

This guide shows how to integrate the AnkaSecure SDK into your own Java application. It assumes you have the AnkaSecureSDK JAR available (either through a Maven dependency or direct inclusion) and that you have valid credentials (clientId, clientSecret for application authentication or username/password for user authentication).

Code Examples

This documentation contains code examples showing how to use the SDK API. These examples are intended for developers integrating the SDK into their applications.

For complete working examples, see the Integration Flows section which provides 28 detailed scenarios.


1. Setup & Dependencies

There are two common approaches:

If the SDK is published to an internal or public artifact repository, add to your pom.xml:

<dependency>
    <groupId>co.ankatech.secureclient</groupId>
    <artifactId>AnkaSecureSDK</artifactId>
    <version>3.0.0</version>
</dependency>

Gradle:

implementation 'co.ankatech.secureclient:AnkaSecureSDK:3.0.0'

1.2 Local JAR (Alternative)

Download or build the AnkaSecure SDK JAR and add it to your project's classpath:

Maven (system scope):

<dependency>
    <groupId>co.ankatech.secureclient</groupId>
    <artifactId>AnkaSecureSDK</artifactId>
    <version>3.0.0</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/libs/AnkaSecureSDK-3.0.0.jar</systemPath>
</dependency>

Gradle:

implementation files('libs/AnkaSecureSDK-3.0.0.jar')

2. Loading Configuration

The SDK requires connection details (scheme, host, port) and authentication credentials. You can store them in a Properties object, often loaded from a configuration file:

Properties props = new Properties();
try (FileInputStream fis = new FileInputStream("cli.properties")) {
    props.load(fis);
}
Example sdk-config.properties:

# Application credentials
clientId=mySdkApp
clientSecret=mySdkSecret

# API connection
openapi.scheme=https
openapi.host=demo.ankatech.co
openapi.port=443

# TLS settings
openapi.insecureSkipTlsVerify=false

# Timeouts (milliseconds)
openapi.connectTimeoutMs=10000
openapi.readTimeoutMs=30000
openapi.writeTimeoutMs=30000

Security Note

Never commit clientSecret to version control. Use environment variables or secret managers in production. If your application must persist credentials locally, encrypt them at rest using AES-256-GCM with PBKDF2 key derivation.

See Security Best Practices for complete credential management guidance including encryption examples.


3. Instantiating the SDK Factory

After loading properties, create an unauthenticated SDK factory:

import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import java.util.Properties;

Properties props = loadProperties();  // from previous section

// Create the SDK factory (unauthenticated)
AnkaSecureSdk factory = new AnkaSecureSdk(props);

Note: The factory itself does not perform cryptographic operations. You must authenticate to obtain an AuthenticatedSdk instance (see next section).


4. Authentication

Authenticate the factory to obtain an immutable, thread-safe AuthenticatedSdk instance:

import co.ankatech.ankasecure.sdk.AuthenticatedSdk;

try {
    // Application-based authentication (recommended)
    String clientId = props.getProperty("clientId");
    String clientSecret = props.getProperty("clientSecret");

    // Returns AuthenticatedSdk instance (immutable, thread-safe)
    AuthenticatedSdk sdk = factory.authenticateApplication(clientId, clientSecret);

    // Or, user-based authentication if required:
    // AuthenticatedSdk sdk = factory.authenticateUser("username", "password", "tenantId");

    // Now use sdk for all operations
    EncryptResult result = sdk.encrypt("my-key", data);

} catch (AnkaSecureSdkException e) {
    System.err.println("Authentication failed: " + e.getMessage());
    return;
}

Key architectural points:

  • Factory pattern: AnkaSecureSdk is the factory, not the operational SDK
  • Return value: Authentication methods return AuthenticatedSdk (not void)
  • Thread-safety: AuthenticatedSdk is immutable and can be shared across 10,000+ threads
  • Token lifecycle: The authenticated instance remains valid until the JWT expires (check with sdk.isTokenExpired())

Authentication methods:

  • Application authentication (recommended): Use clientId and clientSecret from Tenant Admin CLI
  • User authentication: Use username and password (for user-based operations)

5. Key Operations

5.1 Generating Keys

Simple Keys (classical, PQC, symmetric):

import co.ankatech.ankasecure.sdk.model.GenerateKeySpec;
import co.ankatech.ankasecure.sdk.model.KeyGenerationSummarySpec;

// Generate ML-KEM-768 key
GenerateKeySpec spec = new GenerateKeySpec()
    .setKid("my-ml-kem-key")
    .setKty("ML-KEM")
    .setAlg("ML-KEM-768")
    .setExpiresAt(ZonedDateTime.now().plusYears(1))
    .setMaxUsageLimit(100000);

KeyGenerationSummarySpec result = sdk.generateKey(spec);
System.out.println("Key generated: " + result.getKid());

Composite Keys (hybrid classical + PQC):

import co.ankatech.ankasecure.sdk.model.GenerateCompositeKeySpec;
import co.ankatech.ankasecure.sdk.model.ComponentSpec;

// Generate HYBRID_KEM_COMBINE composite key
GenerateCompositeKeySpec spec = new GenerateCompositeKeySpec()
    .setKid("hybrid-encryption-key")
    .setMode(GenerateCompositeKeySpec.Mode.HYBRID_KEM_COMBINE)
    .addComponent(ComponentSpec.classical(ClassicalCompositeAlgorithm.X25519))
    .addComponent(ComponentSpec.pqc(PqcCompositeAlgorithm.ML_KEM_768))
    .setKdf(Kdf.HKDF_SHA256);

KeyGenerationSummarySpec result = sdk.generateCompositeKey(spec);
System.out.println("Hybrid key generated: " + result.getKid());

Supported key types:

  • Simple keys: 81 algorithms (classical, PQC, symmetric)
  • Composite keys: 35 validated combinations (24 HYBRID_KEM_COMBINE + 11 DUALSIGN)

5.2 Listing Keys

import co.ankatech.ankasecure.sdk.model.KeyMetadata;
import java.util.List;

List<KeyMetadata> keys = sdk.listKeys();

System.out.println("Total keys: " + keys.size());
for (KeyMetadata key : keys) {
    System.out.println("  - " + key.getKid() +
                       " (" + key.getAlg() + ")" +
                       " - Status: " + key.getStatus());
}

5.3 Export Keys

import co.ankatech.ankasecure.sdk.model.ExportedKeySpec;

ExportedKeySpec exported = sdk.exportKey("my-ml-kem-key");
String publicKeyB64 = exported.getPublicKey();
System.out.println("Public key: " + publicKeyB64);

5.4 Remove Key

sdk.removeKey("my-old-key");

6. Encryption & Decryption

6.1 In-Memory Operations (< 100 MB)

import co.ankatech.ankasecure.sdk.model.EncryptResult;
import co.ankatech.ankasecure.sdk.model.DecryptResult;
import java.nio.charset.StandardCharsets;

// Encrypt
byte[] plaintext = "Sensitive data".getBytes(StandardCharsets.UTF_8);
EncryptResult encResult = sdk.encrypt("my-ml-kem-key", plaintext);
String jweToken = encResult.getJweToken();

// Decrypt
DecryptResult decResult = sdk.decrypt(jweToken);
byte[] recovered = decResult.getDecryptedData();
System.out.println("Decrypted: " + new String(recovered, StandardCharsets.UTF_8));

6.2 File Operations (Non-Streaming, < 100 MB)

import java.nio.file.Path;

Path inputFile = Path.of("/data/document.pdf");
Path encryptedFile = Path.of("/data/document.pdf.jwe");
Path decryptedFile = Path.of("/data/document_decrypted.pdf");

// Encrypt file
EncryptResult encResult = sdk.encryptFile("my-key", inputFile, encryptedFile);

// Decrypt file
DecryptResultMetadata decMeta = sdk.decryptFile(encryptedFile, decryptedFile);

6.3 Streaming Operations (Large Files > 100 MB)

Path largeFile = Path.of("/data/backup_500MB.tar.gz");
Path encrypted = Path.of("/data/backup_500MB.tar.gz.jwe");
Path decrypted = Path.of("/data/backup_500MB_restored.tar.gz");

// Encrypt with streaming (detached JWE format)
EncryptResult encResult = sdk.encryptFileStream("archive-key", largeFile, encrypted);

// Decrypt with streaming
DecryptResultMetadata decMeta = sdk.decryptFileStream(encrypted, decrypted);

7. Signing & Verification

7.1 In-Memory Operations

import co.ankatech.ankasecure.sdk.model.SignResult;
import co.ankatech.ankasecure.sdk.model.VerifySignatureResult;

byte[] document = Files.readAllBytes(Path.of("contract.pdf"));

// Sign
SignResult signResult = sdk.sign("my-signing-key", document);
String jwsToken = signResult.getJwsToken();

// Verify
VerifySignatureResult verifyResult = sdk.verifySignature(jwsToken);

if (verifyResult.isValid()) {
    System.out.println("Signature is VALID");
} else {
    System.out.println("Signature is INVALID");
}

7.2 File Operations

Path document = Path.of("/legal/contract.pdf");
Path signature = Path.of("/legal/contract.pdf.jws");

// Sign file
SignResult signResult = sdk.signFile("legal-signing-key", document, signature);

// Verify signature
VerifySignatureResult verifyResult = sdk.verifySignature(signature);
System.out.println("Valid: " + verifyResult.isValid());

7.3 Streaming Operations (Large Files)

Path largeLog = Path.of("/audit/access_log_500MB.csv");
Path signature = Path.of("/audit/access_log.sig");

// Sign with streaming
SignResult signResult = sdk.signFileStream("audit-key", largeLog, signature);

// Verify with streaming
VerifySignatureResult verifyResult = sdk.verifySignatureStream(largeLog, signature);

8. Re-encrypt & Re-sign (Key Rotation)

8.1 Re-encrypt

Switch encrypted data from one key to another without exposing plaintext:

import co.ankatech.ankasecure.sdk.model.ReencryptResult;

String oldJwe = "eyJhbGc...";  // Encrypted with old-key-2023

ReencryptResult result = sdk.reencrypt("new-key-2024", oldJwe);
String newJwe = result.getJweToken();

System.out.println("Migrated from: " + result.getOldKeyAlgorithmUsed());
System.out.println("Migrated to: " + result.getNewKeyAlgorithmUsed());

8.2 Re-sign

import co.ankatech.ankasecure.sdk.model.ResignResult;

String oldJws = "eyJhbGc...";  // Signed with old-rsa-key

ResignResult result = sdk.resign("new-ml-dsa-key", oldJws);
String newJws = result.getJwsToken();

For file-based rotation, use sdk.reencryptFile(...) or sdk.resignFile(...). For streaming (files > 100MB), use sdk.reencryptFileStream(...) or sdk.resignFileStream(...).


9. Composite Hybrid Keys (Quantum-Resistant)

Composite keys combine classical and post-quantum algorithms for defense-in-depth security.

9.1 Generate Hybrid Encryption Key (HYBRID_KEM_COMBINE)

import co.ankatech.ankasecure.sdk.model.GenerateCompositeKeySpec;
import co.ankatech.ankasecure.sdk.model.ComponentSpec;
import co.ankatech.ankasecure.sdk.model.Kdf;

GenerateCompositeKeySpec spec = new GenerateCompositeKeySpec()
    .setKid("hybrid-encryption-key")
    .setMode(GenerateCompositeKeySpec.Mode.HYBRID_KEM_COMBINE)
    .addComponent(ComponentSpec.classical(ClassicalCompositeAlgorithm.X25519))
    .addComponent(ComponentSpec.pqc(PqcCompositeAlgorithm.ML_KEM_768))
    .setKdf(Kdf.HKDF_SHA256);

KeyGenerationSummarySpec result = sdk.generateCompositeKey(spec);

Security: Requires BOTH X25519 and ML-KEM-768 to decrypt (AND-decrypt model).

9.2 Generate Dual Signature Key (DUALSIGN)

GenerateCompositeKeySpec spec = new GenerateCompositeKeySpec()
    .setKid("dual-signature-key")
    .setMode(GenerateCompositeKeySpec.Mode.DUALSIGN)
    .addComponent(ComponentSpec.classical(ClassicalCompositeAlgorithm.ED25519))
    .addComponent(ComponentSpec.pqc(PqcCompositeAlgorithm.ML_DSA_65))
    .setVerificationPolicy(GenerateCompositeKeySpec.VerificationPolicy.ALL);

KeyGenerationSummarySpec result = sdk.generateCompositeKey(spec);

Security: Requires BOTH Ed25519 and ML-DSA-65 signatures to verify (ALL policy).

9.3 Using Composite Keys (Transparent API)

Composite keys work with the same encryption/signing methods as simple keys:

// Encryption with composite key (transparent - same API)
EncryptResult encResult = sdk.encrypt("hybrid-encryption-key", data);

// Signing with composite key (transparent - same API)
SignResult signResult = sdk.sign("dual-signature-key", document);

The SDK automatically handles the hybrid encryption or dual signature based on the key type.

Supported combinations: 35 validated pairings - 24 HYBRID_KEM_COMBINE (encryption) - 11 DUALSIGN (signatures)

See the Composite Hybrid Keys documentation for complete details on supported combinations.


10. Error Handling & Logging

Exception Handling

AnkaSecureSdkException: Thrown for server-side errors (HTTP 401, 404, 500, etc.) or internal SDK errors (invalid responses, configuration issues).

Common error scenarios:

  • 401 Unauthorized: Invalid credentials or expired token
  • 404 Not Found: Key ID does not exist
  • 403 Forbidden: Insufficient permissions for operation
  • 500 Internal Server Error: Server-side error (check correlation ID)

Logging

The SDK uses two loggers:

  • devLogger: Debug-level information (stack traces, HTTP details)
  • userLogger: User-friendly messages (WARN/INFO level)

Configuration:

To enable debug logging, configure your logging framework (Logback, Log4J) to display DEBUG or TRACE messages for the co.ankatech.cli.dev logger.

Example logback.xml:

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

11. Interoperability Operations

These operations enable cross-platform cryptographic collaboration using caller-supplied public keys that are NOT stored in the keystore.

11.1 Verify Signature with External Public Key

Verify a digital signature using a public key provided by an external partner without importing it into your keystore.

import co.ankatech.ankasecure.sdk.model.VerifyExternalSpec;
import co.ankatech.ankasecure.sdk.model.VerifySignatureResponse;
import java.nio.file.Paths;

// Build verification specification
VerifyExternalSpec spec = VerifyExternalSpec.builder()
    .inputFile(Paths.get("contract.pdf"))
    .signatureFile(Paths.get("contract.jws"))
    .kty("ML-DSA")
    .alg("ML-DSA-87")
    .publicKeyFile(Paths.get("partner_pubkey.pem"))
    .build();

// Verify signature
VerifySignatureResponse response = sdk.verifySignatureExternal(spec);

if (response.isValid()) {
    System.out.println("✓ Signature verification: VALID");
    System.out.println("✓ Signer: " + response.getKeyRequested());
    System.out.println("✓ Algorithm: " + response.getAlgorithmUsed());
} else {
    System.out.println("✗ Signature verification: INVALID");
}

Use cases:

  • Verify documents from external organizations without importing their keys
  • Continuous interoperability with partner PKI systems
  • One-time signature verification for untrusted sources

11.2 Encrypt with External Public Key

Encrypt a file for an external recipient using their public key without storing it in your keystore.

import co.ankatech.ankasecure.sdk.model.EncryptExternalSpec;
import java.nio.file.Paths;

// Build encryption specification
EncryptExternalSpec spec = EncryptExternalSpec.builder()
    .inputFile(Paths.get("data.bin"))
    .outputFile(Paths.get("data.jwe"))
    .kty("ML-KEM")
    .alg("ML-KEM-1024")
    .publicKeyFile(Paths.get("recipient_pubkey.pem"))
    .build();

// Encrypt file
sdk.encryptFileExternal(spec);

System.out.println("✓ File encrypted for external recipient");
System.out.println("✓ Algorithm: ML-KEM-1024 + AES-256-GCM");

Use cases:

  • Encrypt files for external partners without storing their keys
  • Continuous secure data exchange with external systems
  • Temporary encryption for one-time recipients

Key distinction:

  • Interoperability operations: Keys NOT stored in keystore (temporary use)
  • Standard operations: Keys stored in keystore (persistent use)

12. Migration Operations

These operations support one-time migration of legacy cryptographic data from external systems. Keys ARE imported into the AnkaSecure keystore for permanent storage and reuse.

12.1 Import PKCS#12 Bundle

Import private keys and certificate chains from PKCS#12 (.p12/.pfx) bundles.

import co.ankatech.ankasecure.sdk.model.Pkcs12ImportSpec;
import co.ankatech.ankasecure.sdk.model.Pkcs12ImportResponse;
import co.ankatech.ankasecure.sdk.model.CertificateValidationMode;
import co.ankatech.ankasecure.sdk.model.KidStrategy;
import java.nio.file.Paths;

// Build import specification
Pkcs12ImportSpec spec = Pkcs12ImportSpec.builder()
    .kid("client-2024")
    .p12File(Paths.get("mycert.p12"))
    .p12Password("changeit")
    .validationMode(CertificateValidationMode.STRICT)
    .kidStrategy(KidStrategy.AUTO)
    .build();

// Import PKCS#12
Pkcs12ImportResponse response = sdk.importPkcs12(spec);

System.out.println("✓ Imported " + response.getImportedCount() + " keys:");
response.getImportedKeys().forEach(key -> {
    System.out.println("  - " + key.getKid() + " (" + key.getAlg() + ")");
});

if (response.getImportedTrustedCertCount() > 0) {
    System.out.println("✓ Imported " + response.getImportedTrustedCertCount() + " trusted certificates");
}

Certificate validation modes:

  • STRICT (production) - Reject expired/invalid certificates
  • IMPORT_ONLY (recovery) - Allow expired certs with restricted operations
  • SKIP (testing only) - No validation

Kid generation strategies:

  • AUTO (default) - Generates kids as {baseKid}-{sanitizedAlias}
  • MANUAL - Use explicit kid mappings via kidMappings parameter

12.2 Analyze PKCS#7 Structure

Introspect PKCS#7/CMS files to understand their structure before conversion.

import co.ankatech.ankasecure.sdk.model.Pkcs7AnalysisSpec;
import co.ankatech.ankasecure.sdk.model.Pkcs7AnalysisResponse;
import java.nio.file.Paths;

// Build analysis specification
Pkcs7AnalysisSpec spec = Pkcs7AnalysisSpec.builder()
    .pkcs7File(Paths.get("legacy_signed.p7s"))
    .validateCertificates(true)
    .build();

// Analyze PKCS#7
Pkcs7AnalysisResponse response = sdk.analyzePkcs7(spec);

System.out.println("Format: " + response.getDetectedFormat().getStructure());
System.out.println("Standard: " + response.getDetectedFormat().getStandard());

if (response.getSigners() != null && !response.getSigners().isEmpty()) {
    System.out.println("Signers:");
    response.getSigners().forEach(signer -> {
        System.out.println("  - " + signer.getIssuerDN());
        System.out.println("    Algorithm: " + signer.getSignatureAlgorithm());
    });
}

if (response.getConversionFeasibility().isCanConvert()) {
    System.out.println("✓ Conversion feasible to: " + response.getConversionFeasibility().getTargetFormat());
} else {
    System.out.println("✗ Conversion not supported");
}

Supported structures:

  • SignedData (signed documents)
  • EnvelopedData (encrypted data)
  • SignedAndEnvelopedData (combined signature + encryption)

12.3 Convert PKCS#7 to JOSE Format

Convert legacy PKCS#7/CMS files to modern JOSE format (JWS/JWE).

import co.ankatech.ankasecure.sdk.model.Pkcs7ConversionSpec;
import co.ankatech.ankasecure.sdk.model.Pkcs7ConversionResponse;
import co.ankatech.ankasecure.sdk.model.TargetFormat;
import co.ankatech.ankasecure.sdk.model.Serialization;
import java.nio.file.Paths;

// Build conversion specification
Pkcs7ConversionSpec spec = Pkcs7ConversionSpec.builder()
    .pkcs7File(Paths.get("legacy_encrypted.p7m"))
    .outputFile(Paths.get("modern_encrypted.jwe"))
    .decryptionKid("legacy-recipient-key")
    .targetFormat(TargetFormat.AUTO)
    .serialization(Serialization.AUTO)
    .upgradeEncryption(true)  // Upgrade CBC → GCM
    .build();

// Convert PKCS#7 to JOSE
Pkcs7ConversionResponse response = sdk.convertPkcs7ToJose(spec);

System.out.println("✓ Converted " + response.getStructure() + " → " + response.getTargetFormat());
System.out.println("✓ JOSE token saved to: modern_encrypted.jwe");

if (response.getSigners() != null && !response.getSigners().isEmpty()) {
    System.out.println("Signers:");
    response.getSigners().forEach(signer -> {
        System.out.println("  - " + signer.getKid() + " (" + signer.getAlgorithm() + ")");
    });
}

if (!response.getWarnings().isEmpty()) {
    System.out.println("Warnings:");
    response.getWarnings().forEach(System.out::println);
}

if (!response.getNextSteps().isEmpty()) {
    System.out.println("\nNext steps:");
    response.getNextSteps().forEach(System.out::println);
}

Conversion process:

  1. Parse PKCS#7 structure
  2. Verify signatures (if SignedData)
  3. Decrypt content (if EnvelopedData, requires private key)
  4. Convert to JOSE format (preserves classical algorithms)
  5. Optionally upgrade algorithms (CBC→GCM)

Prerequisites:

  • Private keys must be imported via importPkcs12() before conversion
  • Signer/recipient matching uses issuerDN + serialNumber

Next steps for PQC:

After conversion to JOSE, use standard SDK methods to upgrade to post-quantum:

// Generate PQ signing key
sdk.generateKey(new GenerateKeySpec()
    .setKid("pq-signing-key")
    .setKty("ML-DSA")
    .setAlg("ML-DSA-87"));

// Re-sign with PQ algorithm (compact JWS)
ResignResult resignResult = sdk.resign("pq-signing-key", oldJws);

// Generate PQ encryption key
sdk.generateKey(new GenerateKeySpec()
    .setKid("pq-encryption-key")
    .setKty("ML-KEM")
    .setAlg("ML-KEM-1024"));

// Re-encrypt with PQ algorithm (compact JWE)
ReencryptResult reencResult = sdk.reencrypt("pq-encryption-key", oldJwe);

Key distinction:

  • Migration operations: Keys ARE imported into keystore (one-time setup)
  • Interoperability operations: Keys NOT stored in keystore (continuous use)

13. Next Steps

Integration Examples

Explore real-world integration scenarios:

  • Integration Examples: Common integration patterns
  • Integration Flows: 34 detailed flows covering all SDK operations
  • Flow 1-4: Basic operations (encrypt/decrypt, sign/verify)
  • Flow 5-9: Post-quantum operations (ML-KEM, ML-DSA)
  • Flow 10-28: Advanced scenarios (rotation, migration, interoperability)
  • Flow 29-34: Composite hybrid keys (quantum-resistant defense-in-depth)

Additional Resources


© 2025 ANKATech Solutions INC. All rights reserved.