Add PIN Block Translate Encrypted; fix CBOR v9 encode; fix EMV MAC tests; fix bcrypt node test
- PIN Block Translate Encrypted: new operation with 5 tests; registered in Payments category - CBOR v9: fix Encoder streaming/Buffer pool issue; JSDoc on helpers - EMV Generate MAC: fix empty-input hex parse, stale 3-arg test, missing padding method in verify test - parseHexBytes: accept empty string as valid 0-byte hex - bcrypt node test: accept $2a prefix from bcryptjs v2.4.3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c628207471
commit
ffc5fcbf41
@ -151,7 +151,7 @@ Operations:
|
||||
- `PIN Data Generate`
|
||||
- `PIN Data Verify`
|
||||
|
||||
> **Note:** Encrypted PIN block translation (decrypt under one incoming zone key, re-encrypt under a different outgoing zone key, as in AWS `TranslatePinData`) is not yet implemented — tracked in issue #4. Use `PIN Block Translate` (section 7) for clear-format-to-format conversion only.
|
||||
> **Note:** Encrypted PIN block translation is implemented as `PIN Block Translate Encrypted` (section 7). Use `PIN Block Translate` for clear-format-to-format conversion only.
|
||||
|
||||
Use this when:
|
||||
- you want AWS-style PIN-data naming for clear ISO 9564 block flows
|
||||
@ -170,17 +170,21 @@ Operations:
|
||||
- `PIN Block Build`
|
||||
- `PIN Block Parse`
|
||||
- `PIN Block Translate`
|
||||
- `PIN Block Translate Encrypted`
|
||||
|
||||
Use this when:
|
||||
- you want the lower-level clear PIN-block tools directly
|
||||
- `PIN Block Translate Encrypted`: decrypt an encrypted PIN block under an incoming zone key (ZPK/PEK), optionally change format, and re-encrypt under an outgoing zone key — this is the acquirer's core PIN routing operation (issue #17)
|
||||
|
||||
Input:
|
||||
- `PIN Block Build`: clear PIN digits
|
||||
- `PIN Block Parse`: clear PIN block hex
|
||||
- `PIN Block Translate`: clear PIN block hex
|
||||
- `PIN Block Translate Encrypted`: encrypted PIN block hex (8 bytes / 16 hex chars)
|
||||
|
||||
Important assumptions:
|
||||
- current clear-block support is ISO formats `0`, `1`, and `3`
|
||||
- `PIN Block Translate Encrypted` uses TDES-ECB; accepts 2-key (16-byte) or 3-key (24-byte) keys
|
||||
|
||||
## 8) Issuer PIN Verification Helpers
|
||||
|
||||
@ -389,6 +393,7 @@ Release guidance: `Publish` = safe with normal guardrails; `Publish with guardra
|
||||
| `PIN Block Build` | Vendor-aligned | AWS `GeneratePinData`; ISO 9564 | Publish with guardrails |
|
||||
| `PIN Block Parse` | Vendor-aligned | AWS `VerifyPinData`; ISO 9564 | Publish with guardrails |
|
||||
| `PIN Block Translate` | Vendor-aligned | AWS `TranslatePinData`; ISO 9564 | Publish with guardrails |
|
||||
| `PIN Block Translate Encrypted` | Vendor-aligned | AWS `TranslatePinData`; ISO 9564; PCI PIN Req 3-3 | Publish with guardrails |
|
||||
| `PIN Data Generate` | Vendor-aligned | AWS `GeneratePinData` | Publish with guardrails |
|
||||
| `PIN Data Verify` | Vendor-aligned | AWS `VerifyPinData` | Publish with guardrails |
|
||||
| `Payment Calculate KCV` | Verified | NIST SP 800-38B; generic AES/TDES/HMAC primitives | Publish |
|
||||
|
||||
@ -611,6 +611,7 @@
|
||||
"PIN Block Build",
|
||||
"PIN Block Parse",
|
||||
"PIN Block Translate",
|
||||
"PIN Block Translate Encrypted",
|
||||
"PIN Data Generate",
|
||||
"PIN Data Verify",
|
||||
"PIN Generate",
|
||||
|
||||
@ -16,7 +16,7 @@ import { toHexFast } from "./Hex.mjs";
|
||||
*/
|
||||
function parseHexBytes(input, name, allowedLengths=[]) {
|
||||
const normalized = (input || "").replace(/\s+/g, "");
|
||||
if (!/^[0-9a-fA-F]+$/.test(normalized) || normalized.length % 2 !== 0) {
|
||||
if (!/^[0-9a-fA-F]*$/.test(normalized) || normalized.length % 2 !== 0) {
|
||||
throw new OperationError(`${name} must be hex.`);
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,73 @@
|
||||
import Operation from "../Operation.mjs";
|
||||
import Cbor from "cbor";
|
||||
|
||||
// cbor v9: Encoder.encode/encodeCanonical return only the first byte.
|
||||
// Pre-sort map keys ourselves and use a custom Map semantic type so the
|
||||
// encoder writes keys in insertion order without re-sorting internally.
|
||||
|
||||
/**
|
||||
* Returns the byte-length of a CBOR-encoded text string key (header + payload).
|
||||
* Used to implement RFC 7049 canonical map key ordering.
|
||||
*
|
||||
* @param {string} s
|
||||
* @returns {number}
|
||||
*/
|
||||
function cborKeyEncodedLen(s) {
|
||||
const n = Buffer.byteLength(s, "utf8");
|
||||
if (n < 24) return 1 + n;
|
||||
if (n < 0x100) return 2 + n;
|
||||
if (n < 0x10000) return 3 + n;
|
||||
return 5 + n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts plain objects to pre-sorted Maps so that the CBOR
|
||||
* encoder emits keys in canonical (length-first, then lexicographic) order
|
||||
* without relying on the cbor library's own canonical sort, which is broken
|
||||
* in cbor v9 for streamed output.
|
||||
*
|
||||
* @param {*} val
|
||||
* @returns {*}
|
||||
*/
|
||||
function prepareCBOR(val) {
|
||||
if (Array.isArray(val)) return val.map(prepareCBOR);
|
||||
if (val !== null && typeof val === "object" && !(val instanceof Map)) {
|
||||
const sorted = Object.keys(val).sort((a, b) => {
|
||||
const la = cborKeyEncodedLen(a), lb = cborKeyEncodedLen(b);
|
||||
if (la !== lb) return la - lb;
|
||||
return Buffer.from(a, "utf8").compare(Buffer.from(b, "utf8"));
|
||||
});
|
||||
return new Map(sorted.map(k => [k, prepareCBOR(val[k])]));
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a value as canonical CBOR using a streaming Encoder.
|
||||
* Returns a Promise that resolves to a Buffer containing the full encoding.
|
||||
*
|
||||
* @param {*} input
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
function cborEncodeCanonical(input) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const enc = new Cbor.Encoder({canonical: true});
|
||||
enc.addSemanticType(Map, (e, m) => {
|
||||
if (!e._pushInt(m.size, 5)) return false;
|
||||
for (const [k, v] of m) {
|
||||
if (!e.pushAny(k) || !e.pushAny(v)) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const bufs = [];
|
||||
enc.on("data", b => bufs.push(b));
|
||||
enc.on("error", reject);
|
||||
enc.on("finish", () => resolve(Buffer.concat(bufs)));
|
||||
enc.pushAny(prepareCBOR(input));
|
||||
enc.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CBOR Encode operation
|
||||
*/
|
||||
@ -32,8 +99,9 @@ class CBOREncode extends Operation {
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
return new Uint8Array(Cbor.encodeCanonical(input)).buffer;
|
||||
async run(input, args) {
|
||||
const buf = await cborEncodeCanonical(input);
|
||||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
222
src/core/operations/TranslatePINBlockEncrypted.mjs
Normal file
222
src/core/operations/TranslatePINBlockEncrypted.mjs
Normal file
@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
* @author Jacob Marks [https://jacobmarks.com]
|
||||
*/
|
||||
|
||||
import forge from "node-forge";
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { PIN_BLOCK_FORMATS, buildPinBlock, parsePinBlock } from "../lib/PinBlock.mjs";
|
||||
|
||||
// ── Crypto helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Validates and normalises a TDES key hex string.
|
||||
* Accepts 16-byte (2-key) or 24-byte (3-key) TDES.
|
||||
*
|
||||
* @param {string} hex
|
||||
* @param {string} label
|
||||
* @returns {string} normalised uppercase hex, always 24 bytes (48 hex chars)
|
||||
*/
|
||||
function normaliseTdesKey(hex, label) {
|
||||
const h = (hex || "").replace(/\s+/g, "").toUpperCase();
|
||||
if (!/^[0-9A-F]+$/.test(h)) throw new OperationError(`${label} must be hex.`);
|
||||
if (h.length === 32) return h + h.slice(0, 16); // expand 2-key to 3-key
|
||||
if (h.length === 48) return h;
|
||||
throw new OperationError(`${label} must be 16 bytes (32 hex chars) or 24 bytes (48 hex chars).`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a hex string to a forge binary string.
|
||||
*
|
||||
* @param {string} hex
|
||||
* @returns {string}
|
||||
*/
|
||||
function hexToForgeBin(hex) {
|
||||
return forge.util.hexToBytes(hex.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts one 8-byte block with 3DES-ECB.
|
||||
*
|
||||
* @param {string} key48hex 24-byte key as 48 uppercase hex chars
|
||||
* @param {string} block16hex 8-byte block as 16 uppercase hex chars
|
||||
* @returns {string} 16 uppercase hex chars
|
||||
*/
|
||||
function tdesEcbEncrypt(key48hex, block16hex) {
|
||||
const cipher = forge.cipher.createCipher("3DES-ECB", hexToForgeBin(key48hex));
|
||||
cipher.mode.pad = () => true;
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(hexToForgeBin(block16hex)));
|
||||
cipher.finish();
|
||||
return forge.util.bytesToHex(cipher.output.getBytes()).toUpperCase().slice(0, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts one 8-byte block with 3DES-ECB.
|
||||
*
|
||||
* @param {string} key48hex
|
||||
* @param {string} block16hex
|
||||
* @returns {string} 16 uppercase hex chars
|
||||
*/
|
||||
function tdesEcbDecrypt(key48hex, block16hex) {
|
||||
const decipher = forge.cipher.createDecipher("3DES-ECB", hexToForgeBin(key48hex));
|
||||
decipher.mode.pad = () => true;
|
||||
decipher.start();
|
||||
decipher.update(forge.util.createBuffer(hexToForgeBin(block16hex)));
|
||||
decipher.finish();
|
||||
return forge.util.bytesToHex(decipher.output.getBytes()).toUpperCase().slice(0, 16);
|
||||
}
|
||||
|
||||
// ── Operation ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* PIN Block Translate Encrypted operation
|
||||
*/
|
||||
class TranslatePINBlockEncrypted extends Operation {
|
||||
|
||||
/**
|
||||
* TranslatePINBlockEncrypted constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "PIN Block Translate Encrypted";
|
||||
this.module = "Payment";
|
||||
this.description = [
|
||||
"Decrypt an encrypted PIN block under an incoming zone key (ZPK / PEK),",
|
||||
" optionally change the PIN block format, and re-encrypt under an outgoing zone key.",
|
||||
" The clear PIN is never present in the output — only the re-encrypted block is returned.",
|
||||
"<br><br>",
|
||||
"This is the acquirer's core PIN routing operation.",
|
||||
" It corresponds to <code>TranslatePinData</code> in AWS Payment Cryptography and to the",
|
||||
" <code>CA</code> / <code>CC</code> command family on Thales payShield.",
|
||||
"<br><br>",
|
||||
"<b>Input:</b> encrypted PIN block as hex (8 bytes / 16 hex chars).",
|
||||
"<br>",
|
||||
"<b>Key algorithm:</b> TDES — key must be 16 bytes (2-key TDES, 32 hex chars) or",
|
||||
" 24 bytes (3-key TDES, 48 hex chars).",
|
||||
" 2-key input is automatically expanded to 3-key (K3 = K1).",
|
||||
"<br><br>",
|
||||
"Supported formats: ISO Format 0, ISO Format 1, ISO Format 3.",
|
||||
" ISO Format 4 (AES, 16-byte block) is not yet supported.",
|
||||
"<br><br>",
|
||||
"<b>PCI PIN requirement:</b> the cardholder PAN must not change between incoming and",
|
||||
" outgoing formats (PCI PIN Security Req 3-3).",
|
||||
" Supplying a different PAN for the target format is permitted only when the target",
|
||||
" format does not use PAN binding (Format 1).",
|
||||
].join("");
|
||||
this.inlineHelp = [
|
||||
"<strong>Input:</strong> encrypted PIN block hex.",
|
||||
"<strong>Args:</strong> incoming ZPK/PEK, incoming format and PAN;",
|
||||
" outgoing ZPK/PEK, outgoing format and PAN.",
|
||||
].join(" ");
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "TDES ZPK-to-ZPK, same format",
|
||||
input: "7F381DBF9F6906C4",
|
||||
args: ["DDDDEEEEFFFFAAAABBBBCCCCDDDDEEEE", "ISO Format 0", "5432101234567890",
|
||||
"0123456789ABCDEFFEDCBA9876543210", "ISO Format 0", "5432101234567890", false]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://wikipedia.org/wiki/ISO_9564";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Incoming key (TDES hex)",
|
||||
type: "string",
|
||||
value: "",
|
||||
comment: "Zone PIN Key (ZPK) or PIN Encryption Key (PEK) used to decrypt the incoming block. 16 bytes (32 hex) for 2-key TDES or 24 bytes (48 hex) for 3-key TDES."
|
||||
},
|
||||
{
|
||||
name: "Incoming format",
|
||||
type: "option",
|
||||
value: PIN_BLOCK_FORMATS,
|
||||
comment: "ISO 9564 format of the incoming encrypted block."
|
||||
},
|
||||
{
|
||||
name: "Incoming PAN",
|
||||
type: "string",
|
||||
value: "",
|
||||
comment: "Primary account number — required when the incoming format is 0 or 3. The implementation uses the rightmost 12 digits excluding the check digit."
|
||||
},
|
||||
{
|
||||
name: "Outgoing key (TDES hex)",
|
||||
type: "string",
|
||||
value: "",
|
||||
comment: "Zone PIN Key (ZPK) or PIN Encryption Key (PEK) used to encrypt the outgoing block. Same key-length rules as the incoming key."
|
||||
},
|
||||
{
|
||||
name: "Outgoing format",
|
||||
type: "option",
|
||||
value: PIN_BLOCK_FORMATS,
|
||||
comment: "ISO 9564 format of the outgoing encrypted block."
|
||||
},
|
||||
{
|
||||
name: "Outgoing PAN",
|
||||
type: "string",
|
||||
value: "",
|
||||
comment: "Required when the outgoing format is 0 or 3. Per PCI PIN Req 3-3, this must equal the incoming PAN when both formats use PAN binding."
|
||||
},
|
||||
{
|
||||
name: "Output as JSON",
|
||||
type: "boolean",
|
||||
value: false,
|
||||
comment: "When enabled, returns the intermediate values (incoming clear block, outgoing clear block) along with the final encrypted block. Use for debugging only — do not expose clear PIN block values in production."
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inKeyHex, inFormat, inPan, outKeyHex, outFormat, outPan, outputJson] = args;
|
||||
|
||||
const encIn = (input || "").replace(/\s+/g, "").toUpperCase();
|
||||
if (!/^[0-9A-F]{16}$/.test(encIn)) {
|
||||
throw new OperationError("Encrypted PIN block must be 16 hex characters (8 bytes).");
|
||||
}
|
||||
|
||||
const inKey = normaliseTdesKey(inKeyHex, "Incoming key");
|
||||
const outKey = normaliseTdesKey(outKeyHex, "Outgoing key");
|
||||
|
||||
// Decrypt incoming encrypted block → clear PIN block
|
||||
const clearIn = tdesEcbDecrypt(inKey, encIn);
|
||||
|
||||
// Parse the clear block to recover the PIN
|
||||
const parsed = parsePinBlock(inFormat, clearIn, inPan);
|
||||
|
||||
// Re-encode in the target format
|
||||
const clearOut = buildPinBlock(outFormat, parsed.pin, outPan, false);
|
||||
|
||||
// Re-encrypt under the outgoing key
|
||||
const encOut = tdesEcbEncrypt(outKey, clearOut);
|
||||
|
||||
if (outputJson) {
|
||||
return JSON.stringify({
|
||||
incoming: {
|
||||
format: inFormat,
|
||||
pan: inPan || null,
|
||||
encryptedBlockHex: encIn,
|
||||
clearBlockHex: clearIn,
|
||||
},
|
||||
pin: parsed.pin,
|
||||
outgoing: {
|
||||
format: outFormat,
|
||||
pan: outPan || null,
|
||||
clearBlockHex: clearOut,
|
||||
encryptedBlockHex: encOut,
|
||||
},
|
||||
}, null, 4);
|
||||
}
|
||||
|
||||
return encOut;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default TranslatePINBlockEncrypted;
|
||||
@ -136,7 +136,7 @@ Tiger-128`;
|
||||
it("Bcrypt", async () => {
|
||||
const result = await chef.bcrypt("Put a Sock In It");
|
||||
const strResult = result.toString();
|
||||
assert.match(strResult, /^\$2b\$10\$[./A-Za-z0-9]{53}$/);
|
||||
assert.match(strResult, /^\$2[ab]\$10\$[./A-Za-z0-9]{53}$/);
|
||||
assert.equal(strResult.split("$").length, 4);
|
||||
}),
|
||||
|
||||
|
||||
@ -956,7 +956,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "EMV Generate MAC",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", 8, false]
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "Method 2", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1024,7 +1024,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "EMV Verify MAC",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "22CB48394DFD1977", true]
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "22CB48394DFD1977", "Method 2", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1341,5 +1341,91 @@ TestRegister.addTests([
|
||||
args: ["00112233445566778899AABBCCDDEEFF", 8, "000102030405060708090A0B0C0D0E0F", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// ── PIN Block Translate Encrypted ─────────────────────────────────────────
|
||||
// Vectors: PIN=1234, PAN=5432101234567890
|
||||
// clear Format 0 block : 041215FEDCBA9876
|
||||
// ZPK_IN (2-key TDES) : DDDDEEEEFFFFAAAABBBBCCCCDDDDEEEE KCV 06332B
|
||||
// ZPK_OUT (2-key TDES) : AABBCCDDEEFF00112233445566778899 KCV C4F0A4
|
||||
// encrypted under ZPK_IN : 7F381DBF9F6906C4
|
||||
// encrypted under ZPK_OUT : 06C0408B869B2CEB
|
||||
// AWS Payment Cryptography comparison (translate_pin_data, TR31_P0_PIN_ENCRYPTION_KEY):
|
||||
// incoming key ARN: arn:aws:payment-cryptography:us-east-1:030716882260:key/yqictqre4fccxmzn
|
||||
// outgoing key ARN: arn:aws:payment-cryptography:us-east-1:030716882260:key/czgtcqq5cpspwcgk
|
||||
{
|
||||
name: "PIN Block Translate Encrypted: same key / same format (round-trip identity)",
|
||||
input: "7F381DBF9F6906C4",
|
||||
expectedOutput: "7F381DBF9F6906C4",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "PIN Block Translate Encrypted",
|
||||
args: ["DDDDEEEEFFFFAAAABBBBCCCCDDDDEEEE", "ISO Format 0", "5432101234567890",
|
||||
"DDDDEEEEFFFFAAAABBBBCCCCDDDDEEEE", "ISO Format 0", "5432101234567890", false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "PIN Block Translate Encrypted: ZPK-to-ZPK same format",
|
||||
input: "7F381DBF9F6906C4",
|
||||
expectedOutput: "06C0408B869B2CEB",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "PIN Block Translate Encrypted",
|
||||
args: ["DDDDEEEEFFFFAAAABBBBCCCCDDDDEEEE", "ISO Format 0", "5432101234567890",
|
||||
"AABBCCDDEEFF00112233445566778899", "ISO Format 0", "5432101234567890", false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "PIN Block Translate Encrypted: ZPK-to-ZPK Format 0 to Format 1",
|
||||
input: "7F381DBF9F6906C4",
|
||||
expectedOutput: "CAC0E6065A56F5F3",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "PIN Block Translate Encrypted",
|
||||
args: ["DDDDEEEEFFFFAAAABBBBCCCCDDDDEEEE", "ISO Format 0", "5432101234567890",
|
||||
"AABBCCDDEEFF00112233445566778899", "ISO Format 1", "", false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "PIN Block Translate Encrypted: JSON output mode",
|
||||
input: "7F381DBF9F6906C4",
|
||||
expectedOutput: JSON.stringify({
|
||||
incoming: {
|
||||
format: "ISO Format 0",
|
||||
pan: "5432101234567890",
|
||||
encryptedBlockHex: "7F381DBF9F6906C4",
|
||||
clearBlockHex: "041215FEDCBA9876"
|
||||
},
|
||||
pin: "1234",
|
||||
outgoing: {
|
||||
format: "ISO Format 0",
|
||||
pan: "5432101234567890",
|
||||
clearBlockHex: "041215FEDCBA9876",
|
||||
encryptedBlockHex: "06C0408B869B2CEB"
|
||||
}
|
||||
}, null, 4),
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "PIN Block Translate Encrypted",
|
||||
args: ["DDDDEEEEFFFFAAAABBBBCCCCDDDDEEEE", "ISO Format 0", "5432101234567890",
|
||||
"AABBCCDDEEFF00112233445566778899", "ISO Format 0", "5432101234567890", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "PIN Block Translate Encrypted: 3-key TDES (48 hex) accepted",
|
||||
input: "7F381DBF9F6906C4",
|
||||
expectedOutput: "06C0408B869B2CEB",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "PIN Block Translate Encrypted",
|
||||
// 3-key expansion of 2-key keys: K3_IN = K2_IN + K2_IN[0..15], same for OUT
|
||||
args: ["DDDDEEEEFFFFAAAABBBBCCCCDDDDEEEEDDDDEEEEFFFFAAAA", "ISO Format 0", "5432101234567890",
|
||||
"AABBCCDDEEFF00112233445566778899AABBCCDDEEFF0011", "ISO Format 0", "5432101234567890", false]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user