JSON Web Signatures on Node.js
My previous article was really just a pointer to some enhancements I made to Kenji Urushima's jsjws project. jsjws is an implementation of JSON Web Signatures (JWS) in Javascript.
Although an excellent library, jsjws as it stands isn't usable on Node.js for the following reasons:
- It uses pure Javascript crypto routines which are slower that those provided by Node.js modules.
- It uses some global functions which are provided by browsers.
- It isn't packaged up as Node.js module.
This article describes:
- Enhancements I made to jsjws to make it run much faster on Node.js.
- A module, node-jsjws which you can use in your Node.js projects.
- Extensions I made to jsjws to support JSON Web Tokens.
About JSON Web Signatures
A JSON Web Signature (JWS) is a standard format for representing JSON data. It has three parts:
- Header
- Metadata such as the algorithm used to generate the signature
- Payload
- The data itself
- Signature
- A cryptographic signature derived from the header and payload
jsjws supports the following signature algorithms:
- RS256, RS512
- These use RSASSA-PKCS1-V1_5 and SHA-256 or SHA-512 to generate the signature.
- PS256, PS512
- These use RSASSA-PSS and SHA-256 or SHA-512 to generate the signature. I'd previously added PSS support to ursa (a Node.js interface to OpenSSL) and to jsjws.
- HS256, HS512
- These use HMAC and SHA-256 or SHA-512 to generate the signature.
- none
- An empty string is used as the signature.
Example
Say we have the following JSON payload data:
{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}
and we want to use HS256 to generate a JSON Web Signature.
First we make the header, which is an object with a single property, alg
, specifying the algorithm we're using. Here's the JSON representation:
{"alg":"HS256"}
Next we encode the header and payload as URL-safe Base 64 (base64url). So in our example, the base64url encoding of the header is:
eyJhbGciOiJIUzI1NiJ9
and the base64url encoding of the payload is:
eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
Now we need to generate a cryptographic signature from the header and payload.
The input to the signature operation is the concatenation of the base64url
header, the character .
and the base64url payload:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
In this example, we're using the HS256 JWS signature algorithm, which is HMAC with SHA-256 as the digest operation. We feed the signature input (above) as the message to HMAC SHA-256 along with some secret key.
If we choose foobar
as our secret key then the base64url encoding of the generated cryptographic signature is:
74x4aMvBBGj5DPfbi6HEk5RxJuc1lnMlnIlhweidQCw
Finally, the JSON Web Signature is the concatenation of the signature input (i.e. the base64url header, the character .
and the base64url payload), the character .
and the base64url cryptographic signature:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.74x4aMvBBGj5DPfbi6HEk5RxJuc1lnMlnIlhweidQCw
The JWS can be sent to other parties, who can decode the header and payload components and determine its validity by verifying the cryptographic signature component.
Optimising jsjws
jsjws is a great pure Javascript library for generating and verifying JSON Web Signatures. However, the main use case for JSON Web Signatures on Node.js is probably asserting identity between different web sites. On busy sites this might require many JWS operations per second so it's important that we optimise jsjws in this scenario.
I've modified jsjws to speed it up a bit on Node.js:
Only parse the header once. jsjws was parsing the header twice: once to verify it's a valid JSON string and another to extract data from it.
Use the built-in
Buffer
class to perform base64 encoding instead of doing it in Javascript.Use the built-in
crypto
module to hash data instead of doing it in Javascript.Use the ursa module to sign data instead of doing it in Javascript. ursa uses OpenSSL to do the heavy lifting.
Kenji Urushima has merged my changes back into jsjws (and its sister project, jsrsasign) so it's ready to run on Node.js.
Introducing node-jsjws
To make jsjws easier to use on Node.js, I've created a module which you can use in your projects. It's available on npm:
npm install jsjws
Here's an example which generates a private key and then uses it to generate a JSON Web Signature from some data:
var jsjws = require('jsjws');
var key = jsjws.generatePrivateKey(2048, 65537);
var header = { alg: 'PS256' };
var payload = { foo: 'bar', wup: 90 };
var sig = new jsjws.JWS().generateJWSByKey(header, payload, key);
var jws = new jsjws.JWS();
assert(jws.verifyJWSByKey(sig, key));
assert.deepEqual(jws.getParsedHeader(), header);
assert.deepEqual(jws.getParsedPayload(), payload);
Use the JWS
class to generate and verify JSON Web Signatures and access the
header and payload. The full API is documented on the node-jsjws homepage, where the source is available too.
You'll also find a full set of unit tests, including tests for interoperability with jwcrypto, python-jws and jsjws in the browser (using the excellent PhantomJS headless browser).
Benchmarks
node-jsjws also comes with a set of benchmarks. Here are some results on a laptop with an Intel Core i5-3210M 2.5Ghz CPU and 6Gb RAM running Ubuntu 13.04.
In the tables, jsjws-fast uses ursa (OpenSSL) for crypto whereas jsjws-slow does everything in Javascript. jwcrypto is Mozilla's implementation of JSON Web Signatures on Node.js.
The algorithm used was RS256 because jwcrypto doesn't support PS256.
generate_key x10 | total (ms) | average (ns) | diff (%) |
---|---|---|---|
jwcrypto | 1,183 | 118,263,125 | - |
jsjws-fast | 1,296 | 129,561,098 | 10 |
jsjws-slow | 32,090 | 3,209,012,197 | 2,613 |
generate_signature x1,000 | total (ms) | average (ns) | diff (%) |
---|---|---|---|
jsjws-fast | 2,450 | 2,450,449 | - |
jwcrypto | 4,786 | 4,786,343 | 95 |
jsjws-slow | 68,589 | 68,588,742 | 2,699 |
load_key x1,000 | total (ms) | average (ns) | diff (%) |
---|---|---|---|
jsjws-fast | 46 | 45,996 | - |
jsjws-slow | 232 | 232,481 | 405 |
verify_signature x1,000 | total (ms) | average (ns) | diff (%) |
---|---|---|---|
jsjws-fast | 134 | 134,032 | - |
jwcrypto | 173 | 173,194 | 29 |
jsjws-slow | 1,706 | 1,705,810 | 1,173 |
You can see that in every case, my optimisations make jsjws much faster on Node.js. It's also faster than jwcrypto for generating and verifying JSON Web Signatures, but slower for generating keys.
The source to the benchmarks is available from the node-jsjws homepage.
JSON Web Tokens
JSON Web Tokens are JSON Web Signatures with some well-defined metadata in the header.
I added support for JSON Web Tokens to node-jsjws, adding the following metadata to the header:
- exp
- The expiry date and time of the token
- nbf
- The valid-from date and time of the token
- iat
- The date and time at which the token was generated
- jti
- A unique identifier for the token
Again, the JWT API is documented on the node-jsjws homepage.
blog comments powered by Disqus