Skip to content

Instantly share code, notes, and snippets.

@agrueneberg
Last active March 19, 2023 06:48
Show Gist options
  • Save agrueneberg/6585680 to your computer and use it in GitHub Desktop.
Save agrueneberg/6585680 to your computer and use it in GitHub Desktop.
HMAC-SHA256 example for verifying both the data integrity and the authentication of a request in Node.js and web browsers.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HMAC-SHA256 Example</title>
</head>
<body>
<script src="http://crypto.stanford.edu/sjcl/sjcl.js"></script>
<script>
var sharedSecret, query, signature, hmac, xhr;
// No longer secret shared secret ;-)
sharedSecret = "super-secret";
query = "key=value";
hmac = new sjcl.misc.hmac(sjcl.codec.utf8String.toBits(sharedSecret), sjcl.hash.sha256);
signature = sjcl.codec.hex.fromBits(hmac.encrypt(query));
xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:1337/?" + query);
xhr.setRequestHeader("X-Signature", signature);
xhr.onload = function () {
console.log(xhr.status, xhr.responseText);
}
xhr.send(null);
</script>
</body>
</html>
var http, crypto, sharedSecret, query, signature;
http = require("http");
crypto = require("crypto");
sharedSecret = "super-secret";
query = "key=value";
signature = crypto.createHmac("sha256", sharedSecret).update(query).digest("hex");
http.get({
port: 1337,
path: "/?" + query,
headers: {
"X-Signature": signature
}
}, function (res) {
console.log(res.statusCode);
});
var http, url, crypto, sharedSecret;
http = require("http");
url = require("url");
crypto = require("crypto");
sharedSecret = "super-secret";
http.createServer(function (req, res) {
var retrievedSignature, parsedUrl, computedSignature;
// Deal with CORS.
res.setHeader("Access-Control-Allow-Origin", "*");
if (req.method === "OPTIONS") {
res.setHeader("Access-Control-Allow-Headers", "X-Signature");
res.writeHead(204);
res.end();
} else {
// Get signature.
retrievedSignature = req.headers["x-signature"];
// Recalculate signature.
parsedUrl = url.parse(req.url);
computedSignature = crypto.createHmac("sha256", sharedSecret).update(parsedUrl.query).digest("hex");
// Compare signatures.
if (computedSignature === retrievedSignature) {
res.writeHead(200, {
"Content-Type": "text/plain"
});
res.end("Hello World\n");
} else {
res.writeHead(403, {
"Content-Type": "text/plain"
});
res.end("Get Out\n");
}
}
}).listen(1337);
console.log("Server running on port 1337");
@tymat
Copy link

tymat commented Aug 23, 2016

This is an insecure implementation that is vulnerable to replay attacks. Each request should include an incremental nonce that is kept track on the server side.

@kabala
Copy link

kabala commented Jul 10, 2017

@tymat How can I achieve that? I wast searching like a crazy the best way to implement a secure HMAC protection for node.

@MohammedEssehemy
Copy link

Thank you.
you saved my day

@mfn
Copy link

mfn commented Apr 29, 2018

This is an insecure implementation that is vulnerable to replay attacks. Each request should include an incremental nonce that is kept track on the server side.

Not only that, it's also vulnerable to timing attacks due to the comparison with pure ===, see https://gist.github.com/agrueneberg/6585680#file-server-js-L24

@xeaone
Copy link

xeaone commented Aug 21, 2018

@mfn I think this should fixes the timing attack.

Modified Fork

        const computedSignatureBuffer = Buffer.from(computedSignature, 'hex');
        const retrievedSignatureBuffer = Buffer.from(retrievedSignature, 'hex');
        const valid = crypto.timingSafeEqual(computedSignatureBuffer, retrievedSignatureBuffer);

        if (valid) {
            res.writeHead(200, {
                "content-type": "text/plain"
            });
            res.end("hello world\n");
        } else {
            res.writeHead(403, {
                "content-type": "text/plain"
            });
            res.end("get out\n");
        }

@shide1989
Copy link

Awesome ! thanks !

@Blackening999
Copy link

@mfn I think this should fixes the timing attack.

Modified Fork

        const computedSignatureBuffer = Buffer.from(computedSignature, 'hex');
        const retrievedSignatureBuffer = Buffer.from(retrievedSignature, 'hex');
        const valid = crypto.timingSafeEqual(computedSignatureBuffer, retrievedSignatureBuffer);

        if (valid) {
            res.writeHead(200, {
                "content-type": "text/plain"
            });
            res.end("hello world\n");
        } else {
            res.writeHead(403, {
                "content-type": "text/plain"
            });
            res.end("get out\n");
        }

That helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment