Posted September 24, 2014 8:57 am by tim & filed under Crypto, Go, NodeJS, PHP, Python, Ruby, Web development.
When communicating messages between multiple systems, it’s always a good idea to authenticate a message before taking action on it. This ensures the message hasn’t been tampered with and that it came from a known source. There are several ways of implementing this type of message authentication, but here we’re going to assume we have control over the systems in question and can use a shared secret that all systems know.
Update: as a Redditor pointed out, these implementations may be vulnerable to timing attacks.
Update 2: Using Go’s hmac.Equals function prevents the leakage of time information and is therefore not vulnerable. The Python example has been updated to use a constant-time comparison function as well.
Update 3: As several HN viewers and blog commenters (Thanks James!) pointed out, the PHP, Ruby, and Node.js versions were never updated to safer alternative string comparison functions to help defend against timing attacks. While the gists have now been updated, the complete efficacy of mitigating timing attacks in these languages is debatable (i.e. https://bugs.python.org/issue15061, https://github.com/joyent/node/issues/8560). Still, the additional safety this provides is worth the slightly added complexity.
Preamble
The code examples below are simplified for easier viewing and readability. In doing so, the cryptographic keys being used are hardcoded in each example. This is never safe and should not be used as-is. Hardcoded keys all too often inadvertently end up in source code repositories. Always use environment variables, untracked configuration files, or other measures to store any cryptographic keys, passwords, or the like.
Verifying HMAC-based signatures puts you at risk for timing attacks. Using constant-time string comparison functions helps to prevent this, but other factors can influence those techniques (i.e. compiler optimizations, VMs, language idiosyncrasies, etc). Use your best judgment and do your own research before implementing.
Go
Generate an HMAC: create a JSON payload from a map and create a signature based on our shared secret.
Verify an HMAC: Given the [QUERYSTRING] from the output above, calculate our own signature and compare it with what’s provided. Then, verify the timestamp is within 30 seconds of the current time.
Python
Generate an HMAC: Create a JSON payload from a Python dictionary and create a signature using the raw message digest. Base64 encode the signature and JSON payload.
Verify an HMAC: Given the [QUERYSTRING] from the previous output, base64 decode the signature and JSON payload, verify the signature by calculating our own digest and comparing it with the signature provided (note: Python 2.7.7 and greater can use hmac.compare_digest instead of ==). Check the timestamp against the current time and if we’re more than 30 seconds off, throw an exception.
Ruby
Generate an HMAC: Create a JSON payload from a Ruby hash, sign, and base64 encode it.
Verify an HMAC: Given the [QUERYSTRING] from the previous output, calculate our own signature and compare it against the one provided. Check the timestamp against our current time and if more than 30 seconds has passed, raise an exception.
PHP
Generate an HMAC: Create a JSON payload from an associative array, sign, and base64 encode it.
Verify an HMAC: Given the [QUERYSTRING] from the previous output, calculate our own signature and compare it against the signature provided. Check the timestamp against the current time and throw an exception if the difference is greater than 30 seconds.
NodeJS
Generate an HMAC: Stringify a Javascript object to create a JSON payload, sign, and base64 encode it.
Verify an HMAC: Given the [QUERYSTRING] from the previous output, parse the query string into parameters, calculate our own signature, and compare it against what’s provided. Check the timestamp against the current time and if the difference is greater than 30 seconds, throw an exception.
Note: we created our own parseQuery function here because url.parse appears to automatically decode certain characters in the query string, preventing the signature comparison from working correctly.
Sign up for Turret.IO — the only data-driven marketing platform made specifically for developers.