我使用GitHub webhook将事件传递给我的应用程序(GitHub的Hubot实例),并以sha1秘密进行保护。
我使用以下代码验证传入的webhooks上的哈希值
crypto = require('crypto')
signature = "sha1=" + crypto.createHmac('sha1', process.env.HUBOT_GITHUB_SECRET).update( new Buffer request.body ).digest('hex')
unless request.headers['x-hub-signature'] is signature
response.send "Signature not valid"
return
在webhook中传递的X-Hub-Signature标题如下所示
X-Hub-Signature:sha1 = 1cffc5d4c77a3f696ecd9c19dbc2575d22ffebd4
我按照GitHub的文档准确传递密钥和数据,但哈希总是不同。
这是GitHub的文档。 https://developer.github.com/v3/repos/hooks/#example
这是我最有可能误解的部分
secret:一个可选的字符串,它与HTTP请求一起作为X-Hub-Signature标头传递。使用secret作为密钥,将此头的值计算为正文的HMAC十六进制摘要。
谁能看到我出错的地方?
答案 0 :(得分:9)
似乎无法使用Buffer,但JSON.stringify();这是我的工作代码:
var
hmac,
calculatedSignature,
payload = req.body;
hmac = crypto.createHmac('sha1', config.github.secret);
hmac.update(JSON.stringify(payload));
calculatedSignature = 'sha1=' + hmac.digest('hex');
if (req.headers['x-hub-signature'] === calculatedSignature) {
console.log('all good');
} else {
console.log('not good');
}
答案 1 :(得分:3)
添加到Patrick's回答。使用crypto.timingSafeEqual来比较HMAC摘要或秘密值是很好的。方法如下:
const blob = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha1', process.env.GITHUB_WEBHOOK_SECRET);
const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`;
const theirSignature = req.get('X-Hub-Signature');
const bufferA = Buffer.from(ourSignature, 'utf8');
const bufferB = Buffer.from(theirSignature, 'utf8');
const safe = crypto.timingSafeEqual(bufferA, bufferB);
if (safe) {
console.log('Valid signature');
} else {
console.log('Invalid signature');
}
要了解更多关于安全比较(例如timingEqual)和简单= ===检查此线程here之间的区别。
在Node.js v6.6.0中添加了答案 2 :(得分:3)
另外,在Patrick的回答中,我建议将Express与它的身体解析器一起使用。 完整示例如下。这适用于Express 4.x,Node 8.x(撰写时最新)。
请替换YOUR_WEBHOOK_SECRET_HERE
并在authorizationSuccessful
功能中执行某些操作。
// Imports
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();
// The GitHub webhook MUST be configured to be sent as "application/json"
app.use(bodyParser.json());
// Verification function to check if it is actually GitHub who is POSTing here
const verifyGitHub = (req) => {
if (!req.headers['user-agent'].includes('GitHub-Hookshot')) {
return false;
}
// Compare their hmac signature to our hmac signature
// (hmac = hash-based message authentication code)
const theirSignature = req.headers['x-hub-signature'];
const payload = JSON.stringify(req.body);
const secret = 'YOUR_WEBHOOK_SECRET_HERE'; // TODO: Replace me
const ourSignature = `sha1=${crypto.createHmac('sha1', secret).update(payload).digest('hex')}`;
return crypto.timingSafeEqual(Buffer.from(theirSignature), Buffer.from(ourSignature));
};
const notAuthorized = (req, res) => {
console.log('Someone who is NOT GitHub is calling, redirect them');
res.redirect(301, '/'); // Redirect to domain root
};
const authorizationSuccessful = () => {
console.log('GitHub is calling, do something here');
// TODO: Do something here
};
app.post('*', (req, res) => {
if (verifyGitHub(req)) {
// GitHub calling
authorizationSuccessful();
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Thanks GitHub <3');
} else {
// Someone else calling
notAuthorized(req, res);
}
});
app.all('*', notAuthorized); // Only webhook requests allowed at this address
app.listen(3000);
console.log('Webhook service running at http://localhost:3000');