diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index c652ed480d..352fa12a78 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -163,6 +163,7 @@
"Derive PBKDF2 key",
"Derive EVP key",
"Derive HKDF key",
+ "Generate Kerberos Keytab",
"Bcrypt",
"Scrypt",
"JWT Sign",
diff --git a/src/core/operations/GenerateKerberosKeytab.mjs b/src/core/operations/GenerateKerberosKeytab.mjs
new file mode 100644
index 0000000000..c7af6fc803
--- /dev/null
+++ b/src/core/operations/GenerateKerberosKeytab.mjs
@@ -0,0 +1,381 @@
+/**
+ * @author mansiverma897993
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import Utils from "../Utils.mjs";
+
+/**
+ * Serialize a single keytab entry into a binary Uint8Array.
+ *
+ * @param {string} principal
+ * @param {string} realm
+ * @param {number} kvno
+ * @param {number} etype
+ * @param {Uint8Array} keyBytes
+ * @returns {Uint8Array}
+ */
+function serializeEntry(principal, realm, kvno, etype, keyBytes) {
+ const components = principal.split("/").map(c => c.trim()).filter(Boolean);
+ const realmBytes = new TextEncoder().encode(realm.trim().toUpperCase());
+
+ let componentsSize = 0;
+ const componentsBytes = components.map(c => {
+ const bytes = new TextEncoder().encode(c);
+ componentsSize += 2 + bytes.length;
+ return bytes;
+ });
+
+ const nameType = 1; // KRB5_NT_PRINCIPAL
+ const timestamp = 0; // Fixed to 0 for deterministic testing
+ const vno8 = kvno & 0xff;
+
+ // Entry content size:
+ // num_components (2)
+ // realm len (2) + realmBytes.length
+ // componentsSize (each comp: 2 len + data)
+ // name_type (4)
+ // timestamp (4)
+ // vno8 (1)
+ // keyblock: keytype (2) + keylen (2) + keyBytes.length
+ // vno (4)
+ const entrySize = 2 + 2 + realmBytes.length + componentsSize + 4 + 4 + 1 + 2 + 2 + keyBytes.length + 4;
+
+ const buffer = new Uint8Array(4 + entrySize);
+ const view = new DataView(buffer.buffer);
+
+ let offset = 0;
+
+ // 1. size (4 bytes, signed 32-bit big-endian)
+ view.setInt32(offset, entrySize, false);
+ offset += 4;
+
+ // 2. num_components (2 bytes, big-endian)
+ view.setInt16(offset, components.length, false);
+ offset += 2;
+
+ // 3. realm length (2 bytes, big-endian)
+ view.setUint16(offset, realmBytes.length, false);
+ offset += 2;
+
+ // 4. realm data
+ buffer.set(realmBytes, offset);
+ offset += realmBytes.length;
+
+ // 5. components
+ for (const compBytes of componentsBytes) {
+ view.setUint16(offset, compBytes.length, false);
+ offset += 2;
+ buffer.set(compBytes, offset);
+ offset += compBytes.length;
+ }
+
+ // 6. name_type (4 bytes, big-endian)
+ view.setUint32(offset, nameType, false);
+ offset += 4;
+
+ // 7. timestamp (4 bytes, big-endian)
+ view.setUint32(offset, timestamp, false);
+ offset += 4;
+
+ // 8. vno8 (1 byte)
+ view.setUint8(offset, vno8);
+ offset += 1;
+
+ // 9. keytype (2 bytes, big-endian)
+ view.setUint16(offset, etype, false);
+ offset += 2;
+
+ // 10. keylen (2 bytes, big-endian)
+ view.setUint16(offset, keyBytes.length, false);
+ offset += 2;
+
+ // 11. key data
+ buffer.set(keyBytes, offset);
+ offset += keyBytes.length;
+
+ // 12. vno (4 bytes, big-endian)
+ view.setUint32(offset, kvno, false);
+ offset += 4;
+
+ return buffer;
+}
+
+/**
+ * Generate Kerberos Keytab operation
+ */
+class GenerateKerberosKeytab extends Operation {
+
+ /**
+ * GenerateKerberosKeytab constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Generate Kerberos Keytab";
+ this.module = "Crypto";
+ this.description = "Generates a Wireshark-compatible Kerberos (KRB5) keytab file from user-provided principal keys or NT hashes. The input can be a single key in the arguments or a list of keys in the main input (in CSV/lines or JSON format).
Input formats:
- CSV/lines: principal,realm,kvno,etype,key (one per line). Supports shorter forms like principal,key or principal,realm,key.
- JSON: an array of keytab entry objects, e.g. [{\"principal\": \"eshellstrop\", \"realm\": \"WORKGROUP\", \"kvno\": 1, \"etype\": 23, \"key\": \"hex_key\"}].
Supported encryption types:
- RC4-HMAC / 23 (16-byte key / 32 hex characters)
- AES128-CTS-HMAC-SHA1-96 / 17 (16-byte key / 32 hex characters)
- AES256-CTS-HMAC-SHA1-96 / 18 (32-byte key / 64 hex characters)";
+ this.infoURL = "https://web.mit.edu/kerberos/krb5-latest/doc/basic/keytab_def.html";
+ this.inputType = "string";
+ this.outputType = "byteArray";
+ this.args = [
+ {
+ "name": "Principal",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "Realm",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "KVNO",
+ "type": "number",
+ "value": 1
+ },
+ {
+ "name": "Encryption Type",
+ "type": "option",
+ "value": [
+ "RC4-HMAC (23)",
+ "AES128-CTS-HMAC-SHA1-96 (17)",
+ "AES256-CTS-HMAC-SHA1-96 (18)"
+ ]
+ },
+ {
+ "name": "Key / NT Hash (Hex)",
+ "type": "string",
+ "value": ""
+ },
+ {
+ "name": "Output Format",
+ "type": "option",
+ "value": [
+ "Raw Keytab File",
+ "Hex",
+ "Base64",
+ "Detailed Summary Text"
+ ]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {byteArray}
+ */
+ run(input, args) {
+ const entries = [];
+
+ // 1. Try parsing main input first
+ if (input && input.trim()) {
+ let parsedJson = null;
+ try {
+ const trimmed = input.trim();
+ if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
+ parsedJson = JSON.parse(trimmed);
+ }
+ } catch (e) {
+ // Not valid JSON, fallback to CSV/lines
+ }
+
+ if (parsedJson) {
+ const list = Array.isArray(parsedJson) ? parsedJson : [parsedJson];
+ for (const item of list) {
+ if (typeof item === "object" && item !== null) {
+ const principal = item.principal || item.username || "";
+ const realm = item.realm || "";
+ const kvno = item.kvno !== undefined ? item.kvno : 1;
+ const etypeInput = item.etype || item.encryption_type || 23;
+ const key = item.key || item.nt_hash || item.value || "";
+
+ if (principal && key) {
+ entries.push({ principal, realm, kvno, etypeInput, key });
+ }
+ }
+ }
+ } else {
+ const rows = Utils.parseCSV(input.trim());
+ for (const row of rows) {
+ if (row.length === 0) continue;
+
+ const firstCol = row[0].trim();
+ if (firstCol.startsWith("#") || firstCol === "") continue;
+
+ // Ignore header line
+ const col1Lower = firstCol.toLowerCase();
+ if (col1Lower === "principal" || col1Lower === "username") continue;
+
+ if (row.length < 2) continue;
+
+ let principal = "";
+ let realm = "";
+ let kvno = 1;
+ let etypeInput = 23;
+ let key = "";
+
+ if (row.length === 2) {
+ principal = row[0];
+ key = row[1];
+ } else if (row.length === 3) {
+ principal = row[0];
+ realm = row[1];
+ key = row[2];
+ } else if (row.length === 4) {
+ principal = row[0];
+ realm = row[1];
+ const val = parseInt(row[2].trim(), 10);
+ if (isNaN(val)) {
+ etypeInput = row[2];
+ } else if (val === 23 || val === 17 || val === 18) {
+ etypeInput = val;
+ } else {
+ kvno = val;
+ }
+ key = row[3];
+ } else {
+ principal = row[0];
+ realm = row[1];
+ kvno = parseInt(row[2].trim(), 10) || 1;
+ etypeInput = row[3];
+ key = row[4];
+ }
+
+ if (principal && key) {
+ entries.push({ principal, realm, kvno, etypeInput, key });
+ }
+ }
+ }
+ }
+
+ // 2. Fall back to UI args if no entries found in main input
+ if (entries.length === 0) {
+ const principal = args[0] || "";
+ const realm = args[1] || "";
+ const kvno = args[2] !== undefined ? args[2] : 1;
+ const etypeInput = args[3];
+ const key = args[4] || "";
+
+ if (principal && key) {
+ entries.push({ principal, realm, kvno, etypeInput, key });
+ }
+ }
+
+ if (entries.length === 0) {
+ throw new OperationError("No principal/key entries provided or found in input/arguments.");
+ }
+
+ const finalEntries = [];
+ for (const entry of entries) {
+ const principal = entry.principal.trim();
+ const realm = entry.realm.trim() || "LOCAL";
+ const kvno = parseInt(entry.kvno, 10) || 1;
+
+ let etype = 23;
+ const etypeStr = String(entry.etypeInput).toLowerCase().trim();
+ if (etypeStr.includes("17") || etypeStr.includes("aes128")) {
+ etype = 17;
+ } else if (etypeStr.includes("18") || etypeStr.includes("aes256")) {
+ etype = 18;
+ } else if (etypeStr.includes("23") || etypeStr.includes("rc4") || etypeStr.includes("arcfour")) {
+ etype = 23;
+ } else {
+ const parsed = parseInt(etypeStr, 10);
+ if (!isNaN(parsed)) {
+ etype = parsed;
+ } else {
+ throw new OperationError(`Unsupported or unrecognized encryption type: ${entry.etypeInput}`);
+ }
+ }
+
+ const cleanKey = entry.key.trim().replace(/^0x/i, "").replace(/[\s:]/g, "");
+ if (!/^[a-fA-F0-9]+$/.test(cleanKey)) {
+ throw new OperationError(`Key for principal ${principal} must be a hex-encoded string.`);
+ }
+
+ const keyLengthInBytes = cleanKey.length / 2;
+ if (etype === 23 && keyLengthInBytes !== 16) {
+ throw new OperationError(`RC4 key (etype 23) for principal ${principal} must be 16 bytes (32 hex characters). Got ${keyLengthInBytes} bytes.`);
+ } else if (etype === 17 && keyLengthInBytes !== 16) {
+ throw new OperationError(`AES128 key (etype 17) for principal ${principal} must be 16 bytes (32 hex characters). Got ${keyLengthInBytes} bytes.`);
+ } else if (etype === 18 && keyLengthInBytes !== 32) {
+ throw new OperationError(`AES256 key (etype 18) for principal ${principal} must be 32 bytes (64 hex characters). Got ${keyLengthInBytes} bytes.`);
+ }
+
+ const keyBytes = Utils.convertToByteArray(cleanKey, "hex");
+ finalEntries.push({ principal, realm, kvno, etype, keyBytes, cleanKey });
+ }
+
+ // Build the binary keytab data
+ let totalLength = 2; // For format version bytes [0x05, 0x02]
+ const serializedEntries = [];
+ for (const entry of finalEntries) {
+ const bytes = serializeEntry(entry.principal, entry.realm, entry.kvno, entry.etype, new Uint8Array(entry.keyBytes));
+ serializedEntries.push(bytes);
+ totalLength += bytes.length;
+ }
+
+ const keytabFile = new Uint8Array(totalLength);
+ keytabFile[0] = 0x05;
+ keytabFile[1] = 0x02;
+ let offset = 2;
+ for (const bytes of serializedEntries) {
+ keytabFile.set(bytes, offset);
+ offset += bytes.length;
+ }
+
+ const outputFormat = args[5];
+ switch (outputFormat) {
+ case "Raw Keytab File":
+ return Array.from(keytabFile);
+ case "Hex": {
+ const hexStr = Array.from(keytabFile).map(b => b.toString(16).padStart(2, "0")).join("");
+ return Utils.strToByteArray(hexStr);
+ }
+ case "Base64": {
+ // Convert byteArray to string of chars, then base64 encode
+ const chars = Utils.byteArrayToChars(Array.from(keytabFile));
+ const b64 = btoa(chars);
+ return Utils.strToByteArray(b64);
+ }
+ case "Detailed Summary Text": {
+ let out = `Kerberos KRB5 Keytab Generation Summary:\n`;
+ out += `-----------------------------------------\n`;
+ out += `Successfully generated a keytab file with ${finalEntries.length} entries:\n\n`;
+
+ finalEntries.forEach((entry, idx) => {
+ let etypeName = "Unknown";
+ if (entry.etype === 23) etypeName = "RC4-HMAC (23)";
+ else if (entry.etype === 17) etypeName = "AES128-CTS-HMAC-SHA1-96 (17)";
+ else if (entry.etype === 18) etypeName = "AES256-CTS-HMAC-SHA1-96 (18)";
+
+ out += `Entry ${idx + 1}:\n`;
+ out += ` Principal: ${entry.principal}\n`;
+ out += ` Realm: ${entry.realm}\n`;
+ out += ` KVNO: ${entry.kvno}\n`;
+ out += ` Etype: ${etypeName}\n`;
+ out += ` Key (Hex): ${entry.cleanKey}\n\n`;
+ });
+
+ out += `Wireshark Configuration Instructions:\n`;
+ out += `-------------------------------------\n`;
+ out += `1. Save the generated keytab output as a file (e.g. krb5.keytab).\n`;
+ out += `2. Open Wireshark and go to: Edit -> Preferences -> Protocols -> KRB5\n`;
+ out += `3. Enable "Decrypt Kerberos traffic" (if available).\n`;
+ out += `4. Click "Browse..." next to "Kerberos keytab file" and select the generated file.\n`;
+ out += `5. Wireshark will now automatically decrypt Kerberos, DCE/RPC, LDAP, and SMB traffic where these keys are valid.\n`;
+
+ return Utils.strToByteArray(out);
+ }
+ default:
+ throw new OperationError(`Unknown output format: ${outputFormat}`);
+ }
+ }
+}
+
+export default GenerateKerberosKeytab;
diff --git a/tests/operations/tests/GenerateKerberosKeytab.mjs b/tests/operations/tests/GenerateKerberosKeytab.mjs
new file mode 100644
index 0000000000..6ddc62a58c
--- /dev/null
+++ b/tests/operations/tests/GenerateKerberosKeytab.mjs
@@ -0,0 +1,84 @@
+/**
+ * Generate Kerberos Keytab operation tests
+ *
+ * @author mansiverma897993
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "Generate Kerberos Keytab: Single key via UI args (RC4)",
+ input: "",
+ expectedOutput: "05020000003b00010009574f524b47524f5550000b657368656c6c7374726f70000000010000000001001700103f29138a04aadc19214e9c04028bf38100000001",
+ recipeConfig: [
+ {
+ op: "Generate Kerberos Keytab",
+ args: [
+ "eshellstrop",
+ "WORKGROUP",
+ 1,
+ "RC4-HMAC (23)",
+ "3f29138a04aadc19214e9c04028bf381",
+ "Hex"
+ ],
+ },
+ ],
+ },
+ {
+ name: "Generate Kerberos Keytab: Multiple keys via CSV input",
+ input: "eshellstrop,WORKGROUP,1,23,3f29138a04aadc19214e9c04028bf381\ncifs/server.example.local,EXAMPLE.LOCAL,2,18,00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
+ expectedOutput: "05020000003b00010009574f524b47524f5550000b657368656c6c7374726f70000000010000000001001700103f29138a04aadc19214e9c04028bf381000000010000005e0002000d4558414d504c452e4c4f43414c00046369667300147365727665722e6578616d706c652e6c6f63616c0000000100000000020012002000112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00000002",
+ recipeConfig: [
+ {
+ op: "Generate Kerberos Keytab",
+ args: [
+ "",
+ "",
+ 1,
+ "RC4-HMAC (23)",
+ "",
+ "Hex"
+ ],
+ },
+ ],
+ },
+ {
+ name: "Generate Kerberos Keytab: JSON input format",
+ input: '[\n {"principal": "eshellstrop", "realm": "WORKGROUP", "kvno": 1, "etype": 23, "key": "3f29138a04aadc19214e9c04028bf381"}\n]',
+ expectedOutput: "05020000003b00010009574f524b47524f5550000b657368656c6c7374726f70000000010000000001001700103f29138a04aadc19214e9c04028bf38100000001",
+ recipeConfig: [
+ {
+ op: "Generate Kerberos Keytab",
+ args: [
+ "",
+ "",
+ 1,
+ "RC4-HMAC (23)",
+ "",
+ "Hex"
+ ],
+ },
+ ],
+ },
+ {
+ name: "Generate Kerberos Keytab: Detailed Summary Text output",
+ input: "",
+ expectedOutput: "Kerberos KRB5 Keytab Generation Summary:\n-----------------------------------------\nSuccessfully generated a keytab file with 1 entries:\n\nEntry 1:\n Principal: eshellstrop\n Realm: WORKGROUP\n KVNO: 1\n Etype: RC4-HMAC (23)\n Key (Hex): 3f29138a04aadc19214e9c04028bf381\n\nWireshark Configuration Instructions:\n-------------------------------------\n1. Save the generated keytab output as a file (e.g. krb5.keytab).\n2. Open Wireshark and go to: Edit -> Preferences -> Protocols -> KRB5\n3. Enable \"Decrypt Kerberos traffic\" (if available).\n4. Click \"Browse...\" next to \"Kerberos keytab file\" and select the generated file.\n5. Wireshark will now automatically decrypt Kerberos, DCE/RPC, LDAP, and SMB traffic where these keys are valid.\n",
+ recipeConfig: [
+ {
+ op: "Generate Kerberos Keytab",
+ args: [
+ "eshellstrop",
+ "WORKGROUP",
+ 1,
+ "RC4-HMAC (23)",
+ "3f29138a04aadc19214e9c04028bf381",
+ "Detailed Summary Text"
+ ],
+ },
+ ],
+ },
+]);