Skip to content

Commit 9ffe0ca

Browse files
committed
Automatic ARQC padding
1 parent 646a8f9 commit 9ffe0ca

File tree

7 files changed

+52
-54
lines changed

7 files changed

+52
-54
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,16 +342,19 @@ and the calculated ARQC value will be added as an additional subfield (tag `9F26
342342

343343
**Note:** Changed behaviour! For v1.0 the data in this field is *appended* to the automatically extracted fields,
344344
whereas as of v1.1 data in this field overrides the extraction.
345-
- *Padding (hex)* (since v1.1): Optional, additional padding bytes to append to transaction data before ARQC calculation
346-
(leave blank for zero-padding, 80 for ISO9797-1 Method 2).
347345

348-
**Note:** Changed behaviour! Until v1.2 the input field
346+
**Note: Changed behaviour!**
347+
- Until v1.2 the input field
349348
*Session Key Derivation Method* (how to derive the UDK from the Master Key) had to be set explicitly.
350349
As of v1.3 this is obsolete as it will be determined automatically by evaluating the Issuer Application Data field (tag `9F10`).
351350

352-
Likewise, as of v1.3, the JMeter properties `jmeter.iso8583.arqcInputTags` and `jmeter.iso8583.arqcFromFullIADForCVNs` have been
351+
- Likewise, as of v1.3, the JMeter properties `jmeter.iso8583.arqcInputTags` and `jmeter.iso8583.arqcFromFullIADForCVNs` have been
353352
removed as jPOS [handles](https://github.com/jpos/jPOS/pull/499) this cryptogram logic internally.
354353

354+
- From v1.1 - v1.3 the field *Padding (hex)* would specify optional, additional padding bytes to append to transaction data
355+
before ARQC calculation. As of v1.4 the padding will be [handled](https://github.com/jpos/jPOS/pull/577) automatically,
356+
depending on the Issuer Application Data.
357+
355358
<h3 id="functions">Crypto Functions (since v1.1)</h3>
356359

357360
#### __calculateCVV

pom.xml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,22 @@
4343
<artifactId>ApacheJMeter_core</artifactId>
4444
<version>4.0</version>
4545
<scope>provided</scope>
46+
<exclusions>
47+
<exclusion>
48+
<groupId>org.bouncycastle</groupId>
49+
<artifactId>*</artifactId>
50+
</exclusion>
51+
</exclusions>
4652
</dependency>
4753
<dependency>
4854
<groupId>org.jpos</groupId>
4955
<artifactId>jpos</artifactId>
50-
<version>2.1.8</version>
56+
<version>2.1.10-SNAPSHOT</version>
5157
</dependency>
5258
<dependency>
5359
<groupId>org.bouncycastle</groupId>
54-
<artifactId>bcprov-jdk15on</artifactId>
55-
<version>1.69</version>
60+
<artifactId>bcprov-lts8on</artifactId>
61+
<version>2.73.2</version>
5662
</dependency>
5763
<dependency>
5864
<groupId>junit</groupId>

src/main/java/nz/co/breakpoint/jmeter/iso8583/ISO8583Crypto.java

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
import org.jpos.emv.IssuerApplicationData;
1717
import org.jpos.emv.cryptogram.CryptogramSpec;
1818
import org.jpos.iso.*;
19-
import org.jpos.security.MKDMethod;
20-
import org.jpos.security.SKDMethod;
2119
import org.jpos.security.jceadapter.JCEHandlerException;
2220
import org.jpos.tlv.ISOTaggedField;
2321
import org.jpos.tlv.TLVList;
@@ -45,8 +43,7 @@ public class ISO8583Crypto extends AbstractTestElement
4543
IMKAC = "imkac",
4644
PAN = "pan",
4745
PSN = "psn",
48-
TXNDATA = "txnData",
49-
PADDING= "padding";
46+
TXNDATA = "txnData";
5047

5148
static final String[] macAlgorithms = new String[]{"", "DESEDE", "ISO9797ALG3MACWITHISO7816-4PADDING"};
5249

@@ -177,8 +174,7 @@ protected void encryptPINBlock(ISO8583Sampler sampler) {
177174
}
178175

179176
protected void calculateARQC(ISO8583Sampler sampler) {
180-
final String hexKey = getImkac(), fieldNo = getIccField(),
181-
txnData = getTxnData(), padding = getPadding();
177+
final String hexKey = getImkac(), fieldNo = getIccField();
182178
final ISOMsg msg = sampler.getRequest();
183179

184180
if (fieldNo == null || fieldNo.isEmpty() || !msg.hasField(fieldNo)) {
@@ -216,7 +212,7 @@ else if (f.getBytes() != null && f.getBytes().length != 0)
216212
log.error("ARQC input extraction failed {}", e.toString(), e);
217213
return;
218214
}
219-
// Then, parse IAD for cryptogram version and key derivation methods:
215+
// Then, parse IAD for cryptogram spec:
220216
IssuerApplicationData iad;
221217
try {
222218
iad = new IssuerApplicationData(emvData.get(ISSUER_APPLICATION_DATA_0x9F10.getTagNumber()));
@@ -225,27 +221,21 @@ else if (f.getBytes() != null && f.getBytes().length != 0)
225221
return;
226222
}
227223
CryptogramSpec cSpec = iad.getCryptogramSpec();
228-
MKDMethod mkdMethod = cSpec.getMKDMethod();
229-
SKDMethod skdMethod = cSpec.getSKDMethod();
230-
log.debug("Detected MKD Method {}, SKD Method {}", mkdMethod, skdMethod);
224+
log.debug("Detected cryptogram {}", cSpec.getDataBuilder().getClass().getSimpleName());
231225

232226
// Next, build input data from explicit data or message fields:
233-
String transactionData = txnData;
227+
String transactionData = getTxnData();
234228
if (transactionData == null || transactionData.isEmpty()) {
235229
TLVList tlvList = new TLVList();
236-
emvData.entrySet().stream().forEach(e -> tlvList.append(e.getKey(), e.getValue()));
237-
transactionData = cSpec.getDataBuilder().buildARQCRequest(tlvList, iad);
230+
emvData.forEach(tlvList::append);
231+
transactionData = cSpec.getDataBuilder().buildARQCRequest_padded(tlvList, iad);
238232
if (transactionData.contains("null")) {
239233
log.warn("EMV data incomplete?");
240234
}
241235
}
242-
// Optionally, apply custom padding:
243-
if (padding != null) {
244-
transactionData += padding;
245-
}
246236

247237
// Lastly, the actual calculation:
248-
final String arqc = securityModule.calculateARQC(mkdMethod, skdMethod, hexKey,
238+
final String arqc = securityModule.calculateARQC(cSpec.getMKDMethod(), cSpec.getSKDMethod(), hexKey,
249239
Optional.ofNullable(getPan())
250240
.orElse(emvData.getOrDefault(APPLICATION_PRIMARY_ACCOUNT_NUMBER_0x5A.getTagNumber(), "")),
251241
Optional.ofNullable(getPsn())
@@ -289,7 +279,4 @@ else if (f.getBytes() != null && f.getBytes().length != 0)
289279

290280
public String getTxnData() { return getPropertyAsString(TXNDATA); }
291281
public void setTxnData(String txnData) { setProperty(TXNDATA, txnData); }
292-
293-
public String getPadding() { return getPropertyAsString(PADDING); }
294-
public void setPadding(String padding) { setProperty(PADDING, padding); }
295282
}

src/main/java/nz/co/breakpoint/jmeter/iso8583/ISO8583CryptoBeanInfo.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public ISO8583CryptoBeanInfo() {
4545
p.setValue(DEFAULT, "");
4646

4747
createPropertyGroup("ARQC", new String[]{
48-
ICCFIELD, IMKAC, PAN, PSN, TXNDATA, PADDING,
48+
ICCFIELD, IMKAC, PAN, PSN, TXNDATA,
4949
});
5050

5151
p = property(ICCFIELD);
@@ -67,9 +67,5 @@ public ISO8583CryptoBeanInfo() {
6767
p = property(TXNDATA);
6868
p.setValue(NOT_UNDEFINED, Boolean.TRUE);
6969
p.setValue(DEFAULT, "");
70-
71-
p = property(PADDING);
72-
p.setValue(NOT_UNDEFINED, Boolean.TRUE);
73-
p.setValue(DEFAULT, "");
7470
}
7571
}

src/main/java/nz/co/breakpoint/jmeter/iso8583/SecurityModule.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package nz.co.breakpoint.jmeter.iso8583;
22

3-
import java.lang.reflect.Field;
4-
import java.lang.reflect.InvocationTargetException;
5-
import java.lang.reflect.Method;
63
import java.security.Key;
74
import javax.crypto.spec.SecretKeySpec;
85
import org.apache.jmeter.util.JMeterUtils;
@@ -12,7 +9,6 @@
129
import org.jpos.core.SimpleConfiguration;
1310
import org.jpos.iso.ISOUtil;
1411
import org.jpos.security.*;
15-
import org.jpos.security.jceadapter.JCEHandler;
1612
import org.jpos.security.jceadapter.JCEHandlerException;
1713
import org.jpos.security.jceadapter.JCESecurityModule;
1814
import org.slf4j.Logger;
@@ -139,7 +135,7 @@ public String calculatePINBlock(String pin, String format, String pan) {
139135

140136
public String generateDESKey(String length) {
141137
try {
142-
Short bits = Short.parseShort(length);
138+
short bits = Short.parseShort(length);
143139
Key key = this.jceHandler.generateDESKey(bits);
144140
// ensure expected key length (https://github.com/jpos/jPOS/blob/v2_1_8/jpos/src/main/java/org/jpos/security/jceadapter/JCEHandler.java#L111-L113):
145141
return ISOUtil.byte2hex(key.getEncoded()).substring(0, bits/4);

src/main/resources/nz/co/breakpoint/jmeter/iso8583/ISO8583CryptoResources.properties

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,3 @@ psn.displayName=Account Sequence Number
2424
psn.shortDescription=Input parameter for session key derivation (2 hex digits). If left blank, it will be extracted from the ICC Data field (tag 5F34).
2525
txnData.displayName=Transaction Data (hex)
2626
txnData.shortDescription=Explicit input data for ARQC calculation. If left blank, the relevant fields will be automatically extracted from the ICC Data field.
27-
padding.displayName=Padding (hex)
28-
padding.shortDescription=Optional padding bytes to append to transaction data before ARQC calculation (leave blank for zero-padding, 80 for ISO9797-1 Method 2)

src/test/java/nz/co/breakpoint/jmeter/iso8583/ISO8583CryptoTest.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package nz.co.breakpoint.jmeter.iso8583;
22

3+
import java.util.Arrays;
34
import java.util.Collection;
45
import java.util.stream.Collectors;
6+
import org.jpos.iso.ISOException;
57
import org.jpos.iso.ISOMsg;
8+
import org.jpos.tlv.ISOTaggedField;
69
import org.junit.Before;
710
import org.junit.Test;
11+
import static org.jpos.emv.EMVStandardTagType.APPLICATION_CRYPTOGRAM_0x9F26;
812
import static org.jpos.emv.EMVStandardTagType.ISSUER_APPLICATION_DATA_0x9F10;
913
import static org.junit.Assert.*;
1014

@@ -120,7 +124,7 @@ public void shouldCalculateMACInLastField() {
120124
}
121125

122126
@Test
123-
public void shouldCalculateARQC() {
127+
public void shouldCalculateARQC() throws ISOException {
124128
sampler.setFields(iccData);
125129
instance.setImkac(DEFAULT_3DES_KEY);
126130
instance.setIccField("55");
@@ -129,23 +133,23 @@ public void shouldCalculateARQC() {
129133
ISOMsg msg = sampler.getRequest();
130134
assertTrue(msg.hasField("55.12"));
131135
assertTrue(msg.getString("55.12").matches("[0-9A-F]{16}"));
136+
assertEquals(APPLICATION_CRYPTOGRAM_0x9F26.getTagNumberHex(), ((ISOTaggedField)msg.getComponent("55.12")).getTag());
132137
}
133138

134139
@Test
135140
public void shouldOverrideARQCInputTags() {
136-
sampler.setFields(iccData);
137141
instance.setImkac(DEFAULT_3DES_KEY);
138142
instance.setIccField("55");
139143

144+
sampler.setFields(iccData);
140145
instance.setTxnData(""); // automatic extraction
141146
instance.process();
142-
143147
ISOMsg msg = sampler.getRequest();
144148
assertTrue(msg.hasField("55.12"));
145149
String arqc = msg.getString("55.12");
146150

147-
sampler.setFields(iccData);
148-
instance.setTxnData(iccData.stream().map(MessageField::getContent).collect(Collectors.joining())); // should result in the same arqc
151+
sampler.removeField("55.12"); // remove cryptogram field
152+
instance.setTxnData(iccData.stream().map(MessageField::getContent).collect(Collectors.joining())+"80"); // hex data input
149153
instance.process();
150154
msg = sampler.getRequest();
151155
assertTrue(msg.hasField("55.12"));
@@ -157,17 +161,25 @@ public void shouldHandleDifferentIssuersForARQCCalculation() {
157161
sampler.setFields(iccData);
158162
instance.setImkac(DEFAULT_3DES_KEY);
159163
instance.setIccField("55");
160-
for (String iad : new String[]{
161-
"06010A03000000", // Visa CVN10
162-
"06011203000000", // Visa CVN18
163-
"0210A00000000000000000000000000000FF", // MCHIP CVN10
164-
"0114020000044000DAC10000000000000000", // MCHIP CVN14
165-
}) {
166-
sampler.setFields(iccData.stream().filter(f -> !ISSUER_APPLICATION_DATA_0x9F10.getTagNumberHex().equals(f.getTag())).collect(Collectors.toList()));
167-
sampler.addField("55.11", iad, ISSUER_APPLICATION_DATA_0x9F10.getTagNumberHex());
164+
instance.setPan("4111111111111111");
165+
instance.setPsn("01");
166+
167+
Arrays.asList(
168+
new String[]{ "06010A03000000", "84D785136EFA29D1" }, // Visa CVN10
169+
new String[]{ "06011203000000", "4576E1E877E459EC" }, // Visa CVN18
170+
new String[]{ "0210A00000000000000000000000000000FF", "AC7FFD216E326F06" }, // MCHIP CVN16
171+
new String[]{ "0114020000044000DAC10000000000000000", "AB2400DF6F95530B" } // MCHIP CVN22
172+
).forEach(pair -> {
173+
String iad = pair[0], arqc = pair[1];
174+
sampler.setFields(iccData.stream()
175+
.map(f -> ISSUER_APPLICATION_DATA_0x9F10.getTagNumberHex().equals(f.getTag()) ?
176+
new MessageField(f.getName(), iad, f.getTag()) : f)
177+
.collect(Collectors.toList())
178+
);
168179
instance.process();
169180
assertTrue(sampler.getRequest().hasField("55.12"));
170-
}
181+
assertEquals(arqc, sampler.getRequest().getString("55.12"));
182+
});
171183
}
172184

173185
@Test

0 commit comments

Comments
 (0)