Skip to content

Instantly share code, notes, and snippets.

@fox3000foxy
Last active June 5, 2025 20:08
Show Gist options
  • Save fox3000foxy/a47adb4bfa347d89751897ab27cae1ec to your computer and use it in GitHub Desktop.
Save fox3000foxy/a47adb4bfa347d89751897ab27cae1ec to your computer and use it in GitHub Desktop.
import crypto from "crypto";
import fs from "fs";
import path from "path";
// ===== Configuration =====
const AES_SECRET_PATH = path.join(process.cwd(), "aes-secret.key");
// ===== AES Secret Initialization =====
let AES_SECRET: Buffer;
try {
if (fs.existsSync(AES_SECRET_PATH)) {
// Load existing secret
AES_SECRET = fs.readFileSync(AES_SECRET_PATH);
} else {
// Generate new 256-bit key and save it
AES_SECRET = crypto.randomBytes(32);
fs.writeFileSync(AES_SECRET_PATH, AES_SECRET, { flag: "wx" });
}
} catch (error) {
console.error("Error initializing AES secret:", error);
throw error;
}
// ===== Encryption / Decryption Utilities =====
/**
* Encrypts a JSON-serializable object using AES-256-GCM.
* @param data Object to encrypt.
* @param secret 256-bit encryption key.
* @returns Base64-encoded encrypted string (IV + Tag + Ciphertext).
*/
export function encryptWithAES(data: object, secret: Buffer): string {
const iv = crypto.randomBytes(12); // 96-bit IV recommended for GCM
const cipher = crypto.createCipheriv("aes-256-gcm", secret, iv);
const json = JSON.stringify(data);
const encrypted = Buffer.concat([
cipher.update(json, "utf8"),
cipher.final(),
]);
const authTag = cipher.getAuthTag();
// Final payload: IV (12) + Tag (16) + Encrypted
return Buffer.concat([iv, authTag, encrypted]).toString("base64");
}
/**
* Decrypts a base64 AES-GCM-encrypted string back into an object.
* @param encrypted Encrypted base64 string.
* @param secret 256-bit encryption key.
* @returns Decrypted object or null on failure.
*/
export function decryptWithAES(encrypted: string, secret: Buffer): object | null {
try {
const buf = Buffer.from(encrypted, "base64");
if (buf.length < 28) return null; // IV (12) + Tag (16) minimum
const iv = buf.subarray(0, 12);
const tag = buf.subarray(12, 28);
const ciphertext = buf.subarray(28);
const decipher = crypto.createDecipheriv("aes-256-gcm", secret, iv);
decipher.setAuthTag(tag);
const decrypted =
decipher.update(ciphertext, undefined, "utf8") +
decipher.final("utf8");
return JSON.parse(decrypted);
} catch (error) {
console.error("Error decrypting with AES:", error);
return null;
}
}
// ===== License Key Management =====
/**
* Generates a single encrypted license key with optional credit.
* @param credits Number of credits or null for unlimited.
* @returns Encrypted license key (base64 without padding).
*/
export function generateKey(credits: number | null): string {
const payload = {
id: crypto.randomBytes(8).toString("base64"), // Unique ID
credit: credits,
};
return encryptWithAES(payload, AES_SECRET).replace(/=+$/, ""); // Remove padding
}
/**
* Decrypts and parses an encrypted license key.
* @param encryptedKey Base64 license key string.
* @returns Parsed payload or null.
*/
export function decryptKey(encryptedKey: string): Record<string, unknown> | null {
return decryptWithAES(encryptedKey, AES_SECRET) as Record<string, unknown> | null;
}
/**
* Generates and saves multiple license keys with different credit amounts.
* Each credit value gets its own output `.txt` file.
* @param credits Array of credit amounts (or null for unlimited).
* @param filePathPrefix Prefix for the output file names.
* @param numberOfKeys Number of keys per credit group.
*/
export function generateKeysAndSave(
credits: (number | null)[],
filePathPrefix: string,
numberOfKeys = 100,
): void {
credits.forEach((credit) => {
const keys = Array.from({ length: numberOfKeys }, () =>
generateKey(credit)
);
const fileName = `${filePathPrefix}${credit ?? "lifetime"}.txt`;
const filePath = path.join(process.cwd(), fileName);
fs.writeFileSync(filePath, keys.join("\n"));
});
}
// ===== Example (commented out) =====
// const credits = [10, 20, 50, 100, null];
// generateKeysAndSave(credits, "keys_credits_", 100);
@fox3000foxy
Copy link
Author

🔐 AES Key Generator for Encrypted Credit-Based Licenses

This Node.js module provides a simple utility to generate, encrypt, and decrypt license keys tied to a credit system using AES-256-GCM encryption. Each key encodes a small JSON payload, including an ID and credit value, and is secured using a secret key stored on disk.


📦 Features

  • AES-256-GCM encryption with authentication tag.
  • Secret key stored in a local aes-secret.key file.
  • Generate individual license keys or batch save to files.
  • Credit value (number | null) embedded in each key.
  • Secure and tamper-resistant key format.

📁 File Structure

  • aes-secret.key: Generated once and reused, holds your AES encryption key.
  • keys_credits_<credit>.txt: Output files generated with multiple license keys.

🔧 Usage

Generate a Single Key

import { generateKey } from "./aes-keygen";

const key = generateKey(50); // 50 credits
console.log("Encrypted Key:", key);

Decrypt a Key

import { decryptKey } from "./aes-keygen";

const data = decryptKey("your_encrypted_key_here");
console.log("Decrypted Data:", data);
// Output: { id: "base64id", credit: 50 }

Batch Generate Keys and Save to File

import { generateKeysAndSave } from "./aes-keygen";

const credits = [10, 20, 50, 100, null]; // null = lifetime/unlimited
generateKeysAndSave(credits, "keys_credits_", 100);

This will create:

  • keys_credits_10.txt
  • keys_credits_20.txt
  • ...
  • keys_credits_lifetime.txt

Each file contains 100 unique, encrypted license keys.


🔐 How Encryption Works

  • Algorithm: AES-256-GCM
  • IV: 12 random bytes
  • Auth Tag: 16 bytes
  • Payload: JSON { id, credit }
  • Final encrypted buffer = IV + AuthTag + Ciphertext, base64-encoded (padding = removed)

Example key:

k93NwEBpxtcYAnA1x12tUMLSuDqGfWJwX02IsEoNRD6cpXN3GOpht6GNpJE

❗ Important Notes

  • The AES secret key is generated once and saved as aes-secret.key in the project root.
  • Keep aes-secret.key secure! Anyone with access can decrypt license keys.
  • Removing padding (=) from base64 is safe but makes the keys slightly shorter.

✅ Requirements

  • Node.js v14+
  • No external dependencies—uses built-in crypto, fs, and path modules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment