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