在 Javascript 中验证 Facebook signed_request 签名

Validate Facebook signed_request signature in Javascript

我正在使用 Classic ASP 构建 Facebook 主页应用。我一直无法匹配 Facebook 作为发布的第一部分传递到应用程序中的签名 signed_request

因为 VBScript 中密码学的库很少,我使用服务器端 Javascript 和来自 https://code.google.com/archive/p/crypto-js/

的 crypto-js 库

我尝试将 Facebook 文档 https://developers.facebook.com/docs/games/gamesonfacebook/login#parsingsr 中的 PHP 代码示例翻译成 Javascript。我可以生成 signed_request 有效负载的 HMAC SHA256 哈希,但它与 signed_request 签名不匹配。

我认为问题在于 Facebook 的签名格式不同。它看起来是二进制的 (~1抚Ö.....) 而我生成的 HMAC SHA256 哈希是一个十六进制字符串 (7f7e8f5f... ..)。在 Facebook 的 PHP 示例中,hash_hmac 函数使用原始二进制参数。所以我认为我需要将 Facebook 的签名转换为十六进制或将我的签名转换为二进制以便进行 "apples-to-apples" 比较并获得匹配。

这是我的代码:

/* Use the libraries from https://code.google.com/archive/p/crypto-js/
crypto-js/crypto-js.min.js
crypto-js/hmac-sha256.min.js
crypto-js/enc-base64.min.js
*/

var signedRequest = Request.queryString("signed_request")

var FB_APP_SECRET = "459f038.....";

var arSR = signedRequest.split(".");
var encodedSig = arSR[0];
var encodedPayload = arSR[1];

var payload = base64UrlDecode(encodedPayload);
var sig = base64UrlDecode(encodedSig);

var expectedSig;

expectedSig = CryptoJS.HmacSHA256(encodedPayload, FB_APP_SECRET); // Unaltered payload string; no match
expectedSig = CryptoJS.HmacSHA256(payload, FB_APP_SECRET); // base64-decoded payload string; no match

if (sig == expectedSig) {
    Response.write(payload);
} else {
    Response.write("Bad signature");
}

function base64UrlDecode(input) {
    // Replace characters and convert from base64.
    return Base64.decode(input.replace("-", "+").replace("_", "/"));
}

在查看有关编码的 crypto-js 文档后,我找到了解决方案。 crypto-js 提供的 de-/encoding 方法在 https://code.google.com/archive/p/crypto-js/ 底部的 'Encoders' 下列出(感谢 CBroe 的推动。)

解决方案是在签名上使用 .toString()。似乎 crypto-js 使用了阻止比较匹配的单词格式。为了坚持使用一个库,我也切换到使用 crypto-js 提供的 base64 解码。

这是我更新后的代码:

/* Use the libraries from https://code.google.com/archive/p/crypto-js/
crypto-js/crypto-js.min.js
crypto-js/hmac-sha256.min.js
crypto-js/enc-base64.min.js
*/

var signedRequest = Request.queryString("signed_request")

var FB_APP_SECRET = "459f038.....";

var arSR = signedRequest.split(".");
var encodedSig = arSR[0];
var encodedPayload = arSR[1];

var payload = base64UrlDecode(encodedPayload);
var sig = base64UrlDecode(encodedSig);

var expectedSig = CryptoJS.HmacSHA256(encodedPayload, FB_APP_SECRET); /******** Correct payload */

if (sig.toString() != expectedSig.toString()) { /******* Use .toString() to convert to normal strings */
    Response.write(payload);
} else {
    Response.write("Bad signature");
}

function base64UrlDecode(input) {
    return CryptoJS.enc.Base64.parse( /******** Decode */
        input.replace("-", "+").replace("_", "/") // Replace characters
    );
}

我最近为他们所需的用户数据删除 webhook 实现了这个。不再需要外部依赖:

const crypto = require('crypto');

function parseSignedRequest(signedRequest, secret) {
  const [signatureReceived, encodedPayload] = signedRequest.split('.', 2);
  const payload = b64decode(encodedPayload)
  const data = JSON.parse(payload);

  const hmac = crypto.createHmac('sha256', secret).update(payload);
  const expectedSignature = hmac.digest('base64');

  if (signatureReceived === expectedSignature) {
    return data;
  } else {
    throw new Error("Signature mismatch");
  }
}

function b64decode(data) {
  const buff = Buffer.from(data, 'base64');
  return buff.toString('ascii');
}

这是 their example PHP code. I also have a repo setup 的翻译,带有测试。