Expand payment parity coverage and chaining docs
This commit is contained in:
parent
96a2945638
commit
1f8297643f
@ -1,290 +1,207 @@
|
||||
# AWS Payment Cryptography Recipe Coverage
|
||||
|
||||
This guide maps AWS Payment Cryptography Data Plane operations to CyberChef recipe starters.
|
||||
|
||||
Intent:
|
||||
- This fork is not a certified HSM.
|
||||
- It is intended to emulate HSM-style payment cryptography behavior in software for development, QA, regression, interoperability, and integration testing.
|
||||
- The goal of this guide is therefore twofold:
|
||||
1. document what can already be emulated with the current operation set
|
||||
2. identify which AWS Payment Cryptography use cases should be added next to improve test-harness coverage
|
||||
This guide maps AWS Payment Cryptography Data Plane operations to the current payment-facing CyberChef surface.
|
||||
|
||||
Source baseline:
|
||||
- AWS Payment Cryptography Data Plane API Reference: https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/Welcome.html
|
||||
- AWS Data Plane actions list: https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_Operations.html
|
||||
|
||||
Coverage legend:
|
||||
- `Direct`: CyberChef can reproduce the core cryptographic shape of the AWS operation.
|
||||
- `Partial`: CyberChef can help with preimage assembly, derivation, or one stage of the flow, but not the full AWS behavior.
|
||||
- `Not yet implemented`: This is a valid testing/emulation target for the fork, but the required payment primitives are not implemented yet.
|
||||
- `Direct`: there is a payment-facing operation or straightforward recipe chain for the software-emulation shape of the AWS action
|
||||
- `Chained`: there is no single operation, but the flow is cleanly achievable by chaining existing operations
|
||||
- `Emulated`: there is a dedicated operation, but the inline comments call out simplifications versus AWS or HSM custody semantics
|
||||
|
||||
## Coverage Summary
|
||||
|
||||
| AWS operation | Coverage | Notes |
|
||||
| AWS operation | Coverage | Use |
|
||||
| --- | --- | --- |
|
||||
| `EncryptData` | `Direct` / `Partial` | Direct for AES, TDES, and the implemented DUKPT-TDES wrapper profiles. Partial for EMV-derived encryption and broader AWS attribute coverage. |
|
||||
| `DecryptData` | `Direct` / `Partial` | Direct for AES, TDES, and the implemented DUKPT-TDES wrapper profiles. Partial for EMV-derived decryption and broader AWS attribute coverage. |
|
||||
| `ReEncryptData` | `Direct` / `Partial` | Direct for plain decrypt-then-encrypt workflows, including the implemented payment-facing AES/TDES wrapper flows. Partial for DUKPT re-encryption breadth and AWS-specific metadata handling. |
|
||||
| `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, ARPC, and ARQC verify wrappers 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. |
|
||||
| `VerifyCardValidationData` | `Direct` | Direct for software CVV/CVV2/iCVV verification using the same clear-CVK assumptions as generation. |
|
||||
| `GeneratePinData` | `Partial` | Clear PIN-block wrapper coverage now exists for ISO formats 0, 1, and 3. PVV, IBM3624, and encrypted-generation paths are still missing. |
|
||||
| `TranslatePinData` | `Partial` | Clear PIN-block wrapper coverage now exists for ISO formats 0, 1, and 3. Encrypted PEK/BDK/ECDH translation is still missing. |
|
||||
| `VerifyPinData` | `Partial` | Clear PIN-block verification wrapper exists, but PVV / IBM3624 verification behavior is still missing. |
|
||||
| `GenerateMacEmvPinChange` | `Not yet implemented` | Requires issuer-script PIN-change building blocks. |
|
||||
| `GenerateAs2805KekValidation` | `Not yet implemented` | Requires AS2805-specific KEK-validation primitives. |
|
||||
| `EncryptData` | `Direct` | `Encrypt Payment Data` |
|
||||
| `DecryptData` | `Direct` | `Decrypt Payment Data` |
|
||||
| `ReEncryptData` | `Direct` | `Re-Encrypt Payment Data` |
|
||||
| `GenerateMac` | `Direct` | `Generate Payment MAC` or `Generate EMV MAC` |
|
||||
| `VerifyMac` | `Direct` | `Verify Payment MAC` or `Verify EMV MAC` |
|
||||
| `VerifyAuthRequestCryptogram` | `Direct` | `Verify EMV ARQC` |
|
||||
| `GenerateCardValidationData` | `Direct` | `Generate Card Validation Data` |
|
||||
| `VerifyCardValidationData` | `Direct` | `Verify Card Validation Data` |
|
||||
| `GeneratePinData` | `Direct` / `Chained` | `Generate Payment PIN Data`, `Generate IBM 3624 PIN Offset`, `Generate VISA PVV` |
|
||||
| `TranslatePinData` | `Direct` / `Chained` | `Translate Payment PIN Data` or clear PIN block plus cipher chaining |
|
||||
| `VerifyPinData` | `Direct` | `Verify Payment PIN Data`, `Verify IBM 3624 PIN`, `Verify VISA PVV` |
|
||||
| `TranslateKeyMaterial` | `Chained` | `Derive ECDH Key Material` + wrap/unwrap + TR-31/TR-34 helpers |
|
||||
| `GenerateAs2805KekValidation` | `Emulated` | `Generate AS2805 KEK Validation` |
|
||||
| `GenerateMacEmvPinChange` | `Direct` / `Emulated` | `Generate EMV MAC For PIN Change` |
|
||||
|
||||
## Direct Recipe Starters
|
||||
## AWS `EncryptData`
|
||||
Preferred operation:
|
||||
- `Encrypt Payment Data`
|
||||
|
||||
## 1) AWS `EncryptData`: AES / TDES / RSA
|
||||
Operations:
|
||||
- `AES Encrypt` or `Triple DES Encrypt` or `RSA Encrypt`
|
||||
|
||||
Suggested use:
|
||||
- Paste the AWS `PlainText` hexBinary value into the input field.
|
||||
- Set the operation input mode to `Hex` and output mode to `Hex`.
|
||||
- Paste the key into the key argument using the correct format selector.
|
||||
- Match the AWS algorithm and mode manually in the chosen CyberChef operation.
|
||||
Good chain:
|
||||
- `Derive DUKPT Key` -> `Triple DES Encrypt`
|
||||
- `Derive ECDH Key Material` -> KDF if needed -> `AES Encrypt`
|
||||
|
||||
Notes:
|
||||
- AWS documents `EncryptData` as supporting symmetric `TDES` and `AES`, asymmetric `RSA`, and derived `DUKPT` or `EMV` schemes.
|
||||
- This starter directly covers only the non-derived AES, TDES, and RSA cases.
|
||||
- use the payment wrapper when you want payment terminology in one operation
|
||||
- use the generic ciphers directly when you need fine-grained mode control
|
||||
|
||||
## 2) AWS `EncryptData`: Payment Wrapper
|
||||
Operations:
|
||||
- `Encrypt payment data`
|
||||
## AWS `DecryptData`
|
||||
Preferred operation:
|
||||
- `Decrypt Payment Data`
|
||||
|
||||
Suggested use:
|
||||
- Paste plaintext into the input field as hex.
|
||||
- Choose a payment-facing profile for AES, TDES, or the implemented DUKPT-TDES wrapper modes.
|
||||
- Provide the direct key or BDK plus KSN, and add the IV when required.
|
||||
Good chain:
|
||||
- `Derive DUKPT Key` -> `Triple DES Decrypt`
|
||||
- `Derive ECDH Key Material` -> KDF if needed -> `AES Decrypt`
|
||||
|
||||
## 3) AWS `DecryptData`: AES / TDES / RSA
|
||||
Operations:
|
||||
- `AES Decrypt` or `Triple DES Decrypt` or `RSA Decrypt`
|
||||
## AWS `ReEncryptData`
|
||||
Preferred operation:
|
||||
- `Re-Encrypt Payment Data`
|
||||
|
||||
Suggested use:
|
||||
- Paste the AWS `CipherText` hexBinary value into the input field.
|
||||
- Set the operation input mode to `Hex` and output mode to `Hex` or `Raw`.
|
||||
- Paste the key into the key argument using the correct format selector.
|
||||
- Match the AWS algorithm and mode manually in the chosen CyberChef operation.
|
||||
Good chain:
|
||||
- `Decrypt Payment Data` -> `Encrypt Payment Data`
|
||||
|
||||
## 4) AWS `DecryptData`: Payment Wrapper
|
||||
Operations:
|
||||
- `Decrypt payment data`
|
||||
## AWS `GenerateMac`
|
||||
Preferred operations:
|
||||
- `Generate Payment MAC`
|
||||
- `Generate EMV MAC`
|
||||
|
||||
Suggested use:
|
||||
- Paste ciphertext into the input field as hex.
|
||||
- Choose a payment-facing profile for AES, TDES, or the implemented DUKPT-TDES wrapper modes.
|
||||
- Provide the direct key or BDK plus KSN, and add the IV when required.
|
||||
Current MAC coverage:
|
||||
- HMAC SHA-224 / 256 / 384 / 512
|
||||
- AES-CMAC
|
||||
- TDES-CMAC
|
||||
- ISO 9797-1 Algorithm 1
|
||||
- ISO 9797-1 Algorithm 3
|
||||
- AS2805-4.1
|
||||
- DUKPT TDES-CMAC
|
||||
- DUKPT ISO 9797-1 Algorithm 1
|
||||
- DUKPT ISO 9797-1 Algorithm 3
|
||||
- EMV retail-MAC style generation with a provided session key
|
||||
|
||||
## 5) AWS `ReEncryptData`: Symmetric Rewrap
|
||||
Operations:
|
||||
- `AES Decrypt` or `Triple DES Decrypt`
|
||||
- `AES Encrypt` or `Triple DES Encrypt`
|
||||
Use `Generate EMV MAC` when:
|
||||
- the AWS flow is EMV-session-key based rather than a static or DUKPT MAC key
|
||||
|
||||
Suggested use:
|
||||
- Paste the incoming ciphertext into the input field as hex.
|
||||
- First decrypt with the incoming key and mode.
|
||||
- Then encrypt with the outgoing key and mode.
|
||||
## AWS `VerifyMac`
|
||||
Preferred operations:
|
||||
- `Verify Payment MAC`
|
||||
- `Verify EMV MAC`
|
||||
|
||||
Notes:
|
||||
- This covers the software-visible decrypt-then-encrypt pattern.
|
||||
- It does not model AWS wrapped-key handling or HSM-side key custody.
|
||||
Use the same method, padding rule, and key context as generation.
|
||||
|
||||
## 6) AWS `ReEncryptData`: Payment Wrapper
|
||||
Operations:
|
||||
- `Re-encrypt payment data`
|
||||
|
||||
Suggested use:
|
||||
- Paste source ciphertext into the input field as hex.
|
||||
- Define the source decrypt profile and the target encrypt profile in one operation.
|
||||
- Use this as the payment-facing version of the decrypt-then-encrypt recipe chain.
|
||||
|
||||
## 7) AWS `GenerateMac`: HMAC
|
||||
Operations:
|
||||
- `From Hex`
|
||||
- `HMAC`
|
||||
- `Take bytes`
|
||||
|
||||
Suggested use:
|
||||
- Paste the AWS `MessageData` hexBinary value into the input field.
|
||||
- Run `From Hex`.
|
||||
- Run `HMAC` with the appropriate key and hash function.
|
||||
- If AWS truncates the MAC, use `Take bytes` to keep the leftmost bytes that match `MacLength`.
|
||||
|
||||
## 8) AWS `GenerateMac`: CMAC
|
||||
Operations:
|
||||
- `From Hex`
|
||||
- `CMAC`
|
||||
- `Take bytes`
|
||||
|
||||
Suggested use:
|
||||
- Paste the AWS `MessageData` hexBinary value into the input field.
|
||||
- Run `From Hex`.
|
||||
- Run `CMAC` with `Encryption algorithm` set to `AES` or `Triple DES`.
|
||||
- Use `Take bytes` to match the requested `MacLength` if truncation is required.
|
||||
|
||||
## 9) AWS `VerifyMac`: Recompute And Compare
|
||||
Operations:
|
||||
- `Verify payment MAC`
|
||||
|
||||
Suggested use:
|
||||
- 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.
|
||||
|
||||
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.
|
||||
|
||||
## 10) 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.
|
||||
|
||||
## 11) AWS `GenerateCardValidationData`: CVV / CVV2 / iCVV
|
||||
Operations:
|
||||
- `Generate card validation data`
|
||||
|
||||
Suggested use:
|
||||
- Paste the clear combined CVK pair into the input field as hex.
|
||||
- Choose the profile that matches the AWS card-validation mode you want to emulate.
|
||||
- Provide the PAN, expiry, and service-code context in the argument fields.
|
||||
|
||||
Notes:
|
||||
- This directly covers software generation of CVV/CVV2/iCVV-style values.
|
||||
- Assumption: CVV2 forces service code `000` and iCVV forces `999`.
|
||||
|
||||
## 12) AWS `VerifyCardValidationData`: CVV / CVV2 / iCVV
|
||||
Operations:
|
||||
- `Verify card validation data`
|
||||
|
||||
Suggested use:
|
||||
- Use the same card context as generation, then supply the incoming value in the `Expected value` argument.
|
||||
- The operation recomputes the value and returns structured verification output.
|
||||
|
||||
Notes:
|
||||
- This is intended for software parity and regression checks.
|
||||
- It does not emulate AWS key custody or HSM-side audit semantics.
|
||||
|
||||
## Partial Recipe Starters
|
||||
|
||||
## 13) AWS `EncryptData` / `DecryptData`: DUKPT-Derived Symmetric Flows
|
||||
Operations:
|
||||
- `Derive DUKPT key`
|
||||
- `AES Encrypt` or `AES Decrypt` or `Triple DES Encrypt` or `Triple DES Decrypt`
|
||||
|
||||
Suggested use:
|
||||
- Derive the transaction key from BDK and KSN first.
|
||||
- Feed the derived key into the cipher operation that matches your target algorithm.
|
||||
|
||||
Notes:
|
||||
- This is useful for offline vector work.
|
||||
- It does not claim one-to-one parity with every AWS DUKPT encryption attribute combination.
|
||||
|
||||
## 14) AWS `VerifyAuthRequestCryptogram`: EMV ARQC Check
|
||||
Operations:
|
||||
## AWS `VerifyAuthRequestCryptogram`
|
||||
Preferred operation:
|
||||
- `Verify EMV ARQC`
|
||||
|
||||
Suggested use:
|
||||
- Paste the already-assembled EMV authorization-request preimage into the input field as hex.
|
||||
- Provide the already-derived AES session key and cryptogram length.
|
||||
- Provide the incoming ARQC and let the wrapper recompute and compare it.
|
||||
Good chain:
|
||||
- preassemble the ARQC input block
|
||||
- derive or supply the session key
|
||||
- verify the ARQC
|
||||
|
||||
Notes:
|
||||
- This is only practical when the session key and exact preimage assembly are already known.
|
||||
- It is a good fit for AES-CMAC-based profiles, not a full generic EMV verifier.
|
||||
Important assumption:
|
||||
- current ARQC / ARPC support is the implemented AES-CMAC profile
|
||||
|
||||
## 15) AWS `TranslateKeyMaterial`: ECDH And Wrapped-Key Inspection
|
||||
Operations:
|
||||
- `Derive ECDH key material`
|
||||
## AWS `GenerateCardValidationData`
|
||||
Preferred operation:
|
||||
- `Generate Card Validation Data`
|
||||
|
||||
Profiles:
|
||||
- CVV / CVC
|
||||
- CVV2 / CVC2
|
||||
- iCVV
|
||||
|
||||
## AWS `VerifyCardValidationData`
|
||||
Preferred operation:
|
||||
- `Verify Card Validation Data`
|
||||
|
||||
## AWS `GeneratePinData`
|
||||
Preferred operations:
|
||||
- `Generate Payment PIN Data`
|
||||
- `Generate IBM 3624 PIN Offset`
|
||||
- `Generate VISA PVV`
|
||||
|
||||
Use:
|
||||
- `Generate Payment PIN Data` for clear ISO format `0`, `1`, and `3` PIN blocks
|
||||
- `Generate IBM 3624 PIN Offset` for issuer-host offset workflows
|
||||
- `Generate VISA PVV` for PVV workflows
|
||||
|
||||
Good chains:
|
||||
- clear PIN -> `Generate Payment PIN Data` -> `Encrypt Payment Data`
|
||||
- clear PIN -> `Generate IBM 3624 PIN Offset`
|
||||
- clear PIN -> `Generate VISA PVV`
|
||||
|
||||
## AWS `TranslatePinData`
|
||||
Preferred operation:
|
||||
- `Translate Payment PIN Data`
|
||||
|
||||
Good chains:
|
||||
- `Parse PIN Block` -> inspect -> `Translate PIN Block`
|
||||
- `Decrypt Payment Data` -> `Translate Payment PIN Data` -> `Encrypt Payment Data`
|
||||
|
||||
Important assumption:
|
||||
- the direct wrapper is for clear ISO PIN-block translation
|
||||
- encrypted-key-custody semantics are still emulated by chaining
|
||||
|
||||
## AWS `VerifyPinData`
|
||||
Preferred operations:
|
||||
- `Verify Payment PIN Data`
|
||||
- `Verify IBM 3624 PIN`
|
||||
- `Verify VISA PVV`
|
||||
|
||||
Use:
|
||||
- `Verify Payment PIN Data` for clear ISO PIN blocks
|
||||
- `Verify IBM 3624 PIN` for issuer offset checks
|
||||
- `Verify VISA PVV` for PVV checks
|
||||
|
||||
## AWS `TranslateKeyMaterial`
|
||||
Preferred chain:
|
||||
- `Derive ECDH Key Material`
|
||||
- KDF if needed
|
||||
- `AES Key Wrap` or `AES Key Unwrap`
|
||||
- `Parse TR-31 key block`
|
||||
- `Parse TR-34 B9 envelope`
|
||||
|
||||
Suggested use:
|
||||
- Use `Derive ECDH key material` to reproduce the shared-secret or KDF stage.
|
||||
- Use the TR-31 or TR-34 parsers to inspect the wrapped key containers involved in the exchange.
|
||||
Important assumption:
|
||||
- this is a recipe chain, not a single HSM-like rewrap boundary
|
||||
|
||||
Notes:
|
||||
- This helps with interoperability debugging.
|
||||
- It does not recreate AWS’s HSM-side translate-and-rewrap behavior.
|
||||
## AWS `GenerateAs2805KekValidation`
|
||||
Preferred operation:
|
||||
- `Generate AS2805 KEK Validation`
|
||||
|
||||
## 16) AWS `GenerateMac`: EMV MAC Preimage Review
|
||||
Operations:
|
||||
- `From Hex`
|
||||
- `CMAC`
|
||||
- `Take bytes`
|
||||
Important assumption:
|
||||
- this is an explicit software emulation helper
|
||||
- the operation comments call out that it does not claim exact HSM-side AS2805 node-initialization behavior
|
||||
|
||||
Suggested use:
|
||||
- Use this to validate assembled EMV message blocks and truncation behavior when you already know the scheme profile and session key.
|
||||
## AWS `GenerateMacEmvPinChange`
|
||||
Preferred operation:
|
||||
- `Generate EMV MAC For PIN Change`
|
||||
|
||||
Notes:
|
||||
- AWS documents `GenerateMac` as supporting EMV MAC.
|
||||
- This fork does not yet have a dedicated EMV MAC operation, so this remains a profile-specific starter rather than a generic implementation.
|
||||
Good chain:
|
||||
- build or obtain the encrypted target PIN block
|
||||
- assemble the issuer-script APDU body
|
||||
- generate the PIN-change MAC
|
||||
|
||||
## 17) AWS `GeneratePinData`: Clear PIN Block Wrapper
|
||||
Operations:
|
||||
- `Generate payment PIN data`
|
||||
Important assumption:
|
||||
- the helper expects the new PIN block to already be encrypted
|
||||
|
||||
Suggested use:
|
||||
- Paste the clear PIN into the input field.
|
||||
- Choose ISO format 0, 1, or 3.
|
||||
- Provide the PAN when the selected format requires it.
|
||||
## Common Chains
|
||||
|
||||
Notes:
|
||||
- This is useful for software test harnesses that need deterministic clear PIN-block construction before encryption.
|
||||
- It does not yet implement PVV generation, IBM 3624 offsets, or encrypted AWS response semantics.
|
||||
## A) DUKPT Request MAC
|
||||
- `Generate Payment MAC`
|
||||
|
||||
## 18) AWS `TranslatePinData`: Clear PIN Block Wrapper
|
||||
Operations:
|
||||
- `Translate payment PIN data`
|
||||
Method:
|
||||
- `DUKPT MAC Request CMAC`
|
||||
- or `DUKPT ISO 9797-1 Algorithm 1`
|
||||
- or `DUKPT ISO 9797-1 Algorithm 3`
|
||||
|
||||
Suggested use:
|
||||
- Paste the source clear PIN block into the input field as hex.
|
||||
- Choose the source and target formats.
|
||||
- Provide source and target PAN values where required.
|
||||
## B) EMV Issuer Script MAC
|
||||
- `Generate EMV MAC`
|
||||
- `Verify EMV MAC`
|
||||
|
||||
Notes:
|
||||
- This is a software emulation helper for test-vector work.
|
||||
- It does not yet emulate encrypted HSM-bound translation between PEK, BDK, or ECDH-derived keys.
|
||||
## C) EMV PIN Change
|
||||
- `Generate EMV MAC For PIN Change`
|
||||
|
||||
## 19) AWS `VerifyPinData`: Clear PIN Block Wrapper
|
||||
Operations:
|
||||
- `Verify payment PIN data`
|
||||
## D) Clear PIN To Encrypted PIN Data
|
||||
- `Generate Payment PIN Data`
|
||||
- `Encrypt Payment Data`
|
||||
|
||||
Suggested use:
|
||||
- Paste the clear PIN block into the input field as hex.
|
||||
- Provide the expected clear PIN and let the wrapper decode and compare it.
|
||||
|
||||
Notes:
|
||||
- This is only structural verification today.
|
||||
- It does not yet implement VISA PVV or IBM 3624 verification logic.
|
||||
|
||||
## Not Yet Implemented
|
||||
|
||||
These AWS operations are still valid emulation targets, but do not yet have recipe-equivalent support in this fork:
|
||||
- `GenerateMacEmvPinChange`
|
||||
- `GenerateAs2805KekValidation`
|
||||
|
||||
Why:
|
||||
- They depend on PVV/IBM3624/issuer-script/AS2805-specific payment primitives that are not implemented here.
|
||||
|
||||
## Good Next Additions
|
||||
|
||||
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. 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.
|
||||
## E) ECDH-Based Key Translation Lab Flow
|
||||
- `Derive ECDH Key Material`
|
||||
- `AES Key Unwrap`
|
||||
- `AES Key Wrap`
|
||||
- `Parse TR-31 key block`
|
||||
|
||||
@ -1,139 +1,283 @@
|
||||
# Payment Recipe Starters
|
||||
|
||||
These recipe starters are designed for software-only inspection, validation, and prototyping workflows.
|
||||
These recipe starters are for software-only payment-crypto emulation, inspection, regression tests, and interoperability work.
|
||||
|
||||
For AWS-specific mappings, see `AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md`.
|
||||
For AWS operation mapping, see `AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md`.
|
||||
|
||||
## 1) TR-31 Header Parse
|
||||
## UI Arrangement
|
||||
|
||||
The `Payments` category is arranged in this order:
|
||||
- payment-facing wrappers first
|
||||
- EMV and card-validation flows next
|
||||
- PIN and issuer-verification helpers after that
|
||||
- key-derivation, KCV, and parser utilities next
|
||||
- generic crypto primitives last for chaining
|
||||
|
||||
That keeps common testing tasks near the top without hiding the underlying `HMAC`, `CMAC`, cipher, and key-wrap primitives that some chains still need.
|
||||
|
||||
## 1) Encrypt / Decrypt / Re-Encrypt Payment Data
|
||||
Operations:
|
||||
- `Parse TR-31 key block`
|
||||
- `Encrypt Payment Data`
|
||||
- `Decrypt Payment Data`
|
||||
- `Re-Encrypt Payment Data`
|
||||
|
||||
## 2) TR-34 B9 Envelope Split
|
||||
Use this when:
|
||||
- you want payment-facing names for AES, TDES, or the implemented DUKPT-TDES profiles
|
||||
- you want one operation for decrypt-then-encrypt rewrapping
|
||||
|
||||
Input:
|
||||
- plaintext or ciphertext in the selected input format
|
||||
|
||||
Important assumptions:
|
||||
- current derived-data coverage is AES, TDES, and the implemented DUKPT-TDES profiles
|
||||
- this is software emulation and does not model AWS key ARNs or HSM custody
|
||||
|
||||
## 2) Generate / Verify Payment MAC
|
||||
Operations:
|
||||
- `Parse TR-34 B9 envelope`
|
||||
- `Generate Payment MAC`
|
||||
- `Verify Payment MAC`
|
||||
|
||||
## 3) KCV Validation
|
||||
Supported methods:
|
||||
- `HMAC SHA-224`
|
||||
- `HMAC SHA-256`
|
||||
- `HMAC SHA-384`
|
||||
- `HMAC SHA-512`
|
||||
- `AES-CMAC`
|
||||
- `TDES-CMAC`
|
||||
- `ISO 9797-1 Algorithm 1`
|
||||
- `ISO 9797-1 Algorithm 3`
|
||||
- `AS2805-4.1`
|
||||
- `DUKPT MAC Request CMAC`
|
||||
- `DUKPT MAC Response CMAC`
|
||||
- `DUKPT ISO 9797-1 Algorithm 1`
|
||||
- `DUKPT ISO 9797-1 Algorithm 3`
|
||||
|
||||
Use this when:
|
||||
- you want one payment-facing MAC surface instead of deciding between generic `HMAC`, `CMAC`, ISO9797, DUKPT, and AS2805 yourself
|
||||
|
||||
Input:
|
||||
- message data in the selected input format
|
||||
|
||||
Important assumptions:
|
||||
- ISO9797 and AS2805 methods use clear TDES keys in software
|
||||
- DUKPT methods expect a clear BDK plus full KSN
|
||||
- EMV MAC is handled by the dedicated EMV MAC operations below
|
||||
|
||||
## 3) Generate / Verify EMV MAC
|
||||
Operations:
|
||||
- `Calculate payment KCV`
|
||||
- `Generate EMV MAC`
|
||||
- `Verify EMV MAC`
|
||||
- `Generate EMV MAC For PIN Change`
|
||||
|
||||
## 4) ECDH Key Agreement (Software)
|
||||
Operations:
|
||||
- `Derive ECDH key material`
|
||||
Use this when:
|
||||
- you already have the EMV session integrity key
|
||||
- you want issuer-script MAC generation or verification
|
||||
- you need a dedicated offline PIN-change MAC helper
|
||||
|
||||
Suggested use:
|
||||
- Import a local private key and peer public key.
|
||||
- Derive raw shared secret or run Concat KDF (`SHA-256` or `SHA-512`) with shared-info.
|
||||
Input:
|
||||
- issuer-script or EMV command payload as hex
|
||||
|
||||
## 5) DUKPT Derivation (Software)
|
||||
Operations:
|
||||
- `Derive DUKPT key`
|
||||
Important assumptions:
|
||||
- these operations do not derive EMV session keys
|
||||
- they apply retail-MAC style EMV MAC generation with ISO9797 padding method 2
|
||||
- `Generate EMV MAC For PIN Change` expects the new PIN block to already be encrypted before you call it
|
||||
|
||||
Suggested use:
|
||||
- Derive IPEK from BDK + KSN.
|
||||
- Derive base session key and apply a variant mask (`PIN`, `MAC Request`, `MAC Response`, `Data`).
|
||||
|
||||
## 6) Payment Data Encrypt / Decrypt / Re-encrypt
|
||||
Operations:
|
||||
- `Encrypt payment data`
|
||||
- `Decrypt payment data`
|
||||
- `Re-encrypt payment data`
|
||||
|
||||
Suggested use:
|
||||
- Paste the message or ciphertext into the input field as hex.
|
||||
- Choose a payment-facing cipher profile for AES, TDES, or DUKPT-derived TDES.
|
||||
- Provide the direct key or BDK plus KSN, then add the IV when the selected mode requires one.
|
||||
|
||||
Scope note:
|
||||
- These wrappers currently cover AES CBC/CTR/ECB, TDES CBC/ECB, and DUKPT-derived TDES CBC/ECB.
|
||||
- They are intended for software test harnesses and intentionally reuse the existing generic cipher implementations underneath.
|
||||
|
||||
## 7) PIN Block Build / Parse / Translate
|
||||
Operations:
|
||||
- `Build PIN block`
|
||||
- `Parse PIN block`
|
||||
- `Translate PIN block`
|
||||
|
||||
Suggested use:
|
||||
- Build clear ISO 9564 format 0, 1, or 3 PIN blocks from a PIN and PAN.
|
||||
- Parse clear test PIN blocks back into PIN, PIN field, PAN field, and filler details.
|
||||
- Translate clear test PIN blocks between supported formats before feeding them into cipher steps.
|
||||
|
||||
Scope note:
|
||||
- This starter currently covers clear software test blocks for ISO formats 0, 1, and 3.
|
||||
- It does not yet generate PVV, IBM 3624 offsets, or encrypted PEK/BDK translation flows by itself.
|
||||
|
||||
## 8) Payment PIN Data Wrappers
|
||||
Operations:
|
||||
- `Generate payment PIN data`
|
||||
- `Translate payment PIN data`
|
||||
- `Verify payment PIN data`
|
||||
|
||||
Suggested use:
|
||||
- Use these wrappers when you want AWS-style PIN-data naming instead of the lower-level PIN-block operations.
|
||||
- They currently cover clear ISO 9564 formats 0, 1, and 3 by delegating to the existing build, translate, and parse operations.
|
||||
|
||||
Scope note:
|
||||
- This is still clear-PIN-block coverage only.
|
||||
- Encrypted PIN data, PVV, and IBM 3624 are still future additions.
|
||||
|
||||
## 9) Card Validation Data (CVV / CVV2 / iCVV)
|
||||
Operations:
|
||||
- `Generate card validation data`
|
||||
- `Verify card validation data`
|
||||
|
||||
Suggested use:
|
||||
- Paste the combined CVK pair into the input field as 16-byte or 24-byte hex.
|
||||
- Choose whether you want CVV/CVC, CVV2/CVC2, or iCVV behavior.
|
||||
- Provide the PAN, expiry month/year, and service-code context in the argument fields.
|
||||
|
||||
Scope note:
|
||||
- This implementation is intended for software test harnesses.
|
||||
- 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.
|
||||
|
||||
## 10) 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.
|
||||
|
||||
## 11) EMV ARQC Generation And Verification (AES-CMAC Profile)
|
||||
## 4) Generate / Verify EMV ARQC And ARPC
|
||||
Operations:
|
||||
- `Generate EMV ARQC`
|
||||
- `Verify EMV ARQC`
|
||||
|
||||
Suggested use:
|
||||
- Paste the already-assembled ARQC input block into the input field as hex.
|
||||
- Provide the already-derived AES session key in the argument field.
|
||||
- Choose how many leftmost CMAC bytes to keep as the final cryptogram.
|
||||
|
||||
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.
|
||||
|
||||
## 12) EMV ARPC Generation (AES-CMAC Response Profile)
|
||||
Operations:
|
||||
- `Generate EMV ARPC`
|
||||
|
||||
Suggested use:
|
||||
- Paste the already-assembled ARPC response input block into the input field as hex.
|
||||
- Provide the already-derived issuer AES session key in the argument field.
|
||||
- Choose how many leftmost CMAC bytes to keep as the final cryptogram.
|
||||
Use this when:
|
||||
- you already know the exact preassembled EMV data block
|
||||
- you already have the derived EMV session key
|
||||
|
||||
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.
|
||||
Input:
|
||||
- preassembled EMV cryptogram input data as hex
|
||||
|
||||
## 13) Combined Message Triage
|
||||
Important assumptions:
|
||||
- current coverage is the implemented AES-CMAC profile
|
||||
- these operations do not assemble CDOL data or derive issuer/session keys
|
||||
|
||||
## 5) Generate / Verify Card Validation Data
|
||||
Operations:
|
||||
- `Generate Card Validation Data`
|
||||
- `Verify Card Validation Data`
|
||||
|
||||
Profiles:
|
||||
- `CVV / CVC (use service code arg)`
|
||||
- `CVV2 / CVC2 (force 000)`
|
||||
- `iCVV (force 999)`
|
||||
|
||||
Input:
|
||||
- combined CVK pair as clear hex
|
||||
|
||||
Important assumptions:
|
||||
- CVV2 forces service code `000`
|
||||
- iCVV forces service code `999`
|
||||
- this is a clear-key software emulation of common card-validation flows
|
||||
|
||||
## 6) Generate / Translate / Verify Payment PIN Data
|
||||
Operations:
|
||||
- `Generate Payment PIN Data`
|
||||
- `Translate Payment PIN Data`
|
||||
- `Verify Payment PIN Data`
|
||||
|
||||
Use this when:
|
||||
- you want AWS-style PIN-data naming for clear ISO 9564 block flows
|
||||
|
||||
Input:
|
||||
- `Generate Payment PIN Data`: clear PIN digits
|
||||
- `Translate Payment PIN Data`: clear PIN block hex
|
||||
- `Verify Payment PIN Data`: clear PIN block hex
|
||||
|
||||
Important assumptions:
|
||||
- these wrappers currently cover clear ISO formats `0`, `1`, and `3`
|
||||
- encrypted PEK/BDK translation is still done by chaining lower-level steps
|
||||
|
||||
## 7) Build / Parse / Translate PIN Block
|
||||
Operations:
|
||||
- `Build PIN Block`
|
||||
- `Parse PIN Block`
|
||||
- `Translate PIN Block`
|
||||
|
||||
Use this when:
|
||||
- you want the lower-level clear PIN-block tools directly
|
||||
|
||||
Input:
|
||||
- `Build PIN Block`: clear PIN digits
|
||||
- `Parse PIN Block`: clear PIN block hex
|
||||
- `Translate PIN Block`: clear PIN block hex
|
||||
|
||||
Important assumptions:
|
||||
- current clear-block support is ISO formats `0`, `1`, and `3`
|
||||
|
||||
## 8) Issuer PIN Verification Helpers
|
||||
Operations:
|
||||
- `Generate IBM 3624 PIN Offset`
|
||||
- `Verify IBM 3624 PIN`
|
||||
- `Generate VISA PVV`
|
||||
- `Verify VISA PVV`
|
||||
|
||||
Use this when:
|
||||
- you need issuer-side PIN verification artifacts rather than PIN blocks
|
||||
|
||||
Input:
|
||||
- clear PIN digits
|
||||
|
||||
Important assumptions:
|
||||
- these helpers use clear PVKs in software
|
||||
- IBM 3624 expects a decimalization table and validation data
|
||||
- VISA PVV uses the common PAN/PVKI/PIN assembly described in the inline comments
|
||||
|
||||
## 9) Key Derivation And Validation
|
||||
Operations:
|
||||
- `Derive DUKPT Key`
|
||||
- `Derive ECDH Key Material`
|
||||
- `Calculate Payment KCV`
|
||||
- `Generate AS2805 KEK Validation`
|
||||
|
||||
Use this when:
|
||||
- you need transaction keys, shared secrets, KCVs, or AS2805-style KEK-validation lab values
|
||||
|
||||
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
|
||||
Operations:
|
||||
- `Parse TR-31 key block`
|
||||
- `Parse TR-34 B9 envelope`
|
||||
- `Parse ASN.1 hex string`
|
||||
|
||||
Use this when:
|
||||
- you need to inspect inbound 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
|
||||
|
||||
## Chaining Patterns
|
||||
|
||||
## A) DUKPT MAC
|
||||
Operations:
|
||||
- `Derive DUKPT Key`
|
||||
- `Generate Payment MAC`
|
||||
|
||||
Flow:
|
||||
- derive the transaction key first if you want to inspect it
|
||||
- or use a DUKPT MAC method directly in `Generate Payment MAC`
|
||||
- use the same KSN and BDK on verify
|
||||
|
||||
## B) ECDH Wrap / Unwrap
|
||||
Operations:
|
||||
- `Derive ECDH Key Material`
|
||||
- `AES Key Wrap`
|
||||
- `AES Key Unwrap`
|
||||
|
||||
Flow:
|
||||
- derive the shared secret
|
||||
- optionally run a KDF if you need a specific KEK size
|
||||
- feed the resulting key into `AES Key Wrap` or `AES Key Unwrap`
|
||||
|
||||
Important assumption:
|
||||
- this is not a full TR-34 or AWS `TranslateKeyMaterial` implementation by itself
|
||||
|
||||
## C) Clear PIN Block To Encrypted PIN Data
|
||||
Operations:
|
||||
- `Generate Payment PIN Data` or `Build PIN Block`
|
||||
- `Encrypt Payment Data`
|
||||
|
||||
Flow:
|
||||
- generate the clear ISO PIN block first
|
||||
- encrypt that block under the desired AES or TDES profile
|
||||
|
||||
## D) Re-Encrypt Payment Data
|
||||
Operations:
|
||||
- `Re-Encrypt Payment Data`
|
||||
|
||||
Flow:
|
||||
- define the source decrypt profile
|
||||
- define the target encrypt profile
|
||||
- keep the payload in hex end to end
|
||||
|
||||
## E) EMV ARQC / ARPC Review
|
||||
Operations:
|
||||
- `Generate EMV ARQC`
|
||||
- `Verify EMV ARQC`
|
||||
- `Generate EMV ARPC`
|
||||
|
||||
Flow:
|
||||
- build the exact request-data preimage outside the op
|
||||
- generate or verify the ARQC with the derived session key
|
||||
- build the response preimage and generate the ARPC
|
||||
|
||||
## F) EMV Script MAC And PIN Change
|
||||
Operations:
|
||||
- `Generate EMV MAC`
|
||||
- `Verify EMV MAC`
|
||||
- `Generate EMV MAC For PIN Change`
|
||||
|
||||
Flow:
|
||||
- assemble the issuer-script APDU body as hex
|
||||
- use the derived integrity key
|
||||
- append the already-encrypted PIN block when generating the PIN-change MAC
|
||||
|
||||
## G) IBM 3624 / PVV Verification
|
||||
Operations:
|
||||
- `Generate IBM 3624 PIN Offset`
|
||||
- `Verify IBM 3624 PIN`
|
||||
- `Generate VISA PVV`
|
||||
- `Verify VISA PVV`
|
||||
|
||||
Flow:
|
||||
- keep the clear PIN in the input field
|
||||
- keep issuer validation data, PAN, PVKI, decimalization table, and PVK in the args
|
||||
- use the JSON output when you need to inspect how the verification artifact was assembled
|
||||
|
||||
## H) AS2805 KEK Validation
|
||||
Operations:
|
||||
- `Generate AS2805 KEK Validation`
|
||||
- `Calculate Payment KCV`
|
||||
|
||||
Flow:
|
||||
- inspect the KEK with `Calculate Payment KCV`
|
||||
- generate request or response RandomKeySend / RandomKeyReceive values with the AS2805 helper
|
||||
|
||||
@ -11,6 +11,7 @@ This list targets software-only development and testing environments.
|
||||
1. Header mutation recipes (usage, mode, exportability, optional block counts).
|
||||
2. Optional-block truncation and malformed-length negative tests.
|
||||
3. Prefix-normalization recipes (`R` prefix handling).
|
||||
4. Create TR-31 key block recipes for symmetric test keys and round-trip parse validation.
|
||||
|
||||
## TR-34 Simulation
|
||||
1. Envelope section split/rebuild recipes.
|
||||
@ -43,8 +44,8 @@ This list targets software-only development and testing environments.
|
||||
## AWS Payment Cryptography Candidate Recipes
|
||||
1. `EncryptData` and `DecryptData` parity vectors for AES, TDES, and RSA.
|
||||
2. `ReEncryptData` parity vectors for decrypt-then-encrypt workflows.
|
||||
3. `GenerateMac` and `VerifyMac` parity vectors for HMAC and CMAC.
|
||||
4. `VerifyAuthRequestCryptogram` preimage-validation recipes for AES-CMAC EMV profiles.
|
||||
3. `GenerateMac` and `VerifyMac` parity vectors across HMAC, CMAC, ISO9797, DUKPT, AS2805, and EMV MAC profiles.
|
||||
4. `VerifyAuthRequestCryptogram` preimage-validation recipes for the implemented AES-CMAC EMV profiles.
|
||||
5. DUKPT derivation-plus-cipher recipes for AWS derived-key lab testing.
|
||||
6. ECDH and TR-31 inspection recipes for `TranslateKeyMaterial` interoperability debugging.
|
||||
7. Gap-tracking recipes for unsupported AWS flows: PVV, IBM3624, encrypted PIN block translation, issuer-script PIN change, and AS2805 KEK validation.
|
||||
6. ECDH plus wrap/unwrap plus TR-31 inspection recipes for `TranslateKeyMaterial` interoperability debugging.
|
||||
7. Remaining gap-tracking recipes for encrypted PIN translation, richer EMV session derivation, and fuller TR-31/TR-34 generation flows.
|
||||
|
||||
@ -568,6 +568,35 @@
|
||||
{
|
||||
"name": "Payments",
|
||||
"ops": [
|
||||
"Encrypt Payment Data",
|
||||
"Decrypt Payment Data",
|
||||
"Re-Encrypt Payment Data",
|
||||
"Generate Payment MAC",
|
||||
"Verify Payment MAC",
|
||||
"Generate EMV MAC",
|
||||
"Verify EMV MAC",
|
||||
"Generate EMV ARQC",
|
||||
"Verify EMV ARQC",
|
||||
"Generate EMV ARPC",
|
||||
"Generate EMV MAC For PIN Change",
|
||||
"Generate Card Validation Data",
|
||||
"Verify Card Validation Data",
|
||||
"Generate Payment PIN Data",
|
||||
"Translate Payment PIN Data",
|
||||
"Verify Payment PIN Data",
|
||||
"Build PIN Block",
|
||||
"Parse PIN Block",
|
||||
"Translate PIN Block",
|
||||
"Generate IBM 3624 PIN Offset",
|
||||
"Verify IBM 3624 PIN",
|
||||
"Generate VISA PVV",
|
||||
"Verify VISA PVV",
|
||||
"Derive DUKPT Key",
|
||||
"Derive ECDH Key Material",
|
||||
"Calculate Payment KCV",
|
||||
"Generate AS2805 KEK Validation",
|
||||
"Parse TR-31 key block",
|
||||
"Parse TR-34 B9 envelope",
|
||||
"HMAC",
|
||||
"CMAC",
|
||||
"AES Encrypt",
|
||||
@ -575,28 +604,7 @@
|
||||
"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",
|
||||
"Encrypt Payment Data",
|
||||
"Decrypt Payment Data",
|
||||
"Re-Encrypt Payment Data",
|
||||
"Generate Payment MAC",
|
||||
"Verify Payment MAC",
|
||||
"Generate Card Validation Data",
|
||||
"Verify Card Validation Data",
|
||||
"Generate EMV ARQC",
|
||||
"Generate EMV ARPC",
|
||||
"Verify EMV ARQC",
|
||||
"Build PIN Block",
|
||||
"Parse PIN Block",
|
||||
"Translate PIN Block",
|
||||
"Generate Payment PIN Data",
|
||||
"Translate Payment PIN Data",
|
||||
"Verify Payment PIN Data"
|
||||
"AES Key Unwrap"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import forge from "node-forge";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { bytesToHex, parseHexBytes, toByteString } from "./PaymentUtils.mjs";
|
||||
import { bytesToHex, parseHexBytes } from "./PaymentUtils.mjs";
|
||||
import { encryptDesEcb, encryptTdesEcb } from "./CardValidationInternals.mjs";
|
||||
|
||||
const CVV_PROFILES = [
|
||||
"CVV / CVC (use service code arg)",
|
||||
@ -55,45 +55,6 @@ function resolveServiceCode(profile, serviceCode) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encrypts one 8-byte block with DES ECB.
|
||||
*
|
||||
* @param {Uint8Array} key8
|
||||
* @param {Uint8Array} block8
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function encryptDesEcb(key8, block8) {
|
||||
const cipher = forge.cipher.createCipher("DES-ECB", toByteString(key8));
|
||||
cipher.mode.pad = function() {
|
||||
return true;
|
||||
};
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(toByteString(block8)));
|
||||
cipher.finish();
|
||||
return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encrypts one 8-byte block with 3DES ECB.
|
||||
*
|
||||
* @param {Uint8Array} key
|
||||
* @param {Uint8Array} block8
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function encryptTdesEcb(key, block8) {
|
||||
const normalizedKey = key.length === 16 ? Uint8Array.from([...key, ...key.slice(0, 8)]) : key;
|
||||
const cipher = forge.cipher.createCipher("3DES-ECB", toByteString(normalizedKey));
|
||||
cipher.mode.pad = function() {
|
||||
return true;
|
||||
};
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(toByteString(block8)));
|
||||
cipher.finish();
|
||||
return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* XORs two byte arrays.
|
||||
*
|
||||
|
||||
48
src/core/lib/CardValidationInternals.mjs
Normal file
48
src/core/lib/CardValidationInternals.mjs
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import forge from "node-forge";
|
||||
import { toByteString } from "./PaymentUtils.mjs";
|
||||
|
||||
/**
|
||||
* Encrypts one 8-byte block with DES ECB.
|
||||
*
|
||||
* @param {Uint8Array} key8
|
||||
* @param {Uint8Array} block8
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function encryptDesEcb(key8, block8) {
|
||||
const cipher = forge.cipher.createCipher("DES-ECB", toByteString(key8));
|
||||
cipher.mode.pad = function() {
|
||||
return true;
|
||||
};
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(toByteString(block8)));
|
||||
cipher.finish();
|
||||
return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts one 8-byte block with 3DES ECB.
|
||||
*
|
||||
* @param {Uint8Array} key
|
||||
* @param {Uint8Array} block8
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function encryptTdesEcb(key, block8) {
|
||||
const normalizedKey = key.length === 16 ? Uint8Array.from([...key, ...key.slice(0, 8)]) : key;
|
||||
const cipher = forge.cipher.createCipher("3DES-ECB", toByteString(normalizedKey));
|
||||
cipher.mode.pad = function() {
|
||||
return true;
|
||||
};
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(toByteString(block8)));
|
||||
cipher.finish();
|
||||
return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8);
|
||||
}
|
||||
|
||||
export {
|
||||
encryptDesEcb,
|
||||
encryptTdesEcb,
|
||||
};
|
||||
79
src/core/lib/EmvMac.mjs
Normal file
79
src/core/lib/EmvMac.mjs
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { generateIso9797Algorithm3Mac } from "./Iso9797.mjs";
|
||||
|
||||
/**
|
||||
* Generates an EMV MAC using an already-derived session key.
|
||||
*
|
||||
* @param {string} messageHex
|
||||
* @param {string} sessionKeyHex
|
||||
* @param {number} outputBytes
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generateEmvMac(messageHex, sessionKeyHex, outputBytes=8) {
|
||||
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),
|
||||
algorithm: "EMV MAC"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an EMV MAC using an already-derived session key.
|
||||
*
|
||||
* @param {string} messageHex
|
||||
* @param {string} sessionKeyHex
|
||||
* @param {string} expectedMac
|
||||
* @returns {Object}
|
||||
*/
|
||||
function verifyEmvMac(messageHex, sessionKeyHex, 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 = generateEmvMac(messageHex, sessionKeyHex, normalizedExpected.length / 2);
|
||||
return {
|
||||
...generated,
|
||||
expectedMacHex: normalizedExpected,
|
||||
valid: generated.macHex === normalizedExpected
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the MAC portion of an EMV PIN-change issuer script.
|
||||
*
|
||||
* @param {string} messageHex
|
||||
* @param {string} encryptedPinBlockHex
|
||||
* @param {string} sessionKeyHex
|
||||
* @param {number} outputBytes
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generateEmvPinChangeMac(messageHex, encryptedPinBlockHex, sessionKeyHex, outputBytes=8) {
|
||||
const normalizedPinBlock = (encryptedPinBlockHex || "").replace(/\s+/g, "").toUpperCase();
|
||||
if (!/^[0-9A-F]{16,32}$/.test(normalizedPinBlock)) {
|
||||
throw new OperationError("New encrypted PIN block must be 8 or 16 bytes of hex.");
|
||||
}
|
||||
|
||||
const combinedMessageHex = `${(messageHex || "").replace(/\s+/g, "").toUpperCase()}${normalizedPinBlock}`;
|
||||
const generated = generateEmvMac(combinedMessageHex, sessionKeyHex, outputBytes);
|
||||
return {
|
||||
...generated,
|
||||
originalMessageHex: (messageHex || "").replace(/\s+/g, "").toUpperCase(),
|
||||
appendedEncryptedPinBlockHex: normalizedPinBlock,
|
||||
issuerScriptHex: combinedMessageHex
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
generateEmvMac,
|
||||
generateEmvPinChangeMac,
|
||||
verifyEmvMac,
|
||||
};
|
||||
214
src/core/lib/Iso9797.mjs
Normal file
214
src/core/lib/Iso9797.mjs
Normal file
@ -0,0 +1,214 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import forge from "node-forge";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { bytesToHex, parseHexBytes, toByteString } from "./PaymentUtils.mjs";
|
||||
|
||||
const ISO9797_PADDING_METHODS = ["Method 1", "Method 2"];
|
||||
|
||||
/**
|
||||
* XORs two byte arrays of equal length.
|
||||
*
|
||||
* @param {Uint8Array} left
|
||||
* @param {Uint8Array} right
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function xorBytes(left, right) {
|
||||
const out = new Uint8Array(left.length);
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
out[i] = left[i] ^ right[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads input according to ISO/IEC 9797-1 padding method 1 or 2.
|
||||
*
|
||||
* @param {Uint8Array} data
|
||||
* @param {number} blockSize
|
||||
* @param {string} paddingMethod
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function applyIso9797Padding(data, blockSize, paddingMethod) {
|
||||
if (!ISO9797_PADDING_METHODS.includes(paddingMethod)) {
|
||||
throw new OperationError("Unsupported ISO9797 padding method.");
|
||||
}
|
||||
|
||||
if (paddingMethod === "Method 1") {
|
||||
const remainder = data.length % blockSize;
|
||||
if (remainder === 0) return Uint8Array.from(data);
|
||||
const out = new Uint8Array(data.length + (blockSize - remainder));
|
||||
out.set(data, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
const remainder = data.length % blockSize;
|
||||
const extra = remainder === 0 ? blockSize : blockSize - remainder;
|
||||
const out = new Uint8Array(data.length + extra);
|
||||
out.set(data, 0);
|
||||
out[data.length] = 0x80;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts one 8-byte block with DES ECB.
|
||||
*
|
||||
* @param {Uint8Array} key8
|
||||
* @param {Uint8Array} block8
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function encryptDesBlock(key8, block8) {
|
||||
const cipher = forge.cipher.createCipher("DES-ECB", toByteString(key8));
|
||||
cipher.mode.pad = function() {
|
||||
return true;
|
||||
};
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(toByteString(block8)));
|
||||
cipher.finish();
|
||||
return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts one 8-byte block with DES ECB.
|
||||
*
|
||||
* @param {Uint8Array} key8
|
||||
* @param {Uint8Array} block8
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function decryptDesBlock(key8, block8) {
|
||||
const decipher = forge.cipher.createDecipher("DES-ECB", toByteString(key8));
|
||||
decipher.mode.unpad = function() {
|
||||
return true;
|
||||
};
|
||||
decipher.start();
|
||||
decipher.update(forge.util.createBuffer(toByteString(block8)));
|
||||
decipher.finish();
|
||||
return Uint8Array.from(decipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts one 8-byte block with TDES ECB.
|
||||
*
|
||||
* @param {Uint8Array} key
|
||||
* @param {Uint8Array} block8
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function encryptTdesBlock(key, block8) {
|
||||
const normalizedKey = key.length === 16 ? Uint8Array.from([...key, ...key.slice(0, 8)]) : key;
|
||||
const cipher = forge.cipher.createCipher("3DES-ECB", toByteString(normalizedKey));
|
||||
cipher.mode.pad = function() {
|
||||
return true;
|
||||
};
|
||||
cipher.start();
|
||||
cipher.update(forge.util.createBuffer(toByteString(block8)));
|
||||
cipher.finish();
|
||||
return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts blocks with DES CBC-MAC style chaining.
|
||||
*
|
||||
* @param {Uint8Array} key8
|
||||
* @param {Uint8Array} padded
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function runDesCbcMac(key8, padded) {
|
||||
let state = new Uint8Array(8);
|
||||
for (let i = 0; i < padded.length; i += 8) {
|
||||
const block = padded.slice(i, i + 8);
|
||||
state = encryptDesBlock(key8, xorBytes(state, block));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a MAC key for ISO9797-style MACs.
|
||||
*
|
||||
* @param {string} keyHex
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function normalizeIso9797Key(keyHex) {
|
||||
return parseHexBytes(keyHex, "MAC key", [16, 24]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ISO9797 algorithm 1 MAC.
|
||||
*
|
||||
* @param {string} inputHex
|
||||
* @param {string} keyHex
|
||||
* @param {string} paddingMethod
|
||||
* @param {number} outputBytes
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generateIso9797Algorithm1Mac(inputHex, keyHex, paddingMethod, outputBytes=8) {
|
||||
const data = parseHexBytes(inputHex, "Input data");
|
||||
const key = normalizeIso9797Key(keyHex);
|
||||
const padded = applyIso9797Padding(data, 8, paddingMethod);
|
||||
const fullMacBytes = encryptTdesBlock(key, runDesCbcMac(key.slice(0, 8), padded));
|
||||
const fullMacHex = bytesToHex(fullMacBytes);
|
||||
const macHex = fullMacHex.substring(0, Math.max(1, Math.min(8, Number(outputBytes) || 8)) * 2);
|
||||
|
||||
return {
|
||||
algorithm: "ISO 9797-1 Algorithm 1",
|
||||
paddingMethod,
|
||||
inputHex: bytesToHex(data),
|
||||
fullMacHex,
|
||||
macHex,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an ISO9797 algorithm 3 retail MAC.
|
||||
*
|
||||
* @param {string} inputHex
|
||||
* @param {string} keyHex
|
||||
* @param {string} paddingMethod
|
||||
* @param {number} outputBytes
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generateIso9797Algorithm3Mac(inputHex, keyHex, paddingMethod, outputBytes=8) {
|
||||
const data = parseHexBytes(inputHex, "Input data");
|
||||
const key = normalizeIso9797Key(keyHex);
|
||||
const padded = applyIso9797Padding(data, 8, paddingMethod);
|
||||
const key1 = key.slice(0, 8);
|
||||
const key2 = key.slice(8, 16);
|
||||
const key3 = key.length === 24 ? key.slice(16, 24) : key1;
|
||||
const cbcState = runDesCbcMac(key1, padded);
|
||||
const fullMacBytes = encryptDesBlock(key3, decryptDesBlock(key2, cbcState));
|
||||
const fullMacHex = bytesToHex(fullMacBytes);
|
||||
const macHex = fullMacHex.substring(0, Math.max(1, Math.min(8, Number(outputBytes) || 8)) * 2);
|
||||
|
||||
return {
|
||||
algorithm: "ISO 9797-1 Algorithm 3",
|
||||
paddingMethod,
|
||||
inputHex: bytesToHex(data),
|
||||
fullMacHex,
|
||||
macHex,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an AS2805 4.1 MAC.
|
||||
*
|
||||
* @param {string} inputHex
|
||||
* @param {string} keyHex
|
||||
* @param {string} paddingMethod
|
||||
* @param {number} outputBytes
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generateAs2805Mac(inputHex, keyHex, paddingMethod="Method 1", outputBytes=8) {
|
||||
const retail = generateIso9797Algorithm3Mac(inputHex, keyHex, paddingMethod, outputBytes);
|
||||
return {
|
||||
...retail,
|
||||
algorithm: "AS2805-4.1"
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
ISO9797_PADDING_METHODS,
|
||||
generateAs2805Mac,
|
||||
generateIso9797Algorithm1Mac,
|
||||
generateIso9797Algorithm3Mac,
|
||||
};
|
||||
@ -7,6 +7,12 @@ import OperationError from "../errors/OperationError.mjs";
|
||||
import HMAC from "../operations/HMAC.mjs";
|
||||
import CMAC from "../operations/CMAC.mjs";
|
||||
import DeriveDUKPTKey from "../operations/DeriveDUKPTKey.mjs";
|
||||
import {
|
||||
ISO9797_PADDING_METHODS,
|
||||
generateAs2805Mac,
|
||||
generateIso9797Algorithm1Mac,
|
||||
generateIso9797Algorithm3Mac,
|
||||
} from "./Iso9797.mjs";
|
||||
|
||||
const PAYMENT_MAC_METHODS = [
|
||||
"HMAC SHA-224",
|
||||
@ -15,8 +21,13 @@ const PAYMENT_MAC_METHODS = [
|
||||
"HMAC SHA-512",
|
||||
"AES-CMAC",
|
||||
"TDES-CMAC",
|
||||
"ISO 9797-1 Algorithm 1",
|
||||
"ISO 9797-1 Algorithm 3",
|
||||
"AS2805-4.1",
|
||||
"DUKPT MAC Request CMAC",
|
||||
"DUKPT MAC Response CMAC",
|
||||
"DUKPT ISO 9797-1 Algorithm 1",
|
||||
"DUKPT ISO 9797-1 Algorithm 3",
|
||||
];
|
||||
|
||||
/**
|
||||
@ -41,7 +52,12 @@ function convertInputToBuffer(input, inputFormat) {
|
||||
function resolveMacKey(method, keySpec) {
|
||||
const normalizedKey = (keySpec.keyValue || "").replace(/\s+/g, "");
|
||||
|
||||
if (method === "DUKPT MAC Request CMAC" || method === "DUKPT MAC Response CMAC") {
|
||||
if (
|
||||
method === "DUKPT MAC Request CMAC" ||
|
||||
method === "DUKPT MAC Response CMAC" ||
|
||||
method === "DUKPT ISO 9797-1 Algorithm 1" ||
|
||||
method === "DUKPT ISO 9797-1 Algorithm 3"
|
||||
) {
|
||||
if (keySpec.keyFormat !== "Hex") {
|
||||
throw new OperationError("DUKPT BDK must be provided in hex.");
|
||||
}
|
||||
@ -49,7 +65,9 @@ function resolveMacKey(method, keySpec) {
|
||||
throw new OperationError("KSN is required for DUKPT MAC methods.");
|
||||
}
|
||||
|
||||
const variant = method === "DUKPT MAC Request CMAC" ? "MAC Request" : "MAC Response";
|
||||
const variant = method === "DUKPT MAC Request CMAC" ? "MAC Request" :
|
||||
method === "DUKPT MAC Response CMAC" ? "MAC Response" :
|
||||
"MAC Request";
|
||||
const dukpt = new DeriveDUKPTKey();
|
||||
const keyHex = dukpt.run(normalizedKey, ["Derive Session Key", keySpec.ksn, variant, false]);
|
||||
|
||||
@ -96,9 +114,10 @@ function byteStringToHex(byteString) {
|
||||
* @param {string} keyFormat
|
||||
* @param {string} ksn
|
||||
* @param {number} outputBytes
|
||||
* @param {string} paddingMethod
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes) {
|
||||
function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes, paddingMethod="Method 1") {
|
||||
const normalizedOutputBytes = Math.max(1, Number(outputBytes) || 8);
|
||||
const inputBuffer = convertInputToBuffer(input, inputFormat);
|
||||
const inputHex = byteStringToHex(Utils.arrayBufferToStr(inputBuffer, false));
|
||||
@ -114,10 +133,18 @@ function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn
|
||||
"HMAC SHA-512": "SHA512",
|
||||
}[method];
|
||||
fullMacHex = hmac.run(inputBuffer, [{ string: keyHex, option: "Hex" }, hashName]).toUpperCase();
|
||||
} else {
|
||||
} else if (method === "AES-CMAC" || method === "TDES-CMAC" || method === "DUKPT MAC Request CMAC" || method === "DUKPT MAC Response CMAC") {
|
||||
const cmac = new CMAC();
|
||||
const algorithm = method === "AES-CMAC" ? "AES" : "Triple DES";
|
||||
fullMacHex = cmac.run(inputBuffer, [{ string: keyHex, option: "Hex" }, algorithm]).toUpperCase();
|
||||
} else if (method === "ISO 9797-1 Algorithm 1" || method === "DUKPT ISO 9797-1 Algorithm 1") {
|
||||
fullMacHex = generateIso9797Algorithm1Mac(inputHex, keyHex, paddingMethod, 8).fullMacHex;
|
||||
} else if (method === "ISO 9797-1 Algorithm 3" || method === "DUKPT ISO 9797-1 Algorithm 3") {
|
||||
fullMacHex = generateIso9797Algorithm3Mac(inputHex, keyHex, paddingMethod, 8).fullMacHex;
|
||||
} else if (method === "AS2805-4.1") {
|
||||
fullMacHex = generateAs2805Mac(inputHex, keyHex, paddingMethod, 8).fullMacHex;
|
||||
} else {
|
||||
throw new OperationError("Unsupported payment MAC method.");
|
||||
}
|
||||
|
||||
const macHex = fullMacHex.substring(0, normalizedOutputBytes * 2);
|
||||
@ -126,6 +153,7 @@ function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn
|
||||
method,
|
||||
inputFormat,
|
||||
inputHex,
|
||||
paddingMethod: method.startsWith("HMAC ") || method.includes("CMAC") ? null : paddingMethod,
|
||||
outputBytes: normalizedOutputBytes,
|
||||
fullMacHex,
|
||||
macHex,
|
||||
@ -143,9 +171,10 @@ function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn
|
||||
* @param {string} keyFormat
|
||||
* @param {string} ksn
|
||||
* @param {string} expectedMac
|
||||
* @param {string} paddingMethod
|
||||
* @returns {Object}
|
||||
*/
|
||||
function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac) {
|
||||
function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac, paddingMethod="Method 1") {
|
||||
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.");
|
||||
@ -158,7 +187,8 @@ function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn,
|
||||
keyValue,
|
||||
keyFormat,
|
||||
ksn,
|
||||
normalizedExpected.length / 2
|
||||
normalizedExpected.length / 2,
|
||||
paddingMethod
|
||||
);
|
||||
|
||||
return {
|
||||
@ -169,6 +199,7 @@ function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn,
|
||||
}
|
||||
|
||||
export {
|
||||
ISO9797_PADDING_METHODS,
|
||||
PAYMENT_MAC_METHODS,
|
||||
generatePaymentMac,
|
||||
verifyPaymentMac,
|
||||
|
||||
252
src/core/lib/PaymentPinVerification.mjs
Normal file
252
src/core/lib/PaymentPinVerification.mjs
Normal file
@ -0,0 +1,252 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import { bytesToHex, parseHexBytes } from "./PaymentUtils.mjs";
|
||||
import { encryptTdesEcb } from "./CardValidationInternals.mjs";
|
||||
|
||||
/**
|
||||
* Normalizes a PAN string.
|
||||
*
|
||||
* @param {string} pan
|
||||
* @returns {string}
|
||||
*/
|
||||
function normalizePan(pan) {
|
||||
const normalized = (pan || "").replace(/\s+/g, "");
|
||||
if (!/^\d{12,19}$/.test(normalized)) {
|
||||
throw new OperationError("PAN must be 12 to 19 digits.");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a clear PIN string.
|
||||
*
|
||||
* @param {string} pin
|
||||
* @returns {string}
|
||||
*/
|
||||
function normalizePin(pin) {
|
||||
const normalized = (pin || "").replace(/\s+/g, "");
|
||||
if (!/^\d{4,12}$/.test(normalized)) {
|
||||
throw new OperationError("PIN must be 4 to 12 digits.");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts hexadecimal characters to decimal digits via a decimalization table.
|
||||
*
|
||||
* @param {string} hex
|
||||
* @param {string} decimalizationTable
|
||||
* @returns {string}
|
||||
*/
|
||||
function decimalizeHex(hex, decimalizationTable) {
|
||||
const normalizedTable = (decimalizationTable || "").replace(/\s+/g, "");
|
||||
if (!/^\d{16}$/.test(normalizedTable)) {
|
||||
throw new OperationError("Decimalization table must be 16 decimal digits.");
|
||||
}
|
||||
|
||||
let out = "";
|
||||
for (const ch of hex.toUpperCase()) {
|
||||
out += normalizedTable[parseInt(ch, 16)];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Packs a hex string into bytes.
|
||||
*
|
||||
* @param {string} hex
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function packHex(hex) {
|
||||
return parseHexBytes(hex, "Packed block");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the IBM 3624 natural PIN.
|
||||
*
|
||||
* @param {string} pvkHex
|
||||
* @param {string} decimalizationTable
|
||||
* @param {string} pinValidationData
|
||||
* @param {string} padCharacter
|
||||
* @param {number} pinLength
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generateIbm3624NaturalPin(pvkHex, decimalizationTable, pinValidationData, padCharacter, pinLength=4) {
|
||||
const normalizedValidationData = (pinValidationData || "").replace(/\s+/g, "");
|
||||
const normalizedPad = (padCharacter || "").replace(/\s+/g, "").toUpperCase();
|
||||
const normalizedPinLength = Math.max(4, Math.min(12, Number(pinLength) || 4));
|
||||
|
||||
if (!/^\d{4,16}$/.test(normalizedValidationData)) {
|
||||
throw new OperationError("PIN validation data must be 4 to 16 decimal digits.");
|
||||
}
|
||||
if (!/^[0-9A-F]$/.test(normalizedPad)) {
|
||||
throw new OperationError("PIN validation data pad character must be one hex nibble.");
|
||||
}
|
||||
|
||||
const pvk = parseHexBytes(pvkHex, "PIN verification key", [16, 24]);
|
||||
const blockHex = normalizedValidationData.padEnd(16, normalizedPad).substring(0, 16);
|
||||
const cipherHex = bytesToHex(encryptTdesEcb(pvk, packHex(blockHex)));
|
||||
const decimalized = decimalizeHex(cipherHex, decimalizationTable);
|
||||
|
||||
return {
|
||||
pinVerificationKeyHex: bytesToHex(pvk),
|
||||
pinValidationData: normalizedValidationData,
|
||||
pinValidationDataPadCharacter: normalizedPad,
|
||||
pinLength: normalizedPinLength,
|
||||
validationBlockHex: blockHex,
|
||||
encryptedValidationBlockHex: cipherHex,
|
||||
decimalized,
|
||||
naturalPin: decimalized.substring(0, normalizedPinLength)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an IBM 3624 offset for a supplied clear PIN.
|
||||
*
|
||||
* @param {string} pvkHex
|
||||
* @param {string} decimalizationTable
|
||||
* @param {string} pinValidationData
|
||||
* @param {string} padCharacter
|
||||
* @param {string} pin
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generateIbm3624PinOffset(pvkHex, decimalizationTable, pinValidationData, padCharacter, pin) {
|
||||
const normalizedPin = normalizePin(pin);
|
||||
const natural = generateIbm3624NaturalPin(
|
||||
pvkHex,
|
||||
decimalizationTable,
|
||||
pinValidationData,
|
||||
padCharacter,
|
||||
normalizedPin.length
|
||||
);
|
||||
let offset = "";
|
||||
for (let i = 0; i < normalizedPin.length; i++) {
|
||||
offset += ((parseInt(normalizedPin[i], 10) - parseInt(natural.naturalPin[i], 10) + 10) % 10).toString();
|
||||
}
|
||||
return {
|
||||
...natural,
|
||||
pin: normalizedPin,
|
||||
pinOffset: offset
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a clear PIN against an IBM 3624 offset.
|
||||
*
|
||||
* @param {string} pvkHex
|
||||
* @param {string} decimalizationTable
|
||||
* @param {string} pinValidationData
|
||||
* @param {string} padCharacter
|
||||
* @param {string} pinOffset
|
||||
* @param {string} pin
|
||||
* @returns {Object}
|
||||
*/
|
||||
function verifyIbm3624Pin(pvkHex, decimalizationTable, pinValidationData, padCharacter, pinOffset, pin) {
|
||||
const normalizedOffset = (pinOffset || "").replace(/\s+/g, "");
|
||||
const normalizedPin = normalizePin(pin);
|
||||
if (!/^\d{4,12}$/.test(normalizedOffset) || normalizedOffset.length !== normalizedPin.length) {
|
||||
throw new OperationError("PIN offset must be 4 to 12 digits and match PIN length.");
|
||||
}
|
||||
|
||||
const generated = generateIbm3624PinOffset(
|
||||
pvkHex,
|
||||
decimalizationTable,
|
||||
pinValidationData,
|
||||
padCharacter,
|
||||
normalizedPin
|
||||
);
|
||||
|
||||
return {
|
||||
...generated,
|
||||
expectedPinOffset: normalizedOffset,
|
||||
valid: generated.pinOffset === normalizedOffset
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimalizes a PVV candidate using the common numeric-first rule.
|
||||
*
|
||||
* @param {string} hex
|
||||
* @returns {string}
|
||||
*/
|
||||
function decimalizePvv(hex) {
|
||||
let out = "";
|
||||
for (const ch of hex.toUpperCase()) {
|
||||
if (/\d/.test(ch)) {
|
||||
out += ch;
|
||||
} else {
|
||||
out += String((ch.charCodeAt(0) - "A".charCodeAt(0)) % 10);
|
||||
}
|
||||
if (out.length >= 4) return out.substring(0, 4);
|
||||
}
|
||||
return out.substring(0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a VISA PVV.
|
||||
*
|
||||
* @param {string} pvkHex
|
||||
* @param {string} pan
|
||||
* @param {string|number} pvki
|
||||
* @param {string} pin
|
||||
* @returns {Object}
|
||||
*/
|
||||
function generateVisaPvv(pvkHex, pan, pvki, pin) {
|
||||
const normalizedPan = normalizePan(pan);
|
||||
const normalizedPin = normalizePin(pin);
|
||||
const normalizedPvki = String(pvki ?? "").replace(/\s+/g, "");
|
||||
|
||||
if (!/^[0-6]$/.test(normalizedPvki)) {
|
||||
throw new OperationError("PVKI must be a single digit from 0 to 6.");
|
||||
}
|
||||
|
||||
const pvk = parseHexBytes(pvkHex, "PIN verification key", [16, 24]);
|
||||
const pvvInput = `${normalizedPan.slice(-12, -1)}${normalizedPvki}${normalizedPin.substring(0, 4)}`;
|
||||
const encryptedHex = bytesToHex(encryptTdesEcb(pvk, packHex(pvvInput)));
|
||||
const pvv = decimalizePvv(encryptedHex);
|
||||
|
||||
return {
|
||||
pinVerificationKeyHex: bytesToHex(pvk),
|
||||
pan: normalizedPan,
|
||||
pinVerificationKeyIndex: Number(normalizedPvki),
|
||||
pin: normalizedPin,
|
||||
pvvInput,
|
||||
encryptedPvvInputHex: encryptedHex,
|
||||
pvv
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a VISA PVV.
|
||||
*
|
||||
* @param {string} pvkHex
|
||||
* @param {string} pan
|
||||
* @param {string|number} pvki
|
||||
* @param {string} pin
|
||||
* @param {string} expectedPvv
|
||||
* @returns {Object}
|
||||
*/
|
||||
function verifyVisaPvv(pvkHex, pan, pvki, pin, expectedPvv) {
|
||||
const normalizedExpected = (expectedPvv || "").replace(/\s+/g, "");
|
||||
if (!/^\d{4}$/.test(normalizedExpected)) {
|
||||
throw new OperationError("Expected PVV must be 4 digits.");
|
||||
}
|
||||
|
||||
const generated = generateVisaPvv(pvkHex, pan, pvki, pin);
|
||||
return {
|
||||
...generated,
|
||||
expectedPvv: normalizedExpected,
|
||||
valid: generated.pvv === normalizedExpected
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
generateIbm3624NaturalPin,
|
||||
generateIbm3624PinOffset,
|
||||
generateVisaPvv,
|
||||
verifyIbm3624Pin,
|
||||
verifyVisaPvv,
|
||||
};
|
||||
108
src/core/operations/GenerateAS2805KEKValidation.mjs
Normal file
108
src/core/operations/GenerateAS2805KEKValidation.mjs
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import CalculatePaymentKCV from "./CalculatePaymentKCV.mjs";
|
||||
import { bytesToHex, parseHexBytes } from "../lib/PaymentUtils.mjs";
|
||||
|
||||
/**
|
||||
* Returns cryptographically random bytes when available.
|
||||
*
|
||||
* @param {number} length
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function randomBytes(length) {
|
||||
const out = new Uint8Array(length);
|
||||
if (globalThis.crypto && globalThis.crypto.getRandomValues) {
|
||||
globalThis.crypto.getRandomValues(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
out[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverts all bytes.
|
||||
*
|
||||
* @param {Uint8Array} bytes
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function invertBytes(bytes) {
|
||||
return Uint8Array.from(bytes, byte => byte ^ 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate AS2805 KEK validation operation.
|
||||
*/
|
||||
class GenerateAS2805KEKValidation extends Operation {
|
||||
/**
|
||||
* GenerateAS2805KEKValidation constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate AS2805 KEK Validation";
|
||||
this.module = "Payment";
|
||||
this.description = "Paste the clear sending KEK into the input field as hex and generate an AS2805 KEK validation request or response.<br><br><b>Input:</b> clear KEK as 16-byte or 24-byte hex.<br><b>Arguments:</b> choose request or response mode, select the random-key length, choose the variant mask label, and optionally provide the incoming RandomKeySend value.<br><br>Assumption: this software emulation returns <code>RandomKeyReceive</code> as the bytewise inverse of <code>RandomKeySend</code>, which is sufficient for lab testing but does not claim exact HSM-side AS2805 node-initialization behavior.";
|
||||
this.inlineHelp = "<strong>Input:</strong> clear KEK hex.<br><strong>Args:</strong> choose request or response mode and provide RandomKeySend for response mode.";
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "AS2805 request sample",
|
||||
input: "0123456789ABCDEFFEDCBA9876543210",
|
||||
args: ["KekValidationRequest", "TDES_2KEY", "VARIANT_MASK_82", "", true]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_GenerateAs2805KekValidation.html";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{ name: "Validation type", type: "option", value: ["KekValidationRequest", "KekValidationResponse"], comment: "Request mode creates a fresh RandomKeySend. Response mode derives RandomKeyReceive from the supplied RandomKeySend." },
|
||||
{ name: "Derive key algorithm", type: "option", value: ["TDES_2KEY", "TDES_3KEY"], comment: "Controls whether RandomKeySend / RandomKeyReceive are 16 bytes or 24 bytes long." },
|
||||
{ name: "RandomKeySend variant mask", type: "option", value: ["VARIANT_MASK_82", "VARIANT_MASK_82C0"], comment: "AWS surfaces this as metadata for AS2805 KEK validation. This emulation reports the selected label but does not model HSM-side key custody." },
|
||||
{ name: "RandomKeySend (response only)", type: "string", value: "", comment: "Required only in response mode. Provide the incoming RandomKeySend hex value from the partner node." },
|
||||
{ name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the KEK KCV and both RandomKeySend / RandomKeyReceive values." },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [validationType, deriveKeyAlgorithm, randomKeySendVariantMask, randomKeySendHex, outputJson] = args;
|
||||
const kek = parseHexBytes(input, "KEK", deriveKeyAlgorithm === "TDES_2KEY" ? [16] : [24]);
|
||||
const randomKeyLength = deriveKeyAlgorithm === "TDES_2KEY" ? 16 : 24;
|
||||
|
||||
let randomKeySend;
|
||||
if (validationType === "KekValidationRequest") {
|
||||
randomKeySend = randomBytes(randomKeyLength);
|
||||
} else {
|
||||
if (!randomKeySendHex) {
|
||||
throw new OperationError("RandomKeySend is required for KEK validation response mode.");
|
||||
}
|
||||
randomKeySend = parseHexBytes(randomKeySendHex, "RandomKeySend", [randomKeyLength]);
|
||||
}
|
||||
|
||||
const randomKeyReceive = invertBytes(randomKeySend);
|
||||
const kcv = new CalculatePaymentKCV().run(bytesToHex(kek), ["Hex", "TDES-ECB (Zeros)", 6]);
|
||||
|
||||
const result = {
|
||||
validationType,
|
||||
deriveKeyAlgorithm,
|
||||
randomKeySendVariantMask,
|
||||
keyCheckValue: kcv,
|
||||
randomKeySend: bytesToHex(randomKeySend),
|
||||
randomKeyReceive: bytesToHex(randomKeyReceive)
|
||||
};
|
||||
|
||||
return outputJson ? JSON.stringify(result, null, 4) : result.randomKeyReceive;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenerateAS2805KEKValidation;
|
||||
51
src/core/operations/GenerateEMVMAC.mjs
Normal file
51
src/core/operations/GenerateEMVMAC.mjs
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { generateEmvMac } from "../lib/EmvMac.mjs";
|
||||
|
||||
/**
|
||||
* Generate EMV MAC operation.
|
||||
*/
|
||||
class GenerateEMVMAC extends Operation {
|
||||
/**
|
||||
* GenerateEMVMAC constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate EMV MAC";
|
||||
this.module = "Payment";
|
||||
this.description = "Paste the issuer-script or EMV command payload into the input field as hex and generate an EMV MAC.<br><br><b>Input:</b> message data as hex.<br><b>Arguments:</b> provide the already-derived EMV session integrity key and choose how many leftmost MAC bytes to return.<br><br>Assumption: this operation expects the EMV session key to have been derived outside the operation and applies ISO9797-3 retail MAC with ISO9797 padding method 2.";
|
||||
this.inlineHelp = "<strong>Input:</strong> issuer-script message data as hex.<br><strong>Args:</strong> provide the derived EMV session integrity key.";
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "EMV MAC sample",
|
||||
input: "8424000008999E57FD0F47CACE0007",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", 8, false]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/use-cases-issuers.generalfunctions.emvmac.html";
|
||||
this.inputType = "string";
|
||||
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: "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." },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [sessionKeyHex, outputBytes, outputJson] = args;
|
||||
const result = generateEmvMac(input, sessionKeyHex, outputBytes);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : result.macHex;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenerateEMVMAC;
|
||||
52
src/core/operations/GenerateEMVMACForPINChange.mjs
Normal file
52
src/core/operations/GenerateEMVMACForPINChange.mjs
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { generateEmvPinChangeMac } from "../lib/EmvMac.mjs";
|
||||
|
||||
/**
|
||||
* Generate EMV MAC for PIN change operation.
|
||||
*/
|
||||
class GenerateEMVMACForPINChange extends Operation {
|
||||
/**
|
||||
* GenerateEMVMACForPINChange constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate EMV MAC For PIN Change";
|
||||
this.module = "Payment";
|
||||
this.description = "Paste the issuer-script APDU command into the input field as hex and generate the MAC for an offline EMV PIN-change script.<br><br><b>Input:</b> issuer-script message data as hex.<br><b>Arguments:</b> provide the already-encrypted target PIN block in hex and the already-derived EMV session integrity key.<br><br>Assumptions: the new PIN block has already been encrypted before calling this operation, and this op appends that encrypted PIN block to the message before applying EMV retail MAC generation.";
|
||||
this.inlineHelp = "<strong>Input:</strong> issuer-script APDU message as hex.<br><strong>Args:</strong> provide the encrypted target PIN block and derived EMV integrity key.";
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "EMV PIN change MAC sample",
|
||||
input: "00A4040008A000000004101080D80500000001010A04000000000000",
|
||||
args: ["67FB27C75580EFE7", "0123456789ABCDEFFEDCBA9876543210", 8, false]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/use-cases-issuers.generalfunctions.emvpinchange.html";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{ name: "New encrypted PIN block (hex)", type: "string", value: "", comment: "Provide the already-encrypted new PIN block that will be appended to the issuer-script message." },
|
||||
{ name: "Session integrity key (hex)", type: "string", value: "", comment: "Provide the already-derived EMV session integrity key in hex. This emulation does not derive EMV keys or encrypt the PIN block for you." },
|
||||
{ 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 composed issuer-script message and the computed MAC." },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [encryptedPinBlockHex, sessionKeyHex, outputBytes, outputJson] = args;
|
||||
const result = generateEmvPinChangeMac(input, encryptedPinBlockHex, sessionKeyHex, outputBytes);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : result.macHex;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenerateEMVMACForPINChange;
|
||||
53
src/core/operations/GenerateIBM3624PINOffset.mjs
Normal file
53
src/core/operations/GenerateIBM3624PINOffset.mjs
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { generateIbm3624PinOffset } from "../lib/PaymentPinVerification.mjs";
|
||||
|
||||
/**
|
||||
* Generate IBM 3624 PIN offset operation.
|
||||
*/
|
||||
class GenerateIBM3624PINOffset extends Operation {
|
||||
/**
|
||||
* GenerateIBM3624PINOffset constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate IBM 3624 PIN Offset";
|
||||
this.module = "Payment";
|
||||
this.description = "Paste the clear PIN into the input field and generate the IBM 3624 offset used by issuer-side PIN verification.<br><br><b>Input:</b> clear PIN digits.<br><b>Arguments:</b> provide the clear PVK in hex, decimalization table, validation data, and pad character.<br><br>Assumption: this is a clear-key software emulation of the IBM 3624 offset algorithm for test harnesses.";
|
||||
this.inlineHelp = "<strong>Input:</strong> clear PIN digits.<br><strong>Args:</strong> provide PVK, decimalization table, validation data, and pad character.";
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "IBM 3624 offset sample",
|
||||
input: "1234",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "0123456789012345", "5432101234567890", "F", true]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/generate-ibm3624.html";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{ name: "PIN verification key (hex)", type: "string", value: "", comment: "Provide the clear IBM 3624 PVK as 16-byte or 24-byte hex." },
|
||||
{ name: "Decimalization table", type: "string", value: "0123456789012345", comment: "Sixteen decimal digits used to map hex nibbles to decimal digits." },
|
||||
{ name: "PIN validation data", type: "string", value: "", comment: "Issuer validation data, typically PAN-derived digits, 4 to 16 digits." },
|
||||
{ name: "Pad character", type: "shortString", value: "F", comment: "Single hex nibble used to right-pad validation data to 16 nibbles." },
|
||||
{ name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the intermediate natural PIN and validation-block details." },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [pvkHex, decimalizationTable, pinValidationData, padCharacter, outputJson] = args;
|
||||
const result = generateIbm3624PinOffset(pvkHex, decimalizationTable, pinValidationData, padCharacter, input);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : result.pinOffset;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenerateIBM3624PINOffset;
|
||||
@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { PAYMENT_MAC_METHODS, generatePaymentMac } from "../lib/PaymentMac.mjs";
|
||||
import { ISO9797_PADDING_METHODS, PAYMENT_MAC_METHODS, generatePaymentMac } from "../lib/PaymentMac.mjs";
|
||||
|
||||
/**
|
||||
* Generate payment MAC operation.
|
||||
@ -18,13 +18,13 @@ class GeneratePaymentMAC extends Operation {
|
||||
|
||||
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.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, choose the ISO9797 padding rule when applicable, and choose the truncation length.<br><br>This wrapper reuses existing HMAC and CMAC primitives where possible and adds payment-specific ISO9797 / AS2805 modes for software testing.";
|
||||
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]
|
||||
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", 8, false]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_GenerateMac.html";
|
||||
@ -41,7 +41,7 @@ class GeneratePaymentMAC extends Operation {
|
||||
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."
|
||||
comment: "Static-key HMAC and CMAC modes reuse the existing generic primitives. ISO9797 and AS2805 modes apply TDES-based payment MAC logic. DUKPT modes derive a TDES session key first."
|
||||
},
|
||||
{
|
||||
name: "Key / BDK",
|
||||
@ -61,6 +61,12 @@ class GeneratePaymentMAC extends Operation {
|
||||
value: "",
|
||||
comment: "Required only for DUKPT MAC methods. Provide the full 10-byte KSN as 20 hex characters."
|
||||
},
|
||||
{
|
||||
name: "ISO9797 padding",
|
||||
type: "option",
|
||||
value: ISO9797_PADDING_METHODS,
|
||||
comment: "Used only for ISO9797 and AS2805 MAC methods. <code>Method 1</code> pads with zero bytes to the next block. <code>Method 2</code> appends <code>80</code> then zeros."
|
||||
},
|
||||
{
|
||||
name: "Output bytes",
|
||||
type: "number",
|
||||
@ -84,8 +90,8 @@ class GeneratePaymentMAC extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, method, keyValue, keyFormat, ksn, outputBytes, outputJson] = args;
|
||||
const result = generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes);
|
||||
const [inputFormat, method, keyValue, keyFormat, ksn, paddingMethod, outputBytes, outputJson] = args;
|
||||
const result = generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes, paddingMethod);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : result.macHex;
|
||||
}
|
||||
}
|
||||
|
||||
52
src/core/operations/GenerateVISAPVV.mjs
Normal file
52
src/core/operations/GenerateVISAPVV.mjs
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { generateVisaPvv } from "../lib/PaymentPinVerification.mjs";
|
||||
|
||||
/**
|
||||
* Generate VISA PVV operation.
|
||||
*/
|
||||
class GenerateVISAPVV extends Operation {
|
||||
/**
|
||||
* GenerateVISAPVV constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Generate VISA PVV";
|
||||
this.module = "Payment";
|
||||
this.description = "Paste the clear PIN into the input field and generate a VISA PIN Verification Value (PVV).<br><br><b>Input:</b> clear PIN digits.<br><b>Arguments:</b> provide the clear PVK in hex, PAN, and PVKI.<br><br>Assumption: this is a clear-key software emulation of the common VISA PVV generation flow for test harnesses.";
|
||||
this.inlineHelp = "<strong>Input:</strong> clear PIN digits.<br><strong>Args:</strong> provide PVK, PAN, and PVKI.";
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "VISA PVV sample",
|
||||
input: "1234",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, true]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VisaPinVerification.html";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{ name: "PIN verification key (hex)", type: "string", value: "", comment: "Provide the clear VISA PVK as 16-byte or 24-byte hex." },
|
||||
{ name: "Primary account number", type: "string", value: "", comment: "Provide the PAN as digits only. The standard PVV input uses the rightmost 11 digits before the check digit." },
|
||||
{ name: "PVKI", type: "number", value: 1, min: 0, max: 6, comment: "PIN verification key index from 0 through 6." },
|
||||
{ name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the assembled PVV input and intermediate encrypted block." },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [pvkHex, pan, pvki, outputJson] = args;
|
||||
const result = generateVisaPvv(pvkHex, pan, pvki, input);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : result.pvv;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenerateVISAPVV;
|
||||
51
src/core/operations/VerifyEMVMAC.mjs
Normal file
51
src/core/operations/VerifyEMVMAC.mjs
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { verifyEmvMac } from "../lib/EmvMac.mjs";
|
||||
|
||||
/**
|
||||
* Verify EMV MAC operation.
|
||||
*/
|
||||
class VerifyEMVMAC extends Operation {
|
||||
/**
|
||||
* VerifyEMVMAC constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Verify EMV MAC";
|
||||
this.module = "Payment";
|
||||
this.description = "Paste the issuer-script or EMV command payload into the input field as hex and verify an EMV MAC.<br><br><b>Input:</b> message data as hex.<br><b>Arguments:</b> provide the already-derived EMV session integrity key and the expected MAC as hex.<br><br>Assumption: this operation expects the EMV session key to have been derived outside the operation and applies ISO9797-3 retail MAC with ISO9797 padding method 2.";
|
||||
this.inlineHelp = "<strong>Input:</strong> issuer-script message data as hex.<br><strong>Args:</strong> provide the derived EMV session key and expected MAC.";
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "EMV MAC verification sample",
|
||||
input: "8424000008999E57FD0F47CACE0007",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "22CB48394DFD1977", true]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/use-cases-issuers.generalfunctions.emvmac.html";
|
||||
this.inputType = "string";
|
||||
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: "Expected MAC (hex)", type: "string", value: "", comment: "Issuer-script MAC to compare against, expressed as even-length hex." },
|
||||
{ name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the recomputed MAC and validity result." },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [sessionKeyHex, expectedMac, outputJson] = args;
|
||||
const result = verifyEmvMac(input, sessionKeyHex, expectedMac);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : String(result.valid);
|
||||
}
|
||||
}
|
||||
|
||||
export default VerifyEMVMAC;
|
||||
54
src/core/operations/VerifyIBM3624PIN.mjs
Normal file
54
src/core/operations/VerifyIBM3624PIN.mjs
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { verifyIbm3624Pin } from "../lib/PaymentPinVerification.mjs";
|
||||
|
||||
/**
|
||||
* Verify IBM 3624 PIN operation.
|
||||
*/
|
||||
class VerifyIBM3624PIN extends Operation {
|
||||
/**
|
||||
* VerifyIBM3624PIN constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Verify IBM 3624 PIN";
|
||||
this.module = "Payment";
|
||||
this.description = "Paste the clear PIN into the input field and verify it against an IBM 3624 offset.<br><br><b>Input:</b> clear PIN digits.<br><b>Arguments:</b> provide the clear PVK in hex, decimalization table, validation data, pad character, and expected offset.<br><br>Assumption: this is a clear-key software emulation of the IBM 3624 offset verification flow.";
|
||||
this.inlineHelp = "<strong>Input:</strong> clear PIN digits.<br><strong>Args:</strong> provide PVK, decimalization table, validation data, pad character, and expected offset.";
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "IBM 3624 verify sample",
|
||||
input: "1234",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "0123456789012345", "5432101234567890", "F", "3207", true]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/verify-pin-data.ibm3624-example.html";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{ name: "PIN verification key (hex)", type: "string", value: "", comment: "Provide the clear IBM 3624 PVK as 16-byte or 24-byte hex." },
|
||||
{ name: "Decimalization table", type: "string", value: "0123456789012345", comment: "Sixteen decimal digits used to map hex nibbles to decimal digits." },
|
||||
{ name: "PIN validation data", type: "string", value: "", comment: "Issuer validation data, typically PAN-derived digits, 4 to 16 digits." },
|
||||
{ name: "Pad character", type: "shortString", value: "F", comment: "Single hex nibble used to right-pad validation data to 16 nibbles." },
|
||||
{ name: "PIN offset", type: "string", value: "", comment: "Stored IBM 3624 offset value to compare against." },
|
||||
{ name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the recomputed offset and validity result." },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [pvkHex, decimalizationTable, pinValidationData, padCharacter, pinOffset, outputJson] = args;
|
||||
const result = verifyIbm3624Pin(pvkHex, decimalizationTable, pinValidationData, padCharacter, pinOffset, input);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : String(result.valid);
|
||||
}
|
||||
}
|
||||
|
||||
export default VerifyIBM3624PIN;
|
||||
@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { PAYMENT_MAC_METHODS, verifyPaymentMac } from "../lib/PaymentMac.mjs";
|
||||
import { ISO9797_PADDING_METHODS, PAYMENT_MAC_METHODS, verifyPaymentMac } from "../lib/PaymentMac.mjs";
|
||||
|
||||
/**
|
||||
* Verify payment MAC operation.
|
||||
@ -18,13 +18,13 @@ class VerifyPaymentMAC extends Operation {
|
||||
|
||||
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.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, choose the ISO9797 padding rule when applicable, 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]
|
||||
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", "339AF1AD1650E908", true]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VerifyMac.html";
|
||||
@ -41,7 +41,7 @@ class VerifyPaymentMAC extends Operation {
|
||||
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."
|
||||
comment: "Static-key HMAC and CMAC modes reuse the existing generic primitives. ISO9797 and AS2805 modes apply TDES-based payment MAC logic. DUKPT modes derive a TDES session key first."
|
||||
},
|
||||
{
|
||||
name: "Key / BDK",
|
||||
@ -61,6 +61,12 @@ class VerifyPaymentMAC extends Operation {
|
||||
value: "",
|
||||
comment: "Required only for DUKPT MAC methods. Provide the full 10-byte KSN as 20 hex characters."
|
||||
},
|
||||
{
|
||||
name: "ISO9797 padding",
|
||||
type: "option",
|
||||
value: ISO9797_PADDING_METHODS,
|
||||
comment: "Used only for ISO9797 and AS2805 MAC methods. Keep this aligned with the sender."
|
||||
},
|
||||
{
|
||||
name: "Expected MAC (hex)",
|
||||
type: "string",
|
||||
@ -82,8 +88,8 @@ class VerifyPaymentMAC extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [inputFormat, method, keyValue, keyFormat, ksn, expectedMac, outputJson] = args;
|
||||
const result = verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac);
|
||||
const [inputFormat, method, keyValue, keyFormat, ksn, paddingMethod, expectedMac, outputJson] = args;
|
||||
const result = verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac, paddingMethod);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : String(result.valid);
|
||||
}
|
||||
}
|
||||
|
||||
53
src/core/operations/VerifyVISAPVV.mjs
Normal file
53
src/core/operations/VerifyVISAPVV.mjs
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import { verifyVisaPvv } from "../lib/PaymentPinVerification.mjs";
|
||||
|
||||
/**
|
||||
* Verify VISA PVV operation.
|
||||
*/
|
||||
class VerifyVISAPVV extends Operation {
|
||||
/**
|
||||
* VerifyVISAPVV constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Verify VISA PVV";
|
||||
this.module = "Payment";
|
||||
this.description = "Paste the clear PIN into the input field and verify it against a VISA PVV.<br><br><b>Input:</b> clear PIN digits.<br><b>Arguments:</b> provide the clear PVK in hex, PAN, PVKI, and expected PVV.<br><br>Assumption: this is a clear-key software emulation of the common VISA PVV verification flow for test harnesses.";
|
||||
this.inlineHelp = "<strong>Input:</strong> clear PIN digits.<br><strong>Args:</strong> provide PVK, PAN, PVKI, and expected PVV.";
|
||||
this.testDataSamples = [
|
||||
{
|
||||
name: "VISA PVV verify sample",
|
||||
input: "1234",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, "6077", true]
|
||||
}
|
||||
];
|
||||
this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VisaPinVerificationValue.html";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{ name: "PIN verification key (hex)", type: "string", value: "", comment: "Provide the clear VISA PVK as 16-byte or 24-byte hex." },
|
||||
{ name: "Primary account number", type: "string", value: "", comment: "Provide the PAN as digits only. The standard PVV input uses the rightmost 11 digits before the check digit." },
|
||||
{ name: "PVKI", type: "number", value: 1, min: 0, max: 6, comment: "PIN verification key index from 0 through 6." },
|
||||
{ name: "Expected PVV", type: "string", value: "", comment: "Stored PVV value to compare against." },
|
||||
{ name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the assembled PVV input and validity result." },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [pvkHex, pan, pvki, expectedPvv, outputJson] = args;
|
||||
const result = verifyVisaPvv(pvkHex, pan, pvki, input, expectedPvv);
|
||||
return outputJson ? JSON.stringify(result, null, 4) : String(result.valid);
|
||||
}
|
||||
}
|
||||
|
||||
export default VerifyVISAPVV;
|
||||
@ -313,7 +313,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate Payment MAC",
|
||||
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", 8, false]
|
||||
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -324,7 +324,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate Payment MAC",
|
||||
args: ["Hex", "HMAC SHA-256", "00112233445566778899AABBCCDDEEFF", "Hex", "", 8, false]
|
||||
args: ["Hex", "HMAC SHA-256", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -335,7 +335,40 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate Payment MAC",
|
||||
args: ["Hex", "DUKPT MAC Request CMAC", "0123456789ABCDEFFEDCBA9876543210", "Hex", "FFFF9876543210E00008", 8, false]
|
||||
args: ["Hex", "DUKPT MAC Request CMAC", "0123456789ABCDEFFEDCBA9876543210", "Hex", "FFFF9876543210E00008", "Method 1", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Generate Payment MAC: ISO 9797-1 Algorithm 1",
|
||||
input: "1122334455667788",
|
||||
expectedOutput: "0C949BCDEF6FDF1D",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate Payment MAC",
|
||||
args: ["Hex", "ISO 9797-1 Algorithm 1", "0123456789ABCDEFFEDCBA9876543210", "Hex", "", "Method 1", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Generate Payment MAC: ISO 9797-1 Algorithm 3",
|
||||
input: "1122334455667788",
|
||||
expectedOutput: "7E2AEA5CF35FDC0E",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate Payment MAC",
|
||||
args: ["Hex", "ISO 9797-1 Algorithm 3", "0123456789ABCDEFFEDCBA9876543210", "Hex", "", "Method 2", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Generate Payment MAC: AS2805-4.1",
|
||||
input: "1122334455667788",
|
||||
expectedOutput: "3EB3B72576BBBE83",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate Payment MAC",
|
||||
args: ["Hex", "AS2805-4.1", "0123456789ABCDEFFEDCBA9876543210", "Hex", "", "Method 1", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -346,6 +379,7 @@ TestRegister.addTests([
|
||||
method: "AES-CMAC",
|
||||
inputFormat: "Hex",
|
||||
inputHex: "1122334455667788",
|
||||
paddingMethod: null,
|
||||
outputBytes: 8,
|
||||
fullMacHex: "339AF1AD1650E908A794284D91DC6D29",
|
||||
macHex: "339AF1AD1650E908",
|
||||
@ -356,7 +390,48 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Verify Payment MAC",
|
||||
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "339AF1AD1650E908", true]
|
||||
args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", "339AF1AD1650E908", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Generate EMV MAC: issuer script sample",
|
||||
input: "8424000008999E57FD0F47CACE0007",
|
||||
expectedOutput: "22CB48394DFD1977",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate EMV MAC",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Verify EMV MAC: issuer script sample",
|
||||
input: "8424000008999E57FD0F47CACE0007",
|
||||
expectedOutput: JSON.stringify({
|
||||
algorithm: "EMV MAC",
|
||||
paddingMethod: "Method 2",
|
||||
inputHex: "8424000008999E57FD0F47CACE0007",
|
||||
fullMacHex: "22CB48394DFD1977",
|
||||
macHex: "22CB48394DFD1977",
|
||||
expectedMacHex: "22CB48394DFD1977",
|
||||
valid: true
|
||||
}, null, 4),
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Verify EMV MAC",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "22CB48394DFD1977", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Generate EMV MAC For PIN Change: issuer script sample",
|
||||
input: "00A4040008A000000004101080D80500000001010A04000000000000",
|
||||
expectedOutput: "C0F24786EF1C4522",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate EMV MAC For PIN Change",
|
||||
args: ["67FB27C75580EFE7", "0123456789ABCDEFFEDCBA9876543210", 8, false]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -396,6 +471,110 @@ TestRegister.addTests([
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Generate IBM 3624 PIN Offset: known sample",
|
||||
input: "1234",
|
||||
expectedOutput: JSON.stringify({
|
||||
pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210",
|
||||
pinValidationData: "5432101234567890",
|
||||
pinValidationDataPadCharacter: "F",
|
||||
pinLength: 4,
|
||||
validationBlockHex: "5432101234567890",
|
||||
encryptedValidationBlockHex: "8A3712EE04F010A0",
|
||||
decimalized: "8037124404501000",
|
||||
naturalPin: "8037",
|
||||
pin: "1234",
|
||||
pinOffset: "3207"
|
||||
}, null, 4),
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate IBM 3624 PIN Offset",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "0123456789012345", "5432101234567890", "F", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Verify IBM 3624 PIN: known sample",
|
||||
input: "1234",
|
||||
expectedOutput: JSON.stringify({
|
||||
pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210",
|
||||
pinValidationData: "5432101234567890",
|
||||
pinValidationDataPadCharacter: "F",
|
||||
pinLength: 4,
|
||||
validationBlockHex: "5432101234567890",
|
||||
encryptedValidationBlockHex: "8A3712EE04F010A0",
|
||||
decimalized: "8037124404501000",
|
||||
naturalPin: "8037",
|
||||
pin: "1234",
|
||||
pinOffset: "3207",
|
||||
expectedPinOffset: "3207",
|
||||
valid: true
|
||||
}, null, 4),
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Verify IBM 3624 PIN",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "0123456789012345", "5432101234567890", "F", "3207", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Generate VISA PVV: known sample",
|
||||
input: "1234",
|
||||
expectedOutput: JSON.stringify({
|
||||
pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210",
|
||||
pan: "5432101234567890",
|
||||
pinVerificationKeyIndex: 1,
|
||||
pin: "1234",
|
||||
pvvInput: "1012345678911234",
|
||||
encryptedPvvInputHex: "6A77E65CFE349D60",
|
||||
pvv: "6077"
|
||||
}, null, 4),
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate VISA PVV",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Verify VISA PVV: known sample",
|
||||
input: "1234",
|
||||
expectedOutput: JSON.stringify({
|
||||
pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210",
|
||||
pan: "5432101234567890",
|
||||
pinVerificationKeyIndex: 1,
|
||||
pin: "1234",
|
||||
pvvInput: "1012345678911234",
|
||||
encryptedPvvInputHex: "6A77E65CFE349D60",
|
||||
pvv: "6077",
|
||||
expectedPvv: "6077",
|
||||
valid: true
|
||||
}, null, 4),
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Verify VISA PVV",
|
||||
args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, "6077", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Generate AS2805 KEK Validation: response sample",
|
||||
input: "0123456789ABCDEFFEDCBA9876543210",
|
||||
expectedOutput: JSON.stringify({
|
||||
validationType: "KekValidationResponse",
|
||||
deriveKeyAlgorithm: "TDES_2KEY",
|
||||
randomKeySendVariantMask: "VARIANT_MASK_82",
|
||||
keyCheckValue: "08D7B4",
|
||||
randomKeySend: "9217DC67B8763BABCFDF3DADFCD0F84A",
|
||||
randomKeyReceive: "6DE823984789C4543020C252032F07B5"
|
||||
}, null, 4),
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "Generate AS2805 KEK Validation",
|
||||
args: ["KekValidationResponse", "TDES_2KEY", "VARIANT_MASK_82", "9217DC67B8763BABCFDF3DADFCD0F84A", true]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Verify Payment PIN Data: ISO Format 0",
|
||||
input: "041215FEDCBA9876",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user