Add payment MAC wrapper operations

This commit is contained in:
J8k3 2026-04-25 09:20:28 -04:00
parent 7433b07f2b
commit 3f1b63378f
8 changed files with 471 additions and 30 deletions

View File

@ -25,8 +25,8 @@ Coverage legend:
| `EncryptData` | `Direct` / `Partial` | Direct for AES, TDES, RSA. Partial for DUKPT and EMV-derived encryption. |
| `DecryptData` | `Direct` / `Partial` | Direct for AES, TDES, RSA. Partial for DUKPT and EMV-derived decryption. |
| `ReEncryptData` | `Direct` / `Partial` | Direct for plain decrypt-then-encrypt workflows. Partial for DUKPT re-encryption. |
| `GenerateMac` | `Direct` / `Partial` | Direct for HMAC and CMAC. Partial for DUKPT MAC and EMV MAC flows. |
| `VerifyMac` | `Direct` / `Partial` | Direct by recomputing and comparing HMAC/CMAC. Partial for DUKPT MAC and EMV MAC flows. |
| `GenerateMac` | `Direct` / `Partial` | Direct for static-key HMAC and CMAC, and direct for the implemented DUKPT CMAC wrapper modes. Partial for ISO 9797, EMV MAC, and AS2805 flows. |
| `VerifyMac` | `Direct` / `Partial` | Direct for static-key HMAC and CMAC, and direct for the implemented DUKPT CMAC wrapper modes. Partial for ISO 9797, EMV MAC, and AS2805 flows. |
| `VerifyAuthRequestCryptogram` | `Partial` | Usable for AES-CMAC ARQC/ARPC-style checking when session key and preimage are already known. Dedicated ARQC and ARPC generators now exist for that constrained profile. |
| `TranslateKeyMaterial` | `Partial` | Useful for ECDH derivation and TR-31 inspection, not full HSM-side rewrap semantics. |
| `GenerateCardValidationData` | `Direct` | Direct for software CVV/CVV2/iCVV generation when the combined CVK pair is provided as clear hex. |
@ -103,15 +103,29 @@ Suggested use:
## 6) AWS `VerifyMac`: Recompute And Compare
Operations:
- `From Hex`
- `HMAC` or `CMAC`
- `Take bytes`
- `Verify payment MAC`
Suggested use:
- Recompute the MAC using the same starter as `GenerateMac`.
- Compare the result to the AWS `Mac` value manually or with a follow-on comparison recipe.
- Paste the message into the input field, choose the MAC method, and provide either the direct key or the DUKPT BDK plus KSN.
- Supply the expected MAC in hex and let the wrapper recompute and compare it.
## 7) AWS `GenerateCardValidationData`: CVV / CVV2 / iCVV
Notes:
- This covers the implemented static-key HMAC/CMAC and DUKPT-CMAC wrapper modes directly.
- ISO 9797, EMV MAC, and AS2805-specific verification are still partial gaps.
## 7) AWS `GenerateMac`: Payment Wrapper
Operations:
- `Generate payment MAC`
Suggested use:
- Paste the message into the input field and choose the payment MAC method that best matches the AWS attributes.
- Use direct key input for static HMAC or CMAC modes, or provide a BDK plus KSN for the implemented DUKPT CMAC request and response modes.
Notes:
- This wrapper exists for usability so payment users can stay in the `Payments` category without needing to know which low-level primitive is underneath.
- It intentionally reuses the existing generic `HMAC` and `CMAC` implementations.
## 8) AWS `GenerateCardValidationData`: CVV / CVV2 / iCVV
Operations:
- `Generate card validation data`
@ -124,7 +138,7 @@ Notes:
- This directly covers software generation of CVV/CVV2/iCVV-style values.
- Assumption: CVV2 forces service code `000` and iCVV forces `999`.
## 8) AWS `VerifyCardValidationData`: CVV / CVV2 / iCVV
## 9) AWS `VerifyCardValidationData`: CVV / CVV2 / iCVV
Operations:
- `Verify card validation data`
@ -138,7 +152,7 @@ Notes:
## Partial Recipe Starters
## 9) AWS `EncryptData` / `DecryptData`: DUKPT-Derived Symmetric Flows
## 10) AWS `EncryptData` / `DecryptData`: DUKPT-Derived Symmetric Flows
Operations:
- `Derive DUKPT key`
- `AES Encrypt` or `AES Decrypt` or `Triple DES Encrypt` or `Triple DES Decrypt`
@ -151,20 +165,6 @@ Notes:
- This is useful for offline vector work.
- It does not claim one-to-one parity with every AWS DUKPT encryption attribute combination.
## 10) AWS `GenerateMac` / `VerifyMac`: DUKPT MAC
Operations:
- `Derive DUKPT key`
- `From Hex`
- `CMAC` or `HMAC`
- `Take bytes`
Suggested use:
- Derive the transaction key from BDK and KSN.
- Convert `MessageData` from hex and generate the MAC using the derived key.
Notes:
- Treat this as a lab starter, not proof of parity with AWSs full DUKPT MAC union attributes.
## 11) AWS `VerifyAuthRequestCryptogram`: EMV ARQC Check
Operations:
- `Generate EMV ARQC`
@ -257,6 +257,7 @@ Why:
If you want closer AWS coverage, the highest-value missing operations are:
1. PIN block encode/decode for ISO 9564 formats 0, 1, 3, and 4.
2. IBM 3624 and VISA PVV generation and verification.
3. Dedicated EMV MAC and profile-specific EMV session-derivation helpers.
4. Clear-to-encrypted and encrypted-to-encrypted PIN translation flows.
5. TR-31 unwrap and rewrap helpers for dynamic-key workflows.
3. ISO 9797 and AS2805-specific MAC generation and verification.
4. Dedicated EMV MAC and profile-specific EMV session-derivation helpers.
5. Clear-to-encrypted and encrypted-to-encrypted PIN translation flows.
6. TR-31 unwrap and rewrap helpers for dynamic-key workflows.

