Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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");
@thalesfsp

This comment has been minimized.

Copy link

thalesfsp commented Jan 6, 2014

Why do you have and where do u user client.js if the comm diagram is: nodejs:Server (Test) <-- (fsdhf83sdf!) --> (Test) Client:browser

@PulsarBlow

This comment has been minimized.

Copy link

PulsarBlow commented Aug 8, 2014

I think client.js is the node version (alternative) to the client.html.
So to answer your question, in your use case you don't use client.js.

@rdooh

This comment has been minimized.

Copy link

rdooh commented Oct 24, 2014

Looks good for the server-to-server, but for the client-to-server, seems like it's pretty easy to fake - you've got the [// No longer secret shared secret ;-)] and the method in plain view. Am I missing something?

@rdooh

This comment has been minimized.

Copy link

rdooh commented Oct 24, 2014

or maybe we're assuming that the client only arrives at the page (or receives code-on-demand) after initial login...

@moshest

This comment has been minimized.

Copy link

moshest commented Feb 19, 2015

Notice that Hmac.update(query) should get a Buffer and not a string.
It's case problems on unicode strings..

@tymat

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Copy link

MohammedEssehemy commented Sep 4, 2017

Thank you.
you saved my day

@mfn

This comment has been minimized.

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

@vokeio

This comment has been minimized.

Copy link

vokeio 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");
        }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.