diff --git a/PAYMENT_RECIPES.md b/PAYMENT_RECIPES.md
index a20720ec..5ea0af5c 100644
--- a/PAYMENT_RECIPES.md
+++ b/PAYMENT_RECIPES.md
@@ -200,16 +200,25 @@ Important assumptions:
- `Derive DUKPT Key` is TDES DUKPT, not AES DUKPT
- `Generate AS2805 KEK Validation` is an emulation-oriented helper and explicitly documents its simplifications in the operation comments
-## 10) Key Container Inspection
+## 10) Key Container And HSM Command Inspection
Operations:
+- `Parse Thales payShield command`
+- `Parse Futurex Excrypt command`
- `Parse TR-31 key block`
- `Parse TR-34 B9 envelope`
Use this when:
-- you need to inspect inbound wrapped-key material or transport frames during testing
+- you need to inspect vendor HSM command syntax, wrapped-key material, or transport frames during testing
Input:
-- full TR-31 or TR-34 payload as text or hex, depending on the operation comment
+- `Parse Thales payShield command`: raw legacy host command or response text
+- `Parse Futurex Excrypt command`: raw bracketed Excrypt command or response text
+- `Parse TR-31 key block` / `Parse TR-34 B9 envelope`: full payload as text or hex, depending on the operation comment
+
+Important assumptions:
+- the Thales and Futurex parsers currently focus on visible message syntax, delimiters, command identification, and field splitting rather than deep per-command semantic decoding
+- `Parse Thales payShield command` expects the configured message-header length to be supplied in the op args
+- `Parse Futurex Excrypt command` treats Excrypt messages as delimiter-based tag/value fields and commonly uses the `AO` field as the command code
## Chaining Patterns
@@ -309,3 +318,13 @@ Operations:
Flow:
- inspect the KEK with `Calculate Payment KCV`
- generate request or response RandomKeySend / RandomKeyReceive values with the AS2805 helper
+
+## J) Vendor Command Triage
+Operations:
+- `Parse Thales payShield command`
+- `Parse Futurex Excrypt command`
+
+Flow:
+- paste the raw host message first before trying to interpret the business meaning
+- use the parsed command code, delimiters, header, trailer, or tag/value split to confirm what family of command you are looking at
+- follow with lower-level payment, EMV, PIN, or key-container recipes only after the transport syntax is understood
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index cffaf11e..98a88240 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -615,6 +615,7 @@
"Calculate Payment KCV",
"Generate AS2805 KEK Validation",
"Parse Thales payShield command",
+ "Parse Futurex Excrypt command",
"Parse TR-31 key block",
"Parse TR-34 B9 envelope",
"HMAC",
diff --git a/src/core/operations/ParseFuturexExcryptCommand.mjs b/src/core/operations/ParseFuturexExcryptCommand.mjs
new file mode 100644
index 00000000..d6d1c228
--- /dev/null
+++ b/src/core/operations/ParseFuturexExcryptCommand.mjs
@@ -0,0 +1,177 @@
+/**
+ * @license Apache-2.0
+ * @author Jacob Marks [https://jacobmarks.com]
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+const COMMANDS = {
+ CAAV: "Calculate Account holder Authentication Value",
+ DAPT: "Decrypt Apple Pay Token",
+ DCDK: "Decrypt Cardholder Data Using DUKPT",
+ DGPT: "Decrypt Google Pay Token",
+ DRKI: "Identification Request",
+ DRKK: "Key Request",
+ DRKV: "Key Verification Request",
+ DSPT: "Decrypt Samsung Pay Token",
+ ECDK: "Encrypt Cardholder Data Using DUKPT",
+ EMPT: "Translate PIN Block for EMV Personalization",
+ EMVA: "Verify ARQC and optionally generate ARPC",
+ EMVG: "Generate Master Key",
+ EMVK: "Derive Key from Vendor Master Key and Derivation Data",
+ EMVM: "Generate or Verify MAC",
+ EMVP: "PIN Change",
+ EMVR: "EMV RSA Private Key or Component Translation to Encryption Under a Personalization Key",
+ EMVS: "Translate an ICC Master Key to Encryption Under a Personalization Key",
+ EMVT: "EMV Translate Sensitive Data",
+ GCAV: "Generate CAVV",
+ GCIV: "Generate a CVC3 IV",
+ GCSC: "Generate American Express CSC Value",
+ GCVC: "Generate CVC and CVC2",
+ GCVV: "Generate CVV or CVC Value",
+ GDAC: "Generate a Data Authentication Code",
+ GDCV: "Generate dCVV/CVC3",
+ GDDC: "Generate Discover dynamic CVV",
+ GEMC: "Generate EMV ICC Certificate",
+ GEMQ: "Generate EMV Issuer CSR",
+ GHMC: "Generate HCE Mobile Cryptogram",
+ GHMD: "Generate HCE Magstripe Verification Value",
+ GHMK: "Generate HCE Mobile Keys",
+ GHPB: "Generate HMAC and PBKDF2 Obfuscated Value",
+ GIDN: "Generate an ICC dynamic number",
+ GMAC: "Generate Message Authentication Code",
+ GNOF: "Generate New Offset",
+ GOFC: "Generate Offset of Clear PIN",
+ GOFF: "Generate PIN offset value",
+ GOPC: "Generate Offset and EMV PIN Change",
+ GPIN: "Generate PIN",
+ GPMC: "General Purpose Symmetric MAC",
+ GVDC: "Generate dynamic CVV",
+ HMAC: "Generate MAC Hash",
+ OFPC: "Perform EMV PIN Change Using Offset",
+ ONGQ: "Translate PAN Encrypted Under an Asymmetric Key Pair to a Different Trusted Public Key",
+ PEDK: "Key Request",
+ RKHM: "Generate or Verify HMAC",
+ RPIN: "PIN Change and Optional PIN Verification",
+ SSAD: "Sign Static Authentication Data with Issuer Private Key",
+ TCDK: "Translate Cardholder Data Using DUKPT",
+ TDKD: "Translate Cardholder Data Using DUKPT and Symmetric Keys",
+ TKDR: "Translate DUKPT Data to RSA with Specific Output Data",
+ TPCP: "Translate Encrypted PIN Coordinates to a PEK for Generate New Map Collection",
+ TPDD: "Allow an encrypted ANSI PIN block to be translated",
+ TPIN: "Translate PIN blocks",
+ TRPN: "Translate PIN from RSA to Symmetric PIN Block",
+ TSPN: "Translate PIN from PIN block to RSA encryption",
+ VAAV: "Verify Account Holder Authentication Value",
+ VCAC: "Verify EMV Mastercard CAP Token",
+ VCAV: "Verify Cardholder Authentication Verification Value",
+ VCSC: "Verify American Express CSC Value",
+ VCVC: "Verify CVC and CVC2",
+ VCVV: "Verify CVV",
+ VDAC: "Verify a Data Authentication Code",
+ VDCV: "Verify CVC3",
+ VDDC: "Verify dynamic CVC value",
+ VEMI: "Verify an EMV Issuer Certificate",
+ VHMC: "Verify HCE Mobile Cryptogram",
+ VHMD: "Verify HCE Magstripe Verification Value",
+ VIDN: "Verify an ICC dynamic number",
+ VMAC: "Verify Message Authentication Code",
+ VMAP: "Verify MAC and PIN",
+ VPIN: "Verify PIN",
+ VVDC: "Verify a dynamic CVV",
+ WPIN: "Weak PIN checking",
+ XPIN: "PIN translation"
+};
+
+/**
+ * Parses an Excrypt field into tag/value components.
+ *
+ * @param {string} field
+ * @returns {{raw: string, tag: string, value: string}}
+ */
+function parseField(field) {
+ const tag = field.substring(0, Math.min(2, field.length)).toUpperCase();
+ return {
+ raw: field,
+ tag,
+ value: field.substring(tag.length)
+ };
+}
+
+/**
+ * Parse Futurex Excrypt command operation.
+ */
+class ParseFuturexExcryptCommand extends Operation {
+
+ /**
+ * ParseFuturexExcryptCommand constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Parse Futurex Excrypt command";
+ this.module = "Payment";
+ this.description = "Paste a Futurex Excrypt command or response into the input field as text.
General syntax: Excrypt messages are enclosed by opening and closing delimiters, typically [ and ]. Inside the message, fields are semicolon-delimited. Each field is a tag/value pair, for example AOECHO where AO is the tag and ECHO is the value. The command code is commonly carried in the AO field.
Input: raw Excrypt message text.
This operation parses the visible Excrypt message syntax, extracts semicolon-delimited fields, splits fields into tag/value pairs, and resolves the AO command code to a known payment command name when available from the Futurex payment integration guide.";
+ this.inlineHelp = "Syntax: [tagvalue;tagvalue;...] where fields are separated by semicolons and tags are typically two characters such as AO.
Input: raw Futurex Excrypt message text.";
+ this.testDataSamples = [
+ {
+ name: "Excrypt command sample",
+ input: "[AOGMAC;FS6;RV0011223344556677;]"
+ }
+ ];
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [];
+ }
+
+ /**
+ * @param {string} input
+ * @returns {string}
+ */
+ run(input) {
+ const rawInput = (input || "").replace(/\r?\n/g, "");
+ if (!rawInput.length) {
+ throw new OperationError("No input.");
+ }
+
+ const openingDelimiterPresent = rawInput.startsWith("[");
+ const closingDelimiterPresent = rawInput.endsWith("]");
+ const body = rawInput.replace(/^\[/, "").replace(/\]$/, "");
+ const rawFields = body.split(";").filter(field => field.length > 0);
+
+ if (!rawFields.length) {
+ throw new OperationError("No Excrypt fields found.");
+ }
+
+ const fields = rawFields.map(parseField);
+ const commandField = fields.find(field => field.tag === "AO") || fields[0];
+ const commandCode = commandField.value.toUpperCase();
+ const commandName = COMMANDS[commandCode] || null;
+ const notes = [];
+
+ if (!openingDelimiterPresent || !closingDelimiterPresent) {
+ notes.push("Message is missing one or both expected Excrypt outer delimiters.");
+ }
+
+ if (!commandName) {
+ notes.push("Command code was not found in the Futurex payment integration guide lookup.");
+ }
+
+ return JSON.stringify({
+ rawInput,
+ openingDelimiterPresent,
+ closingDelimiterPresent,
+ body,
+ rawFields,
+ fields,
+ commandFieldTag: commandField.tag,
+ commandCode,
+ commandName,
+ fieldCount: fields.length,
+ notes
+ }, null, 4);
+ }
+}
+
+export default ParseFuturexExcryptCommand;
diff --git a/tests/operations/tests/Payment.mjs b/tests/operations/tests/Payment.mjs
index f303a3f6..b8447c4e 100644
--- a/tests/operations/tests/Payment.mjs
+++ b/tests/operations/tests/Payment.mjs
@@ -92,6 +92,94 @@ TestRegister.addTests([
}
]
},
+ {
+ name: "Parse Futurex Excrypt command: bracketed fields",
+ input: "[AOGMAC;FS6;RV0011223344556677;]",
+ expectedOutput: JSON.stringify({
+ rawInput: "[AOGMAC;FS6;RV0011223344556677;]",
+ openingDelimiterPresent: true,
+ closingDelimiterPresent: true,
+ body: "AOGMAC;FS6;RV0011223344556677;",
+ rawFields: [
+ "AOGMAC",
+ "FS6",
+ "RV0011223344556677"
+ ],
+ fields: [
+ {
+ raw: "AOGMAC",
+ tag: "AO",
+ value: "GMAC"
+ },
+ {
+ raw: "FS6",
+ tag: "FS",
+ value: "6"
+ },
+ {
+ raw: "RV0011223344556677",
+ tag: "RV",
+ value: "0011223344556677"
+ }
+ ],
+ commandFieldTag: "AO",
+ commandCode: "GMAC",
+ commandName: "Generate Message Authentication Code",
+ fieldCount: 3,
+ notes: []
+ }, null, 4),
+ recipeConfig: [
+ {
+ op: "Parse Futurex Excrypt command",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "Parse Futurex Excrypt command: missing closing delimiter",
+ input: "[AOVMAC;FS6;RV89ABCDEF",
+ expectedOutput: JSON.stringify({
+ rawInput: "[AOVMAC;FS6;RV89ABCDEF",
+ openingDelimiterPresent: true,
+ closingDelimiterPresent: false,
+ body: "AOVMAC;FS6;RV89ABCDEF",
+ rawFields: [
+ "AOVMAC",
+ "FS6",
+ "RV89ABCDEF"
+ ],
+ fields: [
+ {
+ raw: "AOVMAC",
+ tag: "AO",
+ value: "VMAC"
+ },
+ {
+ raw: "FS6",
+ tag: "FS",
+ value: "6"
+ },
+ {
+ raw: "RV89ABCDEF",
+ tag: "RV",
+ value: "89ABCDEF"
+ }
+ ],
+ commandFieldTag: "AO",
+ commandCode: "VMAC",
+ commandName: "Verify Message Authentication Code",
+ fieldCount: 3,
+ notes: [
+ "Message is missing one or both expected Excrypt outer delimiters."
+ ]
+ }, null, 4),
+ recipeConfig: [
+ {
+ op: "Parse Futurex Excrypt command",
+ args: []
+ }
+ ]
+ },
{
name: "Parse TR-31 key block: fixed header only",
input: "D0016D0AB00E0000",