问题
I have a Java servlet that consumes Paypal Restful Webhooks. In verifying the request signature, I followed the instructions detailed in
https://developer.paypal.com/webapps/developer/docs/integration/direct/rest-webhooks-overview/#event-security
However, I can't seem to successfully verify the signature even while following the pseudocode in the paypal doc to the dot. Here's the actual code that I use to verify (the below method always returns false):
private static final String WEBHOOK_ID = "4HL82785RC0XXXXXX";
private boolean isValidRequest(HttpServletRequest req, String payload) throws Exception {
String transmissionId = req.getHeader("PAYPAL-TRANSMISSION-ID");
String timeStamp = req.getHeader("PAYPAL-TRANSMISSION-TIME");
String crc32 = getCrcSum(payload);
String expectedSignature = String.format("%s|%s|%s|%s", transmissionId, timeStamp, WEBHOOK_ID, crc32);
System.out.println("EXPECTED SIG:" + expectedSignature);
String actualSignatureEncoded = req.getHeader("PAYPAL-TRANSMISSION-SIG");
String certUrl = req.getHeader("PAYPAL-CERT-URL");
String algo = req.getHeader("PAYPAL-AUTH-ALGO");
Signature shaWithRsa = Signature.getInstance(algo);
byte[] certData = HttpUtils.getBytes(new URL(certUrl), null);
Certificate certificate = X509Certificate.getInstance(certData);
shaWithRsa.initVerify(certificate.getPublicKey());
shaWithRsa.update(expectedSignature.getBytes());
byte[] actualSignature = Base64.decodeBase64(actualSignatureEncoded.getBytes());
return shaWithRsa.verify(actualSignature);
}
private static String getCrcSum(final String body) {
byte[] bytes = body.getBytes();
CRC32 checkSum = new CRC32();
checkSum.update(bytes, 0, bytes.length);
return String.valueOf(checkSum.getValue());
//return Long.toHexString(checkSum.getValue());
}
HttpUtils.getBytes(new URL(certUrl), null); is just a helper method for retrieving the results of a GET request. It returns a valid Certificate.
Probable culprits that I can think of are: 1. Computation of CRC32 is somehow different from how Paypal computes it on their end. 2. Public key from the Paypal URL does not match the Private key used by Paypal.
Here is how I get the payload from the servlet request:
String payload = getString(req.getInputStream());
private static String getString(InputStream is) {
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String line;
try {
br = new BufferedReader(new InputStreamReader(is));
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
I use https://developer.paypal.com/developer/webhooksSimulator for testing.
回答1:
It turns out that the code above is working properly. The issue was how to properly test it.
Paypal's webhook simulator in https://developer.paypal.com/developer/webhooksSimulator is only meant for connectivity tests.
The webhook simulation API detailed in https://developer.paypal.com/webapps/developer/docs/api/#simulate-a-webhook-event will work for verification of the signature, but there is a catch that was NOT documented AFAIK. The simulate API accepts either the webhook_id or the webhook url as a parameter (or both). As per my tests, if you only specify the url, the webhook does not verify properly. But if you specify the webhook_id, the webhook verification process works accordingly.
Unfortunately for me, I have been testing using the simulate API while only specifying the url. Thanks to @wpohl for giving me the idea to use the webhook_id in the simulate API.
回答2:
The documentation stated "Webhook ID: This is the ID of the webhook resource for the destination URL on which the event is delivered". So it is not the id of the event (in my case: "WH-36432655JG839693T-2LC486465H4712400"). But if you use the webhook simulator you didn't have a persistent "webhook resource".
I created an webhook and triggered a notification. and with that ID (In my case "3EB298650W722680T") it works.
来源:https://stackoverflow.com/questions/29641095/verify-paypal-restful-webhook-signature-in-java