Example Utility Class
All integration flows import co.ankatech.ankasecure.sdk.examples.ExampleUtil
for:
- encrypted-credential authentication
- property loading (
cli.properties
,-Dcli.config
) - JSON pretty-printing with Java Time
- AES-GCM helper functions
- fatal-exit convenience
// FILE: ExampleUtil.java
package co.ankatech.ankasecure.sdk.examples;
import co.ankatech.ankasecure.sdk.AnkaSecureSdk;
import co.ankatech.ankasecure.sdk.exception.AnkaSecureSdkException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.text.MessageFormat;
import java.util.*;
public final class ExampleUtil {
private ExampleUtil() {/* utility */}
/* ---------- JSON pretty printer ---------- */
private static final ObjectMapper JSON = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.registerModule(new JavaTimeModule());
/* ---------- CLI config loader ------------ */
public static Properties loadProperties() {
Properties p = new Properties();
File local = new File("cli.properties");
try {
if (local.isFile()) {
try (InputStream in = new FileInputStream(local)) {
p.load(in);
System.out.println("Loaded config from " + local.getAbsolutePath());
return p;
}
}
String sys = System.getProperty("cli.config");
if (sys != null && !sys.isBlank()) {
File f = new File(sys);
if (f.isFile()) {
try (InputStream in = new FileInputStream(f)) {
p.load(in);
System.out.println("Loaded config from " + f.getAbsolutePath());
return p;
}
}
}
try (InputStream in = ExampleUtil.class.getResourceAsStream("/cli.properties")) {
if (in == null) fatal("cli.properties not found; run `init` first.", null);
p.load(in);
System.out.println("Loaded config from classpath /cli.properties");
return p;
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to load configuration", e);
}
}
/* ---------- Auth helpers ----------------- */
public static AnkaSecureSdk authenticate(Properties props) {
String uuid = props.getProperty("client.uuid");
String salt = props.getProperty("client.salt");
String idEnc = props.getProperty("clientIdEnc");
String scEnc = props.getProperty("clientSecretEnc");
if (uuid == null || salt == null || idEnc == null || scEnc == null) {
fatal("CLI not initialised; run `init` first.", null);
}
try {
byte[] key = deriveKey(uuid, salt);
String id = decryptValue(idEnc, key);
String sec = decryptValue(scEnc, key);
AnkaSecureSdk sdk = new AnkaSecureSdk(props);
sdk.authenticateApplication(id, sec);
System.out.println("Authenticated clientId=" + id);
return sdk;
} catch (AnkaSecureSdkException e) {
fatal(MessageFormat.format("Auth failed HTTP={0}", e.getStatusCode()), e);
return null; // never reached
} catch (Exception e) {
fatal("Credential decryption error", e);
return null;
}
}
/* ---------- JSON helper ------------------ */
public static String toJson(Object o) {
try { return JSON.writeValueAsString(o); }
catch (IOException e) { throw new UncheckedIOException(e); }
}
/* ---------- AES-GCM derivation ------------ */
private static byte[] deriveKey(String uuid, String saltHex) throws Exception {
byte[] salt = hexToBytes(saltHex);
PBEKeySpec spec = new PBEKeySpec(uuid.toCharArray(), salt, 150_000, 256);
return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
.generateSecret(spec).getEncoded();
}
private static String decryptValue(String b64, byte[] key) throws Exception {
byte[] blob = Base64.getDecoder().decode(b64);
byte[] iv = Arrays.copyOfRange(blob, 0, 12);
byte[] ct = Arrays.copyOfRange(blob, 12, blob.length);
Cipher c = Cipher.getInstance("AES/GCM/NoPadding");
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"),
new GCMParameterSpec(128, iv));
return new String(c.doFinal(ct), StandardCharsets.UTF_8);
}
private static byte[] hexToBytes(String h) {
byte[] out = new byte[h.length() / 2];
for (int i = 0; i < h.length(); i += 2) {
out[i / 2] = (byte) ((Character.digit(h.charAt(i),16) << 4)
+ Character.digit(h.charAt(i+1),16));
}
return out;
}
/* ---------- misc ------------------------- */
public static void ensureDir(Path p) {
try { Files.createDirectories(p); }
catch (IOException e) { fatal("Cannot create dir " + p, e); }
}
public static void fatal(String msg, Throwable t) {
System.err.println(msg + (t!=null?": "+t.getMessage():""));
if (t!=null) t.printStackTrace(System.err);
System.exit(1);
}
}
> **Dependency** — this class imports `ExampleUtil`.
> If you have not copied `ExampleUtil.java` yet, see [example_util.md](example_util.md).
3 · Compile everything together
All other flows run the same way—no code duplication, one shared utility.