View File

@ -62,7 +62,22 @@ Scope note:
- CVV2 forces service code `000` and iCVV forces `999`.
- It does not try to emulate scheme-specific dCVV, token CVV, or issuer-host formatting differences beyond the common decimalization flow.
## 8) EMV ARQC Generation (AES-CMAC Profile)
## 8) Payment MAC Generation And Verification
Operations:
- `Generate payment MAC`
- `Verify payment MAC`
Suggested use:
- Paste the message data into the input field.
- Choose whether the MAC should use static `HMAC`, static `CMAC`, or DUKPT-derived TDES-CMAC.
- Provide either a direct MAC key or a BDK plus KSN, depending on the selected method.
Scope note:
- This wrapper intentionally reuses the existing generic `HMAC` and `CMAC` implementations instead of duplicating crypto code.
- Current DUKPT coverage derives TDES session keys and applies TDES-CMAC for request and response MAC variants.
- ISO 9797, EMV session-derivation MAC, and AS2805 are still future additions.
## 9) EMV ARQC Generation (AES-CMAC Profile)
Operations:
- `Generate EMV ARQC`
@ -75,7 +90,7 @@ Scope note:
- This operation is intentionally limited to AES-CMAC-style EMV profiles.
- It does not derive EMV session keys or assemble CDOL/tag data for you.
## 9) EMV ARPC Generation (AES-CMAC Response Profile)
## 10) EMV ARPC Generation (AES-CMAC Response Profile)
Operations:
- `Generate EMV ARPC`
@ -88,7 +103,7 @@ Scope note:
- This operation is intentionally limited to AES-CMAC response profiles where the issuer session key and exact preimage are already known.
- Legacy 3DES EMV ARQC/ARPC flows are not covered.
## 10) Combined Message Triage
## 11) Combined Message Triage
Operations:
- `Parse TR-34 B9 envelope`
- `Parse ASN.1 hex string`

View File

@ -568,11 +568,21 @@
{
"name": "Payments",
"ops": [
"HMAC",
"CMAC",
"AES Encrypt",
"AES Decrypt",
"Triple DES Encrypt",
"Triple DES Decrypt",
"AES Key Wrap",
"AES Key Unwrap",
"Parse TR-31 key block",
"Parse TR-34 B9 envelope",
"Calculate payment KCV",
"Derive ECDH key material",
"Derive DUKPT key",
"Generate payment MAC",
"Verify payment MAC",
"Generate card validation data",
"Verify card validation data",
"Generate EMV ARQC",

175
src/core/lib/PaymentMac.mjs Normal file
View File

@ -0,0 +1,175 @@
/**
* @license Apache-2.0
*/
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import HMAC from "../operations/HMAC.mjs";
import CMAC from "../operations/CMAC.mjs";
import DeriveDUKPTKey from "../operations/DeriveDUKPTKey.mjs";
const PAYMENT_MAC_METHODS = [
"HMAC SHA-224",
"HMAC SHA-256",
"HMAC SHA-384",
"HMAC SHA-512",
"AES-CMAC",
"TDES-CMAC",
"DUKPT MAC Request CMAC",
"DUKPT MAC Response CMAC",
];
/**
* Converts a string input into an ArrayBuffer according to the selected format.
*
* @param {string} input
* @param {string} inputFormat
* @returns {ArrayBuffer}
*/
function convertInputToBuffer(input, inputFormat) {
const byteString = Utils.convertToByteString(input || "", inputFormat);
return Utils.strToArrayBuffer(byteString);
}
/**
* Resolves the effective MAC key for the selected method.
*
* @param {string} method
* @param {Object} keySpec
* @returns {{keyHex: string, keyContext: Object}}
*/
function resolveMacKey(method, keySpec) {
const normalizedKey = (keySpec.keyValue || "").replace(/\s+/g, "");
if (method === "DUKPT MAC Request CMAC" || method === "DUKPT MAC Response CMAC") {
if (keySpec.keyFormat !== "Hex") {
throw new OperationError("DUKPT BDK must be provided in hex.");
}
if (!keySpec.ksn) {
throw new OperationError("KSN is required for DUKPT MAC methods.");
}
const variant = method === "DUKPT MAC Request CMAC" ? "MAC Request" : "MAC Response";
const dukpt = new DeriveDUKPTKey();
const keyHex = dukpt.run(normalizedKey, ["Derive Session Key", keySpec.ksn, variant, false]);
return {
keyHex,
keyContext: {
keySource: "Derived from DUKPT BDK",
ksn: keySpec.ksn.replace(/\s+/g, "").toUpperCase(),
dukptVariant: variant
}
};
}
const byteString = Utils.convertToByteString(keySpec.keyValue || "", keySpec.keyFormat);
if (!byteString.length) {
throw new OperationError("Key material is required.");
}
return {
keyHex: byteStringToHex(byteString),
keyContext: {
keySource: "Direct key input"
}
};
}
/**
* Converts a byte string into uppercase hex.
*
* @param {string} byteString
* @returns {string}
*/
function byteStringToHex(byteString) {
return Array.from(byteString, ch => ch.charCodeAt(0).toString(16).padStart(2, "0")).join("").toUpperCase();
}
/**
* Generates a payment MAC using the selected method.
*
* @param {string} input
* @param {string} inputFormat
* @param {string} method
* @param {string} keyValue
* @param {string} keyFormat
* @param {string} ksn
* @param {number} outputBytes
* @returns {Object}
*/
function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes) {
const normalizedOutputBytes = Math.max(1, Number(outputBytes) || 8);
const inputBuffer = convertInputToBuffer(input, inputFormat);
const inputHex = byteStringToHex(Utils.arrayBufferToStr(inputBuffer, false));
const { keyHex, keyContext } = resolveMacKey(method, { keyValue, keyFormat, ksn });
let fullMacHex;
if (method.startsWith("HMAC ")) {
const hmac = new HMAC();
const hashName = {
"HMAC SHA-224": "SHA224",
"HMAC SHA-256": "SHA256",
"HMAC SHA-384": "SHA384",
"HMAC SHA-512": "SHA512",
}[method];
fullMacHex = hmac.run(inputBuffer, [{ string: keyHex, option: "Hex" }, hashName]).toUpperCase();
} else {
const cmac = new CMAC();
const algorithm = method === "AES-CMAC" ? "AES" : "Triple DES";
fullMacHex = cmac.run(inputBuffer, [{ string: keyHex, option: "Hex" }, algorithm]).toUpperCase();
}
const macHex = fullMacHex.substring(0, normalizedOutputBytes * 2);
return {
method,
inputFormat,
inputHex,
outputBytes: normalizedOutputBytes,
fullMacHex,
macHex,
...keyContext
};
}
/**
* Verifies a payment MAC by recomputing and comparing it.
*
* @param {string} input
* @param {string} inputFormat
* @param {string} method
* @param {string} keyValue
* @param {string} keyFormat
* @param {string} ksn
* @param {string} expectedMac
* @returns {Object}
*/
function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac) {
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 = generatePaymentMac(
input,
inputFormat,
method,
keyValue,
keyFormat,
ksn,
normalizedExpected.length / 2
);
return {
...generated,
expectedMacHex: normalizedExpected,
valid: generated.macHex === normalizedExpected
};
}
export {
PAYMENT_MAC_METHODS,
generatePaymentMac,
verifyPaymentMac,
};

View File

@ -0,0 +1,93 @@
/**
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import { PAYMENT_MAC_METHODS, generatePaymentMac } from "../lib/PaymentMac.mjs";
/**
* Generate payment MAC operation.
*/
class GeneratePaymentMAC extends Operation {
/**
* GeneratePaymentMAC constructor.
*/
constructor() {
super();
this.name = "Generate payment MAC";
this.module = "Payment";
this.description = "Paste the message data into the input field and generate a payment-oriented MAC using one payment-facing operation.<br><br><b>Input:</b> message data in the selected input format.<br><b>Arguments:</b> choose the MAC method, provide either a direct key or a DUKPT BDK, optionally provide a KSN for DUKPT methods, and choose the truncation length.<br><br>This wrapper reuses existing HMAC, CMAC, and DUKPT operations instead of duplicating their crypto logic.";
this.inlineHelp = "<strong>Input:</strong> message data.<br><strong>Args:</strong> choose the payment MAC method, then provide either a direct key or a DUKPT BDK plus KSN.";
this.testDataSamples = [
{
name: "Static AES-CMAC sample",
input: "1122334455667788",
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", 8, false]
}
];
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_GenerateMac.html";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "UTF8", "Latin1", "Base64"],
comment: "How to decode the input field before MAC generation. Use <code>Hex</code> for payment test vectors expressed as hex."
},
{
name: "MAC method",
type: "option",
value: PAYMENT_MAC_METHODS,
comment: "Static-key HMAC and CMAC modes reuse the existing generic primitives. DUKPT modes derive a TDES session key first and then apply TDES-CMAC."
},
{
name: "Key / BDK",
type: "string",
value: "",
comment: "Provide the direct MAC key for HMAC or CMAC methods, or the clear BDK for DUKPT methods."
},
{
name: "Key format",
type: "option",
value: ["Hex", "UTF8", "Latin1", "Base64"],
comment: "How to decode the key input. Assumption: DUKPT BDK input must be <code>Hex</code>."
},
{
name: "KSN (DUKPT only)",
type: "string",
value: "",
comment: "Required only for DUKPT MAC methods. Provide the full 10-byte KSN as 20 hex characters."
},
{
name: "Output bytes",
type: "number",
value: 8,
min: 1,
max: 64,
comment: "Number of leftmost MAC bytes to return. Leave at <code>8</code> for common payment truncation lengths."
},
{
name: "Output as JSON",
type: "boolean",
value: false,
comment: "When enabled, returns the full MAC, truncation details, and key-context metadata."
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, method, keyValue, keyFormat, ksn, outputBytes, outputJson] = args;
const result = generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes);
return outputJson ? JSON.stringify(result, null, 4) : result.macHex;
}
}
export default GeneratePaymentMAC;

View File

@ -0,0 +1,91 @@
/**
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import { PAYMENT_MAC_METHODS, verifyPaymentMac } from "../lib/PaymentMac.mjs";
/**
* Verify payment MAC operation.
*/
class VerifyPaymentMAC extends Operation {
/**
* VerifyPaymentMAC constructor.
*/
constructor() {
super();
this.name = "Verify payment MAC";
this.module = "Payment";
this.description = "Paste the message data into the input field and verify a payment-oriented MAC using one payment-facing operation.<br><br><b>Input:</b> message data in the selected input format.<br><b>Arguments:</b> choose the MAC method, provide either a direct key or a DUKPT BDK, add the KSN for DUKPT methods, and supply the expected MAC as hex.<br><br>This wrapper recomputes the MAC using the same payment-specific assumptions as the generate operation.";
this.inlineHelp = "<strong>Input:</strong> message data.<br><strong>Args:</strong> choose the payment MAC method, provide the key context, then paste the expected MAC.";
this.testDataSamples = [
{
name: "Static AES-CMAC verification sample",
input: "1122334455667788",
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "339AF1AD1650E908", true]
}
];
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VerifyMac.html";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "UTF8", "Latin1", "Base64"],
comment: "How to decode the input field before MAC verification."
},
{
name: "MAC method",
type: "option",
value: PAYMENT_MAC_METHODS,
comment: "Static-key HMAC and CMAC modes reuse the existing generic primitives. DUKPT modes derive a TDES session key first and then apply TDES-CMAC."
},
{
name: "Key / BDK",
type: "string",
value: "",
comment: "Provide the direct MAC key for HMAC or CMAC methods, or the clear BDK for DUKPT methods."
},
{
name: "Key format",
type: "option",
value: ["Hex", "UTF8", "Latin1", "Base64"],
comment: "How to decode the key input. Assumption: DUKPT BDK input must be <code>Hex</code>."
},
{
name: "KSN (DUKPT only)",
type: "string",
value: "",
comment: "Required only for DUKPT MAC methods. Provide the full 10-byte KSN as 20 hex characters."
},
{
name: "Expected MAC (hex)",
type: "string",
value: "",
comment: "MAC value to compare against, expressed as even-length hex."
},
{
name: "Output as JSON",
type: "boolean",
value: true,
comment: "When enabled, returns the recomputed MAC, comparison target, and validity result."
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, method, keyValue, keyFormat, ksn, expectedMac, outputJson] = args;
const result = verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac);
return outputJson ? JSON.stringify(result, null, 4) : String(result.valid);
}
}
export default VerifyPaymentMAC;

View File

@ -221,6 +221,8 @@ input.toggle-string {
filter: brightness(100%);
}
.operation .form-group.is-filled label.bmd-label-floating,
.operation .form-group.is-focused label.bmd-label-floating,
.operation .bmd-form-group.is-filled label.bmd-label-floating,
.operation .bmd-form-group.is-focused label.bmd-label-floating {
top: 4px !important;

View File

@ -255,6 +255,60 @@ TestRegister.addTests([
}
]
},
{
name: "Generate payment MAC: AES-CMAC",
input: "1122334455667788",
expectedOutput: "339AF1AD1650E908",
recipeConfig: [
{
op: "Generate payment MAC",
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", 8, false]
}
]
},
{
name: "Generate payment MAC: HMAC SHA-256",
input: "1122334455667788",
expectedOutput: "9300E1D36DD30415",
recipeConfig: [
{
op: "Generate payment MAC",
args: ["Hex", "HMAC SHA-256", "00112233445566778899AABBCCDDEEFF", "Hex", "", 8, false]
}
]
},
{
name: "Generate payment MAC: DUKPT MAC Request CMAC",
input: "1122334455667788",
expectedOutput: "3616961727FE155D",
recipeConfig: [
{
op: "Generate payment MAC",
args: ["Hex", "DUKPT MAC Request CMAC", "0123456789ABCDEFFEDCBA9876543210", "Hex", "FFFF9876543210E00008", 8, false]
}
]
},
{
name: "Verify payment MAC: AES-CMAC",
input: "1122334455667788",
expectedOutput: JSON.stringify({
method: "AES-CMAC",
inputFormat: "Hex",
inputHex: "1122334455667788",
outputBytes: 8,
fullMacHex: "339AF1AD1650E908A794284D91DC6D29",
macHex: "339AF1AD1650E908",
keySource: "Direct key input",
expectedMacHex: "339AF1AD1650E908",
valid: true
}, null, 4),
recipeConfig: [
{
op: "Verify payment MAC",
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "339AF1AD1650E908", true]
}
]
},
{
name: "Derive ECDH key material: raw shared secret",
input: ecdhPrivateKey,