用Java验证Paypal Restful Webhook签名

时间:2015-04-15 03:27:24

标签: java rest servlets encryption paypal

我有一个使用Paypal Restful Webhooks的Java servlet。在验证请求签名时,我按照

中的详细说明进行操作

https://developer.paypal.com/webapps/developer/docs/integration/direct/rest-webhooks-overview/#event-security

但是,即使按照paypal文档中的伪代码到达点,我似乎也无法成功验证签名。这是我用来验证的实际代码(以下方法总是返回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);只是一个帮助方法,用于检索GET请求的结果。它返回一个有效的证书。

我能想到的可能罪魁祸首是: 1.计算CRC32在某种程度上与Paypal在其结束时计算它的方式不同。 2. Paypal URL中的公钥与Paypal使用的私钥不匹配。

以下是我从servlet请求中获取有效负载的方法:

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();
}

我使用https://developer.paypal.com/developer/webhooksSimulator进行测试。

2 个答案:

答案 0 :(得分:3)

事实证明上面的代码工作正常。问题是如何正确测试它。

https://developer.paypal.com/developer/webhooksSimulator中的Paypal的webhook模拟器仅用于连接测试。

https://developer.paypal.com/webapps/developer/docs/api/#simulate-a-webhook-event中详细介绍的webhook模拟API可用于验证签名,但有一个未记录的文件AFAIK。模拟API接受webhook_id或webhook url作为参数(或两者)。根据我的测试,如果您只指定了网址,则网络链接无法正确验证。但是,如果您指定webhook_id,则webhook验证过程会相应地运行。

不幸的是,我一直在使用模拟API进行测试,只指定了网址。感谢@wpohl让我想到在模拟API中使用webhook_id。

答案 1 :(得分:1)

所述文档" Webhook ID:这是传递事件的目标URL的webhook资源的ID"。因此,它不是事件的ID(在我的情况下:" WH-36432655JG839693T-2LC486465H4712400")。但是如果你使用webhook模拟器,你就没有持久的#h; webhook资源"。

我创建了一个webhook并触发了通知。并且使用该ID(在我的情况下" 3EB298650W722680T")它可以工作。