6

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=
Community
  • 1
  • 1
nikc.org
  • 16,462
  • 6
  • 50
  • 83
  • That's probably not what you want to hear, but I would highly recommend to leave the handling of the protocol to others. It's complicated and changes frequently. An open-source solution that is being peer-reviewed will ensure that everything is always conform with the latest specs. [See this page for Node.js socket modules](https://github.com/joyent/node/wiki/modules#wiki-ws-ajax) – Alex Turpin Jan 24 '12 at 16:42
  • @Xeon06 I'm well aware of this. But since I like to dissect stuff in order to understand them better, I want to make a working PoC. This is not something I would use in a production environment. – nikc.org Jan 24 '12 at 16:45
  • Ah yes, I can relate. I myself made my own WebSocket servers in the beggining just to do it for the sake of learning, but switched to a more stable solution after. Best of luck with your problem. – Alex Turpin Jan 24 '12 at 16:48
  • @Xeon06, I don't disagree in general that you shouldn't re-invent the wheel. However, the protocol isn't changing frequently. It's now an IETF RFC standard. The wire protocol framing for HyBi was pretty much unchanged for the prior year leading up to standardization. The changes were in error codes and standard wording and such. Browser support is trickling in so it feels like things are changing, but the protocol isn't. – kanaka Jan 24 '12 at 17:40

2 Answers2

5

Sometimes reading the manual actually helps.

hash.digest([encoding])

Calculates the digest of all of the passed data to be hashed. The encoding can be 'hex', 'binary' or 'base64'.

(Emphasis mine.)

So the problem was solved by changing the code to:

var magicString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
var secWsKey = req.headers['sec-websocket-key'];
var hash = require('crypto')
             .createHash('SHA1')
             .update(secWsKey + magicString)
             .digest('base64'); // <- see that, silly.
var handshake = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" +
            "Upgrade: WebSocket\r\n" +
            "Connection: Upgrade\r\n" +
            "Sec-WebSocket-Accept: " + hash + "\r\n" +
            "\r\n";

socket.write(handshake);

'Tis time to feel silly. (Again.)

nikc.org
  • 16,462
  • 6
  • 50
  • 83
0

use this http://pajhome.org.uk/crypt/md5/sha1.html and code

b64pad = "=";
var b64hash = b64_sha1(secWsKey + magicString);
console.log(b64hash);
jumkey
  • 1
  • 1