import datetime as dt import string allowed_chars = string.ascii_uppercase + string.digits cords = "ADFGVX" def clean(s: str, *, numbers: bool = False) -> str: allowed = string.ascii_uppercase + (string.digits if numbers else "") return "".join(c for c in s.upper() if c in allowed) def square(key: str) -> str: key = clean(key) if len(key) < 3: raise ValueError("square key must have at least 3 letters") letters = "".join(dict.fromkeys(key + string.ascii_uppercase)) if len(letters) != 26: raise ValueError("invalid square key") return "".join(c + (str(ord(c) - 64) if c < "J" else "0" if c == "J" else "") for c in letters) def column_order(key: str) -> list[int]: key = clean(key) if len(key) < 5: raise ValueError("column key must have at least 5 letters") # InitColumnar picks the leftmost smallest remaining character each time. return sorted(range(len(key)), key=lambda idx: (key[idx], idx)) def transpose_encode(text: str, key: str) -> str: width = len(clean(key)) return "".join(text[i::width] for i in column_order(key)) def transpose_decode(text: str, key: str) -> str: width = len(clean(key)) out = [""] * len(text) pos = 0 for col in column_order(key): for idx in range(col, len(text), width): out[idx] = text[pos] pos += 1 return "".join(out) def encode_adfgvx(plain: str, square_key: str, column_key: str) -> str: plain = clean(plain, numbers=True) sq = square(square_key) converted = "".join(cords[sq.index(c) // 6] + cords[sq.index(c) % 6] for c in plain) return transpose_encode(converted, column_key) def decode_adfgvx(cipher: str, square_key: str, column_key: str) -> str: cipher = clean(cipher) if not cipher or len(cipher) % 2 or any(c not in cords for c in cipher): raise ValueError("cipher must have an even, nonzero number of ADFGVX letters") sq = square(square_key) unshuffled = transpose_decode(cipher, column_key) return "".join(sq[cords.index(unshuffled[i]) * 6 + cords.index(unshuffled[i + 1])] for i in range(0, len(unshuffled), 2)) def check_site(site: str) -> str: site = site.upper() if len(site) != 3 or any(c not in allowed_chars for c in site): raise ValueError("site must be exactly three A-Z/0-9 characters") return site def make_license(site: str, year: int, month: int) -> str: site = check_site(site) if not 1 <= month <= 12: raise ValueError("month must be 1..12") if not 1000 <= year <= 9999: raise ValueError("year must be four digits") yd = [chr(65 + int(d)) for d in f"{year:04d}"] mc = chr(65 + month) plain = site[0] + yd[0] + site[1] + mc + site[2] + "".join(yd[1:]) return encode_adfgvx(plain, site + "AMOS", "HAGGAI") def validate_license(site: str, key: str, today: dt.date | None = None) -> dict: site = check_site(site) today = today or dt.date.today() try: plain = decode_adfgvx(key, site + "AMOS", "HAGGAI").strip() if len(plain) < 5 or (plain[0] + plain[2] + plain[4]).upper() != site: return {"valid": False, "reason": "invalid format/site mismatch"} if len(key) == 10: year = 2000 + ord(plain[1]) - 65 else: if len(plain) < 8: raise ValueError("decoded license too short") year = sum((ord(plain[idx]) - 65) * multiple for idx, multiple in ((1, 1000), (5, 100), (6, 10), (7, 1))) month = ord(plain[3]) - 65 if not (today.year <= year <= today.year + 2 and 1 <= month <= 12): return {"valid": False, "reason": "year/month outside accepted range"} expiration = dt.date(year, month, 1) remaining = (expiration - today).days if remaining <= 0: return {"valid": False, "reason": "expired; new GUID?", "expires": str(expiration), "days_remaining": 0} return {"valid": True, "expires": str(expiration), "days_remaining": remaining} except (ValueError, IndexError, TypeError): return {"valid": False, "reason": "malformed license"} def decode_site_request(request_code: str) -> str: """Recover the three-character site ID from an app-generated request code.""" site = decode_adfgvx(request_code.strip(), "JOSHUA", "NAHUM") return check_site(site) def main() -> None: blue = "\033[96m" green = "\033[92m" dim = "\033[90m" reset = "\033[0m" print(f"\n{blue} libLockdown license key generator{reset}") print('--------------------------------------------------') try: request = input(f"{blue}?{reset} Site request code: ").strip().upper() exp_month = int(input(f"{blue}?{reset} Expiration month (1-12): ").strip()) exp_year = int(input(f"{blue}?{reset} Expiration year (e.g., 2027): ").strip()) # if it is more then two years in the future, warn the user if exp_year > dt.date.today().year + 2: print(f"{dim} Warning: Expiration year is more than two years in the future. This will not be accepted by the application.{reset}") site = decode_site_request(request) if encode_adfgvx(site, "JOSHUA", "NAHUM") != request: print(f"\n {dim}Original request code: {request}{reset}") print(f" {dim}Re-encoded request code: {encode_adfgvx(site, 'JOSHUA', 'NAHUM')}{reset}") print(f" {dim}Site GUID: {site}{reset}") print() raise ValueError("[!] Request code failed round-trip verification... This should never happen.") print(f"{green}[+]{reset} Generating license key...") key = make_license(site, exp_year, exp_month) print(f"\n {dim}Site GUID{reset} {site}") print(f" {dim}Expires{reset} {exp_year}-{exp_month:02d}-01") print(f" {green}License key{reset} {key}\n") result = validate_license(site, key) if not result["valid"]: print(f" {dim}Validation: invalid ({result['reason']}){reset}\n") else: print(f" {green}Validation: valid ({result['days_remaining']} days remaining){reset}\n") except (ValueError, IndexError, TypeError) as exc: print(f"\n Error: {exc}\n") raise SystemExit(1) except (EOFError, KeyboardInterrupt): print("\n") raise SystemExit(130) if __name__ == "__main__": main()