A while ago, I started experimenting with WebSockets with Node.js taking care of the backend. It was working fine, but now when I return the protocol has been updated and I can't get it to work properly anymore.
Specifically, the problem is the Sec-WebSocket-Accept
header. I seem to be doing something wrong when calculating it, although I can't really fathom what that might be. As far as I can tell, I'm following the instructions on Wikipedia to the dot.
Here's my code:
var magicString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; var secWsKey = req.headers['sec-websocket-key']; var hash = require('crypto') .createHash('SHA1') .update(secWsKey + magicString) .digest('hex'); var b64hash = new Buffer(hash).toString('base64'); var handshake = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: " + b64hash + "\r\n" + "\r\n"; socket.write(handshake);
An example connection:
// The incoming headers { upgrade: 'websocket', connection: 'Upgrade', host: 'localhost:8888', origin: 'http://localhost:8888', 'sec-websocket-key': '4aRdFZG5uYrEUw8dsNLW6g==', 'sec-websocket-version': '13' } // The outgoing handshake HTTP/1.1 101 Switching Protocols Upgrade: WebSocket Connection: Upgrade Sec-WebSocket-Accept: YTYwZWRlMjQ4NWFhNzJiYmRjZTQ5ODI4NjUwMWNjNjE1YTM0MzZkNg== // Result: Error during WebSocket handshake: Sec-WebSocket-Accept mismatch
Looking more into this, I tried replicating the calculated hash in the wiki and it fails.
var hash = require('crypto') .createHash('SHA1') .update('x3JJHMbDL1EzLkh9GBhXDw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11') .digest('hex'); // Result : 1d29ab734b0c9585240069a6e4e3e91b61da1969 // Expected: 1d29ab734b0c9585240069a6e4e3e91b61da1969 var buf = new Buffer(hash).toString('base64'); // Result : MWQyOWFiNzM0YjBjOTU4NTI0MDA2OWE2ZTRlM2U5MWI2MWRhMTk2OQ== // Expected: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
As you can see, the SHA1 hash is correct, but the base64 encoding is not. Looking at this answer, it seems I would be doing it right. I tried the same process in PHP and I get the same result, so clearly I'm Doing It Wrong.
I'm running Node.js v0.6.8.
Getting closer
Experimenting further with PHP, which is more familiar to me, and deriving from the behaviour of printf
in the shell, I came up with this working snippet:
$hash = sha1('x3JJHMbDL1EzLkh9GBhXDw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11'); $hashdec = ''; for ($i = 0; $i < strlen($hash); $i += 2) { $hashdec .= chr(hexdec(substr($hash, $i, 2))); }; echo base64_encode($hashdec); // Result : HSmrc0sMlYUkAGmm5OPpG2HaGWk= // Expected: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
I then tried to replicate this in JavaScript, but with no avail.
var magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; var key = "4aRdFZG5uYrEUw8dsNLW6g=="; var magic_key = magic + key; var hash = require('crypto').createHash('sha1').update(magic_key).digest('hex'); var buf = new Buffer(hash.length / 2); for (var i = 0; i < hash.length; i += 2) { var token = hash.substr(i, 2); var int = parseInt(token.toString(16), 16); var chr = String.fromCharCode(int); buf.write(chr); } console.log(buf.toString('base64')); // Result : w53dAAEAAADBIIAFAQAAAGGAtwA= // Expected: HSmrc0sMlYUkAGmm5OPpG2HaGWk=