Fix AES DUKPT derivation data format; add X9.24-3 test vectors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e864259c1e
commit
b724fc4b8c
@ -11,7 +11,7 @@ import { toHexFast } from "../lib/Hex.mjs";
|
|||||||
// ── X9.24-3 key usage indicators (bytes 2-3 of derivation data) ───────────────
|
// ── X9.24-3 key usage indicators (bytes 2-3 of derivation data) ───────────────
|
||||||
|
|
||||||
const KEY_USAGE = {
|
const KEY_USAGE = {
|
||||||
"IK Derivation": 0x8000, // BDK → device Initial Key
|
"IK Derivation": 0x8001, // BDK → device Initial Key (X9.24-3 §6.3.1)
|
||||||
Intermediate: 0x0000, // internal binary-tree node (not user-visible)
|
Intermediate: 0x0000, // internal binary-tree node (not user-visible)
|
||||||
"PIN Encryption": 0x1000,
|
"PIN Encryption": 0x1000,
|
||||||
"MAC Generation": 0x2000, // sender / request direction
|
"MAC Generation": 0x2000, // sender / request direction
|
||||||
@ -165,15 +165,42 @@ function aesCmac(key16, message) {
|
|||||||
// ── X9.24-3 AES-128 DUKPT derivation ─────────────────────────────────────────
|
// ── X9.24-3 AES-128 DUKPT derivation ─────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the 20-byte derivation data block (ANSI X9.24-3-2017).
|
* Builds the 16-byte IK derivation data block (ANSI X9.24-3-2017 §6.3.1).
|
||||||
|
*
|
||||||
|
* Layout (IK derivation only — uses full 8-byte IKI, no counter field):
|
||||||
|
* [0] version = 0x01
|
||||||
|
* [1] key size class = 0x01 (AES-128)
|
||||||
|
* [2-3] key usage = 0x8001 (IK Derivation)
|
||||||
|
* [4-5] algorithm = 0x0002 (AES-128)
|
||||||
|
* [6-7] key length = 0x0080 (128 bits)
|
||||||
|
* [8-15] IKI (full 8 bytes)
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} iki8
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
function ikDerivationData(iki8) {
|
||||||
|
const d = new Uint8Array(16);
|
||||||
|
d[0] = 0x01; d[1] = 0x01;
|
||||||
|
d[2] = (KEY_USAGE["IK Derivation"] >> 8) & 0xFF;
|
||||||
|
d[3] = KEY_USAGE["IK Derivation"] & 0xFF;
|
||||||
|
d[4] = (ALGO_CODE >> 8) & 0xFF; d[5] = ALGO_CODE & 0xFF;
|
||||||
|
d[6] = (KEY_LEN_VAL >> 8) & 0xFF; d[7] = KEY_LEN_VAL & 0xFF;
|
||||||
|
d.set(iki8, 8);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the 16-byte derivation data block for intermediate-node and working-key
|
||||||
|
* derivation (ANSI X9.24-3-2017 §6.3.2 / §6.3.3).
|
||||||
*
|
*
|
||||||
* Layout:
|
* Layout:
|
||||||
* [0-1] version = 0x0001
|
* [0] version = 0x01
|
||||||
* [2-3] key usage indicator
|
* [1] key size class = 0x01 (AES-128)
|
||||||
* [4-5] algorithm = 0x0002 (AES-128)
|
* [2-3] key usage indicator
|
||||||
* [6-7] key length = 0x0080 (128 bits)
|
* [4-5] algorithm = 0x0002 (AES-128)
|
||||||
* [8-15] IKI (8 bytes, from KSN bytes 0-7)
|
* [6-7] key length = 0x0080 (128 bits)
|
||||||
* [16-19] counter register (4 bytes)
|
* [8-11] last 4 bytes of IKI (IKI[4..7])
|
||||||
|
* [12-15] counter register (4 bytes, big-endian)
|
||||||
*
|
*
|
||||||
* @param {number} usage
|
* @param {number} usage
|
||||||
* @param {Uint8Array} iki8
|
* @param {Uint8Array} iki8
|
||||||
@ -181,16 +208,17 @@ function aesCmac(key16, message) {
|
|||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
function derivationData(usage, iki8, counterReg) {
|
function derivationData(usage, iki8, counterReg) {
|
||||||
const d = new Uint8Array(20);
|
const d = new Uint8Array(16);
|
||||||
d[0] = 0x00; d[1] = 0x01;
|
d[0] = 0x01; d[1] = 0x01;
|
||||||
d[2] = (usage >> 8) & 0xFF; d[3] = usage & 0xFF;
|
d[2] = (usage >> 8) & 0xFF; d[3] = usage & 0xFF;
|
||||||
d[4] = (ALGO_CODE >> 8) & 0xFF; d[5] = ALGO_CODE & 0xFF;
|
d[4] = (ALGO_CODE >> 8) & 0xFF; d[5] = ALGO_CODE & 0xFF;
|
||||||
d[6] = (KEY_LEN_VAL >> 8) & 0xFF; d[7] = KEY_LEN_VAL & 0xFF;
|
d[6] = (KEY_LEN_VAL >> 8) & 0xFF; d[7] = KEY_LEN_VAL & 0xFF;
|
||||||
d.set(iki8, 8);
|
// last 4 bytes of 8-byte IKI
|
||||||
d[16] = (counterReg >>> 24) & 0xFF;
|
d[8] = iki8[4]; d[9] = iki8[5]; d[10] = iki8[6]; d[11] = iki8[7];
|
||||||
d[17] = (counterReg >>> 16) & 0xFF;
|
d[12] = (counterReg >>> 24) & 0xFF;
|
||||||
d[18] = (counterReg >>> 8) & 0xFF;
|
d[13] = (counterReg >>> 16) & 0xFF;
|
||||||
d[19] = counterReg & 0xFF;
|
d[14] = (counterReg >>> 8) & 0xFF;
|
||||||
|
d[15] = counterReg & 0xFF;
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +230,7 @@ function derivationData(usage, iki8, counterReg) {
|
|||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
function deriveIK(bdk16, iki8) {
|
function deriveIK(bdk16, iki8) {
|
||||||
return aesCmac(bdk16, derivationData(KEY_USAGE["IK Derivation"], iki8, 0));
|
return aesCmac(bdk16, ikDerivationData(iki8));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -235,6 +263,7 @@ function deriveTransactionKey(ik16, iki8, counter) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Derives a purpose-specific working key from the transaction key.
|
* Derives a purpose-specific working key from the transaction key.
|
||||||
|
* Uses the full 32-bit counter in the derivation data (not the 21-bit tree counter).
|
||||||
*
|
*
|
||||||
* @param {Uint8Array} txKey16
|
* @param {Uint8Array} txKey16
|
||||||
* @param {Uint8Array} iki8
|
* @param {Uint8Array} iki8
|
||||||
@ -243,7 +272,7 @@ function deriveTransactionKey(ik16, iki8, counter) {
|
|||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
function deriveWorkingKey(txKey16, iki8, counter, purposeName) {
|
function deriveWorkingKey(txKey16, iki8, counter, purposeName) {
|
||||||
return aesCmac(txKey16, derivationData(KEY_USAGE[purposeName], iki8, counter & 0x1FFFFF));
|
return aesCmac(txKey16, derivationData(KEY_USAGE[purposeName], iki8, counter));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Operation class ───────────────────────────────────────────────────────────
|
// ── Operation class ───────────────────────────────────────────────────────────
|
||||||
@ -269,15 +298,17 @@ class DeriveDUKPTAESKey extends Operation {
|
|||||||
"The <b>KSN</b> is 12 bytes: 8-byte Initial Key Identifier (IKI) + 4-byte transaction counter.",
|
"The <b>KSN</b> is 12 bytes: 8-byte Initial Key Identifier (IKI) + 4-byte transaction counter.",
|
||||||
"Only the low 21 bits of the counter are used for derivation (max 2,097,151 transactions per IK).",
|
"Only the low 21 bits of the counter are used for derivation (max 2,097,151 transactions per IK).",
|
||||||
"<br><br>",
|
"<br><br>",
|
||||||
"<b>Derivation data format (X9.24-3, 20 bytes):</b>",
|
"<b>Derivation data format (X9.24-3, 16 bytes — working keys):</b>",
|
||||||
"<pre>",
|
"<pre>",
|
||||||
"[0-1] version = 0x0001\n",
|
"[0] version = 0x01\n",
|
||||||
|
"[1] key size class = 0x01 (AES-128)\n",
|
||||||
"[2-3] key usage indicator\n",
|
"[2-3] key usage indicator\n",
|
||||||
"[4-5] algorithm = 0x0002 (AES-128)\n",
|
"[4-5] algorithm = 0x0002 (AES-128)\n",
|
||||||
"[6-7] key length = 0x0080 (128 bits)\n",
|
"[6-7] key length = 0x0080 (128 bits)\n",
|
||||||
"[8-15] IKI (8 bytes from KSN)\n",
|
"[8-11] last 4 bytes of IKI\n",
|
||||||
"[16-19] counter register (4 bytes)\n",
|
"[12-15] transaction counter (4 bytes, full 32-bit value)\n",
|
||||||
"</pre>",
|
"</pre>",
|
||||||
|
"IK derivation uses a separate 16-byte block with full 8-byte IKI at [8-15] and usage 0x8001.",
|
||||||
"<b>Key usage codes:</b> PIN Encryption=0x1000, MAC Generation=0x2000, ",
|
"<b>Key usage codes:</b> PIN Encryption=0x1000, MAC Generation=0x2000, ",
|
||||||
"MAC Verification=0x2001, MAC Both Ways=0x2002, ",
|
"MAC Verification=0x2001, MAC Both Ways=0x2002, ",
|
||||||
"Data Encryption=0x3000, Data Decryption=0x3001, Data Both Ways=0x3002.",
|
"Data Encryption=0x3000, Data Decryption=0x3001, Data Both Ways=0x3002.",
|
||||||
|
|||||||
@ -315,6 +315,98 @@ TestRegister.addTests([
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// ── DUKPT Derive AES Key — ANSI X9.24-3-2017 official test vectors ───────
|
||||||
|
// BDK-128: FEDCBA9876543210F1F1F1F1F1F1F1F1
|
||||||
|
// KSN: 1234567890123456 (IKI) + counter
|
||||||
|
// Source: https://x9.org/standards/x9-24-part-3-test-vectors/
|
||||||
|
name: "DUKPT Derive AES Key: IK from BDK (X9.24-3 §6.3.1)",
|
||||||
|
input: "FEDCBA9876543210F1F1F1F1F1F1F1F1",
|
||||||
|
expectedOutput: "1273671EA26AC29AFA4D1084127652A1",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "DUKPT Derive AES Key",
|
||||||
|
args: ["BDK", "Initial Key (IK)", "123456789012345600000001", "PIN Encryption", false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DUKPT Derive AES Key: PIN Encryption key, counter 1 (X9.24-3 §6.3.3)",
|
||||||
|
input: "FEDCBA9876543210F1F1F1F1F1F1F1F1",
|
||||||
|
expectedOutput: "AF8CB133A78F8DC2D1359F18527593FB",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "DUKPT Derive AES Key",
|
||||||
|
args: ["BDK", "Working Key", "123456789012345600000001", "PIN Encryption", false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DUKPT Derive AES Key: MAC Generation key, counter 1",
|
||||||
|
input: "FEDCBA9876543210F1F1F1F1F1F1F1F1",
|
||||||
|
expectedOutput: "A2DC23DE6FDE0824A2BC321E08E4B8B7",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "DUKPT Derive AES Key",
|
||||||
|
args: ["BDK", "Working Key", "123456789012345600000001", "MAC Generation", false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DUKPT Derive AES Key: Data Encryption key, counter 1",
|
||||||
|
input: "FEDCBA9876543210F1F1F1F1F1F1F1F1",
|
||||||
|
expectedOutput: "A35C412EFD41FDB98B69797C02DCD08F",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "DUKPT Derive AES Key",
|
||||||
|
args: ["BDK", "Working Key", "123456789012345600000001", "Data Encryption", false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DUKPT Derive AES Key: PIN Encryption key, counter 8",
|
||||||
|
input: "FEDCBA9876543210F1F1F1F1F1F1F1F1",
|
||||||
|
expectedOutput: "4D9DF3FBEE3448FC3E676D04320A90F5",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "DUKPT Derive AES Key",
|
||||||
|
args: ["BDK", "Working Key", "123456789012345600000008", "PIN Encryption", false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DUKPT Derive AES Key: PIN Encryption key, counter 131072 (0x20000, first skipped-bit counter)",
|
||||||
|
input: "FEDCBA9876543210F1F1F1F1F1F1F1F1",
|
||||||
|
expectedOutput: "AB828BE7B58C7EC5D5ED0D5D320A0C9D",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "DUKPT Derive AES Key",
|
||||||
|
args: ["BDK", "Working Key", "123456789012345600020000", "PIN Encryption", false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DUKPT Derive AES Key: PIN Encryption key, counter 8675309 (0x845FED, midrange)",
|
||||||
|
input: "FEDCBA9876543210F1F1F1F1F1F1F1F1",
|
||||||
|
expectedOutput: "D1DDA386AA4A556AF0119FDCB5D132C6",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "DUKPT Derive AES Key",
|
||||||
|
args: ["BDK", "Working Key", "1234567890123456 00845FED", "PIN Encryption", false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DUKPT Derive AES Key: working key from IK input, counter 1 PIN Encryption",
|
||||||
|
input: "1273671EA26AC29AFA4D1084127652A1",
|
||||||
|
expectedOutput: "AF8CB133A78F8DC2D1359F18527593FB",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "DUKPT Derive AES Key",
|
||||||
|
args: ["Initial Key (IK)", "Working Key", "123456789012345600000001", "PIN Encryption", false]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "DUKPT Derive TDES Key: known IPEK vector",
|
name: "DUKPT Derive TDES Key: known IPEK vector",
|
||||||
input: "0123456789ABCDEFFEDCBA9876543210",
|
input: "0123456789ABCDEFFEDCBA9876543210",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user