Xác minh chữ ký
Kiểm tra header X-Nex-Signature bằng HMAC-SHA256 trên thân thô, kèm mã mẫu Node.js, Python, C#, PHP.
Chữ ký là HMAC-SHA256(secret, "{t}." + raw_body), in ra dạng hex chữ thường.
secretlà bí mật ký NexERP đã cấp.tlà số giây Unix lấy từ header.raw_bodylà các byte thô nhận được. Đừng đọc JSON rồi dựng lại chuỗi, vì khoảng trắng và thứ tự khóa sẽ khác và chữ ký sai.
Các bước kiểm tra
- Tách t và v1
Đọc header
X-Nex-Signature, tách hai giá trịtvàv1. - Chống phát lại
Từ chối nếu giờ hiện tại lệch
tquá 300 giây. - Tính HMAC
Tính HMAC-SHA256 trên
"{t}." + raw_body, khóa là bí mật ký. - So sánh chữ ký
So với
v1bằng hàm so sánh thời gian hằng số. Không dùng==. - Loại trùng
Cùng
X-Nex-Event-Idđến nhiều lần thì chỉ xử lý một lần.
Node.js
const crypto = require("crypto");
// rawBody phải là Buffer. Với Express: app.post("/hook", express.raw({ type: "application/json" }), handler)
function verifySignature(secret, header, rawBody, toleranceSec = 300) {
const parts = {};
for (const part of String(header || "").split(",")) {
const i = part.indexOf("=");
if (i > 0) parts[part.slice(0, i)] = part.slice(i + 1);
}
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(Buffer.concat([Buffer.from(`${t}.`), rawBody]))
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Python
import hashlib
import hmac
import time
def verify_signature(secret: str, signature_header: str, raw_body: bytes, tolerance: int = 300, now: float | None = None) -> bool:
try:
parts = dict(p.split("=", 1) for p in signature_header.split(","))
t = int(parts["t"])
v1 = parts["v1"]
except (KeyError, ValueError):
return False
if abs((time.time() if now is None else now) - t) > tolerance:
return False
expected = hmac.new(secret.encode("utf-8"), str(t).encode("ascii") + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode("ascii"), v1.encode("utf-8", "ignore"))
# Flask: verify_signature(SECRET, request.headers["X-Nex-Signature"], request.get_data())C#
using System;
using System.Security.Cryptography;
using System.Text;
public static class NexSignature
{
// rawBody: các byte thô của thân request (đọc Request.Body vào byte[] trước khi parse JSON)
public static bool Verify(string secret, string header, byte[] rawBody, int toleranceSec = 300)
{
long t = 0;
string v1 = null;
foreach (var part in (header ?? "").Split(','))
{
var kv = part.Split('=', 2);
if (kv.Length != 2) continue;
if (kv[0] == "t") long.TryParse(kv[1], out t);
else if (kv[0] == "v1") v1 = kv[1];
}
if (t == 0 || v1 == null) return false;
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > toleranceSec) return false;
var prefix = Encoding.UTF8.GetBytes(t + ".");
var data = new byte[prefix.Length + rawBody.Length];
Buffer.BlockCopy(prefix, 0, data, 0, prefix.Length);
Buffer.BlockCopy(rawBody, 0, data, prefix.Length, rawBody.Length);
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var expected = Convert.ToHexString(hmac.ComputeHash(data)).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(v1));
}
}PHP
<?php
function verify_signature(string $secret, string $header, string $rawBody, int $tolerance = 300): bool
{
$t = null;
$v1 = null;
foreach (explode(",", $header) as $part) {
$kv = explode("=", $part, 2);
if (count($kv) !== 2) continue;
if ($kv[0] === "t") $t = $kv[1];
elseif ($kv[0] === "v1") $v1 = $kv[1];
}
if ($t === null || !ctype_digit($t) || $v1 === null) return false;
if (abs(time() - (int)$t) > $tolerance) return false;
$expected = hash_hmac("sha256", $t . "." . $rawBody, $secret);
return hash_equals($expected, $v1);
}
// $ok = verify_signature($secret, $_SERVER["HTTP_X_NEX_SIGNATURE"] ?? "", file_get_contents("php://input"));Lưu ý
Mọi mẫu trên tính cùng một thứ: HMAC-SHA256 của các byte
"{t}."nối với thân thô, khóa là bí mật ký.Đồng hồ máy bạn lệch quá 300 giây thì chữ ký đúng vẫn bị từ chối. Hãy đồng bộ giờ máy.