From 9015ea9f405830d358125e7fe76beecc4141fbc2 Mon Sep 17 00:00:00 2001 From: J8k3 Date: Tue, 19 May 2026 20:43:25 -0400 Subject: [PATCH] EMV Generate/Verify MAC: add padding method selector (default Method 2) --- src/core/lib/EmvMac.mjs | 10 ++++++---- src/core/operations/GenerateEMVMAC.mjs | 7 ++++--- src/core/operations/VerifyEMVMAC.mjs | 7 ++++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/core/lib/EmvMac.mjs b/src/core/lib/EmvMac.mjs index 9f307e67..b73c9caa 100644 --- a/src/core/lib/EmvMac.mjs +++ b/src/core/lib/EmvMac.mjs @@ -12,16 +12,17 @@ import { generateIso9797Algorithm3Mac } from "./Iso9797.mjs"; * @param {string} messageHex * @param {string} sessionKeyHex * @param {number} outputBytes + * @param {string} paddingMethod * @returns {Object} */ -function generateEmvMac(messageHex, sessionKeyHex, outputBytes=8) { +function generateEmvMac(messageHex, sessionKeyHex, outputBytes=8, paddingMethod="Method 2") { const normalizedKey = (sessionKeyHex || "").replace(/\s+/g, ""); if (!/^[0-9A-Fa-f]+$/.test(normalizedKey) || normalizedKey.length % 2 !== 0) { throw new OperationError("Session key must be hex."); } return { - ...generateIso9797Algorithm3Mac(messageHex, normalizedKey, "Method 2", outputBytes), + ...generateIso9797Algorithm3Mac(messageHex, normalizedKey, paddingMethod, outputBytes), algorithm: "EMV MAC" }; } @@ -32,15 +33,16 @@ function generateEmvMac(messageHex, sessionKeyHex, outputBytes=8) { * @param {string} messageHex * @param {string} sessionKeyHex * @param {string} expectedMac + * @param {string} paddingMethod * @returns {Object} */ -function verifyEmvMac(messageHex, sessionKeyHex, expectedMac) { +function verifyEmvMac(messageHex, sessionKeyHex, expectedMac, paddingMethod="Method 2") { const normalizedExpected = (expectedMac || "").replace(/\s+/g, "").toUpperCase(); if (!/^[0-9A-F]+$/.test(normalizedExpected) || normalizedExpected.length % 2 !== 0) { throw new OperationError("Expected MAC must be even-length hex."); } - const generated = generateEmvMac(messageHex, sessionKeyHex, normalizedExpected.length / 2); + const generated = generateEmvMac(messageHex, sessionKeyHex, normalizedExpected.length / 2, paddingMethod); return { ...generated, expectedMacHex: normalizedExpected, diff --git a/src/core/operations/GenerateEMVMAC.mjs b/src/core/operations/GenerateEMVMAC.mjs index 850686fa..fb3c508c 100644 --- a/src/core/operations/GenerateEMVMAC.mjs +++ b/src/core/operations/GenerateEMVMAC.mjs @@ -24,7 +24,7 @@ class GenerateEMVMAC extends Operation { { name: "EMV MAC sample", input: "8424000008999E57FD0F47CACE0007", - args: ["0123456789ABCDEFFEDCBA9876543210", 8, false] + args: ["0123456789ABCDEFFEDCBA9876543210", "Method 2", 8, false] } ]; this.infoURL = "https://en.wikipedia.org/wiki/EMV"; @@ -32,6 +32,7 @@ class GenerateEMVMAC extends Operation { this.outputType = "string"; this.args = [ { name: "Session integrity key (hex)", type: "string", value: "", comment: "Provide the already-derived EMV integrity session key in hex. This op does not derive EMV keys for you." }, + { name: "Padding method", type: "option", value: ["Method 2", "Method 1"], comment: "Method 2 appends 0x80 then zero-pads to block boundary (ISO 7816-4; standard for EMV issuer scripts). Method 1 zero-pads to block boundary only." }, { name: "Output bytes", type: "number", value: 8, min: 1, max: 8, comment: "Number of leftmost MAC bytes to return. EMV issuer scripts commonly use 8 bytes." }, { name: "Output as JSON", type: "boolean", value: false, comment: "When enabled, returns the issuer-script input and full retail-MAC details." }, ]; @@ -43,8 +44,8 @@ class GenerateEMVMAC extends Operation { * @returns {string} */ run(input, args) { - const [sessionKeyHex, outputBytes, outputJson] = args; - const result = generateEmvMac(input, sessionKeyHex, outputBytes); + const [sessionKeyHex, paddingMethod, outputBytes, outputJson] = args; + const result = generateEmvMac(input, sessionKeyHex, outputBytes, paddingMethod); return outputJson ? JSON.stringify(result, null, 4) : result.macHex; } } diff --git a/src/core/operations/VerifyEMVMAC.mjs b/src/core/operations/VerifyEMVMAC.mjs index 70c1106e..da479ab0 100644 --- a/src/core/operations/VerifyEMVMAC.mjs +++ b/src/core/operations/VerifyEMVMAC.mjs @@ -24,7 +24,7 @@ class VerifyEMVMAC extends Operation { { name: "EMV MAC verification sample", input: "8424000008999E57FD0F47CACE0007", - args: ["0123456789ABCDEFFEDCBA9876543210", "22CB48394DFD1977", true] + args: ["0123456789ABCDEFFEDCBA9876543210", "22CB48394DFD1977", "Method 2", true] } ]; this.infoURL = "https://en.wikipedia.org/wiki/EMV"; @@ -33,6 +33,7 @@ class VerifyEMVMAC extends Operation { this.args = [ { name: "Session integrity key (hex)", type: "string", value: "", comment: "Provide the already-derived EMV integrity session key in hex. This op does not derive EMV keys for you." }, { name: "Expected MAC (hex)", type: "string", value: "", comment: "Issuer-script MAC to compare against, expressed as even-length hex." }, + { name: "Padding method", type: "option", value: ["Method 2", "Method 1"], comment: "Must match the method used during generation. Method 2 appends 0x80 then zero-pads (ISO 7816-4; standard for EMV issuer scripts). Method 1 zero-pads only." }, { name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the recomputed MAC and validity result." }, ]; } @@ -43,8 +44,8 @@ class VerifyEMVMAC extends Operation { * @returns {string} */ run(input, args) { - const [sessionKeyHex, expectedMac, outputJson] = args; - const result = verifyEmvMac(input, sessionKeyHex, expectedMac); + const [sessionKeyHex, expectedMac, paddingMethod, outputJson] = args; + const result = verifyEmvMac(input, sessionKeyHex, expectedMac, paddingMethod); return outputJson ? JSON.stringify(result, null, 4) : String(result.valid); } }