关于node.js:Sec-WebSocket-Accept值的Base64编码

Base64 encoding for Sec-WebSocket-Accept value

不久前,我开始尝试使用node.js来处理后端。它工作得很好,但是现在当我返回时,协议已经更新,我不能让它正常工作了。

具体来说,问题是Sec-WebSocket-Accept头。我计算的时候好像做错了什么,虽然我真的不知道那可能是什么。据我所知,我正在按照维基百科上的指示上网。

以下是我的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

" +
           "Upgrade: WebSocket

" +
           "Connection: Upgrade

" +
           "Sec-WebSocket-Accept:" + b64hash +"

" +
           "

";

socket.write(handshake);

示例连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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

更深入地研究这个问题,我尝试在wiki中复制计算散列,但失败了。

1
2
3
4
5
6
7
8
9
10
11
12
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=

如您所见,sha1散列是正确的,但base64编码不是。看看这个答案,我似乎做得对。我在PHP中尝试了相同的过程,得到了相同的结果,所以很明显我做错了。

我正在运行node.js v0.6.8。

越来越近

我对PHP进行了进一步的实验,这对我来说更为熟悉,并且从shell中的printf的行为派生而来,我得出了以下工作片段:

1
2
3
4
5
6
7
8
$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=

然后我尝试在javascript中复制它,但没有任何效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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=


有时阅读手册确实有帮助。

hash.digest([encoding])

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

(强调我的)

因此,通过将代码更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

" +
           "Upgrade: WebSocket

" +
           "Connection: Upgrade

" +
           "Sec-WebSocket-Accept:" + hash +"

" +
           "

";

socket.write(handshake);

是时候觉得自己很傻了。(再次)


使用http://pajhome.org.uk/crypt/md5/sha1.html和代码

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