Skip to content

Commit b3c5098

Browse files
authored
HMAC Troubleshooting tool (#1452)
* Add HMAC calculation tool * Calculate using Adyen library * Improve comments * Cleanup pom, add Java library * Add HMAC calculation for other webhooks * Ignore all target folders * Improve error message upon HMAC calculation failure * Add README * Log size of payload
1 parent fc961b9 commit b3c5098

File tree

9 files changed

+335
-7
lines changed

9 files changed

+335
-7
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.idea
22
*.iml
3-
/target/**
3+
**/target/**
44
.DS_Store
55
.vscode

src/main/java/com/adyen/util/HMACValidator.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,11 @@ public class HMACValidator {
4242
// To calculate the HMAC SHA-256
4343
public String calculateHMAC(String data, String key) throws IllegalArgumentException, SignatureException {
4444
try {
45-
if (data == null || key == null) {
46-
throw new IllegalArgumentException();
45+
if (data == null) {
46+
throw new IllegalArgumentException("payload data is not provided");
47+
}
48+
if (key == null) {
49+
throw new IllegalArgumentException("HMAC key is not provided");
4750
}
4851

4952
byte[] rawKey = DatatypeConverter.parseHexBinary(key);
@@ -62,9 +65,9 @@ public String calculateHMAC(String data, String key) throws IllegalArgumentExcep
6265
// Base64-encode the hmac
6366
return new String(Base64.encodeBase64(rawHmac));
6467
} catch (IllegalArgumentException e) {
65-
throw new IllegalArgumentException("Missing data or key.");
68+
throw new IllegalArgumentException("Missing data or key: " + e.getMessage());
6669
} catch (Exception e) {
67-
throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
70+
throw new SignatureException("Failed to generate HMAC: " + e.getMessage());
6871
}
6972
}
7073

src/test/java/com/adyen/util/HMACValidatorTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public void testCalculateHMACNullPayload() {
154154
try {
155155
new HMACValidator().calculateHMAC((String)null, HMAC_KEY);
156156
} catch (IllegalArgumentException e) {
157-
assertEquals("Missing data or key.", e.getMessage());
157+
assertEquals("Missing data or key: payload data is not provided", e.getMessage());
158158
} catch (SignatureException e) {
159159
fail();
160160
}
@@ -165,7 +165,7 @@ public void testCalculateHMACNullKey() {
165165
try {
166166
new HMACValidator().calculateHMAC("TestPayload", null);
167167
} catch (IllegalArgumentException e) {
168-
assertEquals("Missing data or key.", e.getMessage());
168+
assertEquals("Missing data or key: HMAC key is not provided", e.getMessage());
169169
} catch (SignatureException e) {
170170
fail();
171171
}

tools/hmac/CalculateHmacPayments.java

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import java.io.File;
2+
3+
import com.adyen.model.notification.NotificationRequestItem;
4+
import com.adyen.util.HMACValidator;
5+
import com.adyen.model.notification.NotificationRequest;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
8+
import java.nio.file.Files;
9+
import java.nio.file.Paths;
10+
11+
/**
12+
Script to calculate the HMAC signature of Payments webhooks (where the signature is calculated considering
13+
a subset of the fields in the payload - i.e. NotificationRequestItem object)
14+
15+
Run with: mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPayments -Dexec.args= "{hmacKey} {path_to_JSON_file}"
16+
17+
cd tools/hmac
18+
mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPayments -Dexec.args="11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload.json"
19+
20+
*/
21+
public class CalculateHmacPayments {
22+
23+
public static void main(String[] args) {
24+
if (args.length != 2) {
25+
System.err.println("‼️Error running the script");
26+
System.err.println("Usage: mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPayments -Dexec.args= \"{hmacKey} {path_to_JSON_file}\"");
27+
System.exit(1);
28+
}
29+
30+
String hmacKey = args[0];
31+
String jsonFilePath = args[1];
32+
33+
try {
34+
35+
HMACValidator hmacValidator = new HMACValidator();
36+
String content = loadFromJsonAsString(jsonFilePath);
37+
38+
System.out.println("********");
39+
System.out.println("Payload file: " + jsonFilePath);
40+
System.out.println("Payload length: " + content.length());
41+
42+
NotificationRequest notificationRequest = loadFromJson(jsonFilePath);
43+
// fetch first notificationRequestItem
44+
NotificationRequestItem notificationRequestItem = notificationRequest.getNotificationItems().get(0);
45+
46+
System.out.println("********");
47+
String hmacSignature = hmacValidator.calculateHMAC(notificationRequestItem, hmacKey);
48+
49+
System.out.println("HMAC signature: " + hmacSignature);
50+
} catch (Exception e) {
51+
e.printStackTrace();
52+
}
53+
}
54+
55+
/**
56+
* Load payload from JSON file
57+
* @param filepath
58+
* @return
59+
* @throws Exception
60+
*/
61+
private static NotificationRequest loadFromJson(String filepath) throws Exception {
62+
ObjectMapper objectMapper = new ObjectMapper();
63+
return objectMapper.readValue(new File(filepath), NotificationRequest.class);
64+
}
65+
66+
/**
67+
* Load payload as String from JSON file
68+
* @param filepath
69+
* @return
70+
* @throws Exception
71+
*/
72+
private static String loadFromJsonAsString(String filepath) throws Exception {
73+
return new String(Files.readAllBytes(Paths.get(filepath)));
74+
}
75+
76+
}

tools/hmac/CalculateHmacPlatform.java

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import com.adyen.util.HMACValidator;
2+
3+
import java.nio.file.Files;
4+
import java.nio.file.Paths;
5+
6+
/**
7+
Script to calculate the HMAC signature of Payments webhooks Banking/Management webhooks (where the signature is calculated
8+
over the entire webhook payload)
9+
10+
Run with: mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPlatform -Dexec.args= "{hmacKey} {path_to_JSON_file}"
11+
12+
cd tools/hmac
13+
mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPlatform -Dexec.args="11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload2.json"
14+
15+
*/
16+
public class CalculateHmacPlatform {
17+
18+
public static void main(String[] args) {
19+
if (args.length != 2) {
20+
System.err.println("‼️Error running the script");
21+
System.err.println("Usage: java CalculateHmacPlatform <hmacKey> <payload.json>");
22+
System.exit(1);
23+
}
24+
25+
String hmacKey = args[0];
26+
String jsonFilePath = args[1];
27+
28+
System.out.println("Calculating HMAC signature with payload from " + jsonFilePath);
29+
30+
try {
31+
32+
HMACValidator hmacValidator = new HMACValidator();
33+
34+
String data = loadFromJson(jsonFilePath);
35+
36+
System.out.println("********");
37+
System.out.println("Payload file: " + jsonFilePath);
38+
System.out.println("Payload length: " + data.length());
39+
40+
String hmacSignature = hmacValidator.calculateHMAC(data, hmacKey);
41+
42+
System.out.println("********");
43+
System.out.println("HMAC signature: " + hmacSignature);
44+
} catch (Exception e) {
45+
e.printStackTrace();
46+
}
47+
}
48+
49+
/**
50+
* Load payload as String from JSON file
51+
* @param filepath
52+
* @return
53+
* @throws Exception
54+
*/
55+
private static String loadFromJson(String filepath) throws Exception {
56+
return new String(Files.readAllBytes(Paths.get(filepath)));
57+
}
58+
}

tools/hmac/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## HMAC Tools
2+
3+
This folder contains the tools/scripts to calculate the HMAC signature of the webhook payload.
4+
They can be used to troubleshoot the HMAC signature calculation.
5+
6+
Check tools/hmac/pom.xml to confirm the Adyen Java API library version
7+
8+
Note: make sure you are using the HMAC key used to generate the signature associated with the payload in the JSON file
9+
10+
### Payments webhooks
11+
12+
Copy the content of the webhook in the payload.json (or provide a different file), then run with:
13+
`mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPayments -Dexec.args= "{hmacKey} {path_to_JSON_file}"`
14+
```
15+
cd tools/hmac
16+
17+
mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPayments -Dexec.args="11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload.json"
18+
```
19+
20+
### Platform webhooks (AfP, Management API, etc..)
21+
22+
Copy the content of the webhook in the payload2.json (or provide a different file), then run with:
23+
`mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPlatform -Dexec.args= "{hmacKey} {path_to_JSON_file}"`
24+
```
25+
cd tools/hmac
26+
27+
mvn clean compile exec:java -Dexec.mainClass=CalculateHmacPlatform -Dexec.args="11223344D785FBAE710E7F943F307971BB61B21281C98C9129B3D4018A57B2EB payload2.json"
28+
```

tools/hmac/payload.json

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"live": "false",
3+
"notificationItems": [
4+
{
5+
"NotificationRequestItem": {
6+
"additionalData": {
7+
"cardSummary": "0010",
8+
"hmacSignature": "e7qQumH1fdt5NHb6e62jq6hIGUCpfhAAFXhUXqXcJAQ=",
9+
"expiryDate": "03\/2030",
10+
"recurring.contractTypes": "ONECLICK,RECURRING",
11+
"cardBin": "222241",
12+
"recurring.recurringDetailReference": "DQWW2BQ9FX2R7475",
13+
"recurringProcessingModel": "Subscription",
14+
"metadata.orderTransactionId": "63",
15+
"alias": "F758505419855455",
16+
"paymentMethodVariant": "m1",
17+
"acquirerReference": "JQBMF3P3FJM",
18+
"issuerBin": "22224107",
19+
"cardIssuingCountry": "NL",
20+
"authCode": "060494",
21+
"cardHolderName": "Checkout Test",
22+
"PaymentAccountReference": "Jj3gVfMpPcpKzmHrHmpxqgfsvGaVa",
23+
"checkout.cardAddedBrand": "mc",
24+
"store": "None",
25+
"tokenization.shopperReference": "4d74f0727a318b13f85aee28ae556a08d7d9cf9e6d67b70a62e60e334aa7090d",
26+
"recurring.firstPspReference": "P9M9CTGJKKKKP275",
27+
"tokenization.storedPaymentMethodId": "DQWW2BQ9FX2R7475",
28+
"issuerCountry": "NL",
29+
"aliasType": "Default",
30+
"paymentMethod": "mc",
31+
"recurring.shopperReference": "4d74f0727a318b13f85aee28ae556a08d7d9cf9e6d67b70a62e60e334aa7090d",
32+
"cardPaymentMethod": "mc2"
33+
},
34+
"amount": {
35+
"currency": "EUR",
36+
"value": 32500
37+
},
38+
"eventCode": "AUTHORISATION",
39+
"eventDate": "2025-02-24T15:20:56+01:00",
40+
"merchantAccountCode": "Merchant 1",
41+
"merchantReference": "1143-R2",
42+
"operations": [
43+
"CANCEL",
44+
"CAPTURE",
45+
"REFUND"
46+
],
47+
"paymentMethod": "mc",
48+
"pspReference": "AB1234567890",
49+
"reason": "060494:0010:03\/2030",
50+
"success": "true"
51+
}
52+
}
53+
]
54+
}

tools/hmac/payload2.json

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"data": {
3+
"balancePlatform": "YOUR_BALANCE_PLATFORM",
4+
"accountHolder": {
5+
"contactDetails": {
6+
"email": "[email protected]",
7+
"phone": {
8+
"number": "0612345678",
9+
"type": "mobile"
10+
},
11+
"address": {
12+
"houseNumberOrName": "23",
13+
"city": "Amsterdam",
14+
"country": "NL",
15+
"postalCode": "12345",
16+
"street": "Main Street 1"
17+
}
18+
},
19+
"description": "Shelly Eller",
20+
"legalEntityId": "LE00000000000000000001",
21+
"reference": "YOUR_REFERENCE-2412C",
22+
"capabilities": {
23+
"issueCard": {
24+
"enabled": true,
25+
"requested": true,
26+
"allowed": false,
27+
"verificationStatus": "pending"
28+
},
29+
"receiveFromTransferInstrument": {
30+
"enabled": true,
31+
"requested": true,
32+
"allowed": false,
33+
"verificationStatus": "pending"
34+
},
35+
"sendToTransferInstrument": {
36+
"enabled": true,
37+
"requested": true,
38+
"allowed": false,
39+
"verificationStatus": "pending"
40+
},
41+
"sendToBalanceAccount": {
42+
"enabled": true,
43+
"requested": true,
44+
"allowed": false,
45+
"verificationStatus": "pending"
46+
},
47+
"receiveFromBalanceAccount": {
48+
"enabled": true,
49+
"requested": true,
50+
"allowed": false,
51+
"verificationStatus": "pending"
52+
}
53+
},
54+
"id": "AH00000000000000000001",
55+
"status": "active"
56+
}
57+
},
58+
"environment": "test",
59+
"timestamp": "2024-12-15T15:42:03+01:00",
60+
"type": "balancePlatform.accountHolder.created"
61+
}

tools/hmac/pom.xml

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.adyen</groupId>
8+
<artifactId>hmac-troubleshooting</artifactId>
9+
<version>1.0.0-SNAPSHOT</version>
10+
<packaging>jar</packaging>
11+
12+
<name>HMAC Troubleshooting - Adyen Java library</name>
13+
<description>Scripts to troubleshoot the HMAC validation</description>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>com.adyen</groupId>
18+
<artifactId>adyen-java-api-library</artifactId>
19+
<version>35.0.0</version>
20+
</dependency>
21+
</dependencies>
22+
23+
<build>
24+
<sourceDirectory>.</sourceDirectory>
25+
<plugins>
26+
<plugin>
27+
<groupId>org.apache.maven.plugins</groupId>
28+
<artifactId>maven-compiler-plugin</artifactId>
29+
<version>3.8.1</version>
30+
<configuration>
31+
<source>11</source>
32+
<target>11</target>
33+
</configuration>
34+
</plugin>
35+
<!-- Run with mvn exec:java-->
36+
<plugin>
37+
<groupId>org.codehaus.mojo</groupId>
38+
<artifactId>exec-maven-plugin</artifactId>
39+
<version>3.5.0</version>
40+
41+
<configuration>
42+
<executable>maven</executable>
43+
</configuration>
44+
</plugin>
45+
</plugins>
46+
</build>
47+
48+
</project>

0 commit comments

Comments
 (0)