Skip to content

Instantly share code, notes, and snippets.

@cw2k
Last active February 14, 2026 18:22
Show Gist options
  • Select an option

  • Save cw2k/f6d35659845ae049f44b8d5ed080acc9 to your computer and use it in GitHub Desktop.

Select an option

Save cw2k/f6d35659845ae049f44b8d5ed080acc9 to your computer and use it in GitHub Desktop.
Android: Developer Assistant - LicenseTool
/*
* LicenseTool for Developer Assistant
* -----------------------------------
* Author: Jarosław Wiśniewski
* App: https://play.google.com/store/apps/details?id=com.appsisle.developerassistant
*
* Description:
* This standalone Java console tool can decrypt and re-encrypt license data
* used by the Developer Assistant application. It performs the following steps:
*
* 1. Reads license.xml and crypto_preferences.xml from the current directory.
* 2. Decrypts all license fields using the AES/CBC/PKCS5Padding scheme.
* 3. Writes the decrypted values into license_decoded.txt.
* 4. Opens the file in the system's default text editor.
* 5. Waits for a key press.
* 6. Reads the modified values from license_decoded.txt.
* 7. Validates the status field (Permanent, Subscription, Evaluation, Expired, NotKnownYet, Invalid).
* 8. Re-encrypts the values and writes licenseNew.xml.
*
* The tool is error-tolerant: malformed values, invalid status,
* or formatting issues will not stop execution. Warnings are printed instead.
*
* Compilation:
* javac LicenseTool.java
*
* Execution:
* java LicenseTool
*
* Requirements:
* - Java 11 or newer
* - license.xml and crypto_preferences.xml must exist in the same directory
*/
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;
import java.io.File;
import java.nio.file.*;
import java.security.MessageDigest;
import java.time.*;
import java.util.*;
import java.util.Base64;
public class LicenseTool {
private static final String FIXED_PREFIX = "@TeL8362N%F3976";
private static final String FALLBACK_SEED =
"FrdLFGcCo0bwn88FnLG+4PVccMVwUlqQoz5Ij2FBVKbM2XN8v59Tyw7k2X9sCavdbQi8RyCsXt8yrTwOUY9CJiIc86l16JFtpto6Uzc8xmu2iY+2XkrEftNQfJG0jnybKM3//Mls2cbslqxL7uPaqyvAQ0ROTzSWUyMIH0sSRifRP6WZ5l6+LUxosx9uuJNFNiLG4CJtW9LGFKnAKENd6tTJT7ALXEwZgCHY7acXAz3Thi50J9CCcSlBaRHYE4up4OyD6LE1xwbUsqeR+kr2ePrY+hF5NfS2dviZyVDYlog5OAgIUSIIV60VKLDx2E0ypveLzmjteAeHdRoKZHApGA";
private static final String[] VALID_STATUS = {
"Permanent",
"Subscription",
"Evaluation",
"Expired",
"NotKnownYet",
"Invalid"
};
public static void main(String[] args) {
System.out.println("=== LicenseTool for Developer Assistant ===");
try {
decryptWorkflow();
System.out.println("\nEdit license_decoded.txt, then press ENTER in this window...");
System.in.read();
encryptWorkflow();
System.out.println("\nDone. New file: licenseNew.xml");
} catch (Exception e) {
System.out.println("Unexpected error: " + e.getMessage());
}
}
// ---------------------------------------------------------
// 1) DECRYPT WORKFLOW
// ---------------------------------------------------------
private static void decryptWorkflow() {
try {
Map<String, String> values = loadLicenseValues();
if (values.isEmpty()) {
System.out.println("ERROR: license.xml could not be parsed or is missing.");
return;
}
String seed = loadSeed();
String remain = safeDecrypt(values.get("remain"), seed);
String expiryRaw = safeDecrypt(values.get("expiry"), seed);
String renews = safeDecrypt(values.get("renews"), seed);
String status = safeDecrypt(values.get("status"), seed);
String expiryFormatted = "UNKNOWN";
try {
long ms = Long.parseLong(expiryRaw);
expiryFormatted = formatExpiry(ms);
} catch (Exception e) {
System.out.println("WARNING: expiry could not be formatted: " + e.getMessage());
}
String out =
"remain=" + remain + "\n" +
"expiry=" + expiryRaw + "\n" +
"expiry_formatted=" + expiryFormatted + "\n" +
"renews=" + renews + "\n" +
"status=" + status + "\n";
Files.writeString(Paths.get("license_decoded.txt"), out);
openEditor("license_decoded.txt");
} catch (Exception e) {
System.out.println("Error during decrypt workflow: " + e.getMessage());
}
}
// ---------------------------------------------------------
// 2) ENCRYPT WORKFLOW
// ---------------------------------------------------------
private static void encryptWorkflow() {
try {
List<String> lines = Files.readAllLines(Paths.get("license_decoded.txt"));
Map<String, String> map = new HashMap<>();
for (String line : lines) {
if (!line.contains("=")) continue;
String[] p = line.split("=", 2);
map.put(p[0].trim(), p[1].trim());
}
String seed = loadSeed();
String status = map.get("status");
if (!isValidStatus(status)) {
System.out.println("WARNING: Invalid status \"" + status + "\"");
System.out.println("Allowed: " + String.join(", ", VALID_STATUS));
}
String remainEnc = safeEncrypt(map.get("remain"), seed);
String expiryEnc = safeEncrypt(map.get("expiry"), seed);
String renewsEnc = safeEncrypt(map.get("renews"), seed);
String statusEnc = safeEncrypt(status, seed);
String xml =
"<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
"<map>\n" +
" <string name=\"remain\">" + remainEnc + "</string>\n" +
" <string name=\"expiry\">" + expiryEnc + "</string>\n" +
" <string name=\"renews\">" + renewsEnc + "</string>\n" +
" <string name=\"status\">" + statusEnc + "</string>\n" +
"</map>\n";
Files.writeString(Paths.get("licenseNew.xml"), xml);
} catch (Exception e) {
System.out.println("Error during encrypt workflow: " + e.getMessage());
}
}
// ---------------------------------------------------------
// XML PARSING
// ---------------------------------------------------------
private static Map<String, String> loadLicenseValues() {
Map<String, String> map = new HashMap<>();
try {
File file = new File("license.xml");
if (!file.exists()) {
System.out.println("WARNING: license.xml not found.");
return map;
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(file);
NodeList list = doc.getElementsByTagName("string");
for (int i = 0; i < list.getLength(); i++) {
Element el = (Element) list.item(i);
String name = el.getAttribute("name");
String value = el.getTextContent().trim();
if ("remain".equals(name) ||
"expiry".equals(name) ||
"renews".equals(name) ||
"status".equals(name)) {
map.put(name, value);
}
}
} catch (Exception e) {
System.out.println("WARNING: Failed to parse license.xml: " + e.getMessage());
}
return map;
}
private static String loadSeed() {
try {
File file = new File("crypto_preferences.xml");
if (!file.exists()) {
System.out.println("WARNING: crypto_preferences.xml not found — using fallback seed");
return FALLBACK_SEED;
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(file);
NodeList list = doc.getElementsByTagName("string");
for (int i = 0; i < list.getLength(); i++) {
Element el = (Element) list.item(i);
if ("crypto_seed".equals(el.getAttribute("name"))) {
String seed = el.getTextContent().trim();
if (seed.isEmpty()) {
System.out.println("WARNING: crypto_seed is empty — using fallback seed");
return FALLBACK_SEED;
}
return seed;
}
}
System.out.println("WARNING: crypto_seed not found in crypto_preferences.xml — using fallback seed");
return FALLBACK_SEED;
} catch (Exception e) {
System.out.println("WARNING: Failed to parse crypto_preferences.xml — using fallback seed");
return FALLBACK_SEED;
}
}
// ---------------------------------------------------------
// CRYPTO HELPERS
// ---------------------------------------------------------
private static String safeDecrypt(String base64, String seed) {
if (base64 == null || base64.isEmpty()) return "";
try {
return decrypt(base64, seed);
} catch (Exception e) {
System.out.println("WARNING: Could not decrypt value: " + e.getMessage());
return "";
}
}
private static String safeEncrypt(String plaintext, String seed) {
if (plaintext == null) plaintext = "";
try {
return encrypt(plaintext, seed);
} catch (Exception e) {
System.out.println("WARNING: Could not encrypt value: " + e.getMessage());
return "";
}
}
private static String decrypt(String base64, String seed) throws Exception {
byte[] full = Base64.getDecoder().decode(base64);
if (full.length < 17) throw new IllegalArgumentException("ciphertext too short");
byte[] iv = Arrays.copyOfRange(full, 0, 16);
byte[] ciphertext = Arrays.copyOfRange(full, 16, full.length);
SecretKeySpec key = deriveKey(seed);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return new String(cipher.doFinal(ciphertext), "UTF-8");
}
private static String encrypt(String plaintext, String seed) throws Exception {
SecretKeySpec key = deriveKey(seed);
byte[] iv = new byte[16];
new java.security.SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(plaintext.getBytes("UTF-8"));
byte[] full = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, full, 0, iv.length);
System.arraycopy(encrypted, 0, full, iv.length, encrypted.length);
return Base64.getEncoder().encodeToString(full);
}
private static SecretKeySpec deriveKey(String seed) throws Exception {
String keyString = FIXED_PREFIX + seed;
MessageDigest sha = MessageDigest.getInstance("SHA-256");
byte[] keyBytes = sha.digest(keyString.getBytes("UTF-8"));
return new SecretKeySpec(Arrays.copyOf(keyBytes, 32), "AES");
}
// ---------------------------------------------------------
// HELPERS
// ---------------------------------------------------------
private static boolean isValidStatus(String s) {
if (s == null) return false;
for (String v : VALID_STATUS) {
if (v.equalsIgnoreCase(s)) return true;
}
return false;
}
private static String formatExpiry(long ms) {
Instant instant = Instant.ofEpochMilli(ms);
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
return zdt.toLocalDate() + " " + zdt.toLocalTime().withNano(0);
}
private static void openEditor(String file) {
try {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
Runtime.getRuntime().exec(new String[]{"cmd", "/c", "start", file});
} else if (os.contains("mac")) {
Runtime.getRuntime().exec(new String[]{"open", file});
} else {
Runtime.getRuntime().exec(new String[]{"xdg-open", file});
}
} catch (Exception e) {
System.out.println("WARNING: Could not open editor: " + e.getMessage());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment