Key takeaways
- When WebSocket handshaking, client uses
Sec-WebSocket-Key
header and server usesSec-WebSocket-Accept
header. - The algorithm to create values set in both header is defined in RFC 6455.
- This article explains the algorithm in detail with simple implementations in Go.
WebSocket
WebSocket is a mechanism for low-cost, full-duplex communication on Web, which allows communication in both directions, and unlike half-duplex, allows this to happen simultaneously. The WebSocket protocol was standardized as RFC 6455.
WebSocket is designed to work over HTTP. To achieve compatibility with HTTP, the WebSocket handshake uses the HTTP Upgrade header in order to change from the HTTP protocol to the WebSocket protocol.
The following diagram, quoted by Wikipedia, describe a communication using WebSocket.
There are three main interactions between client and server.
- Handshake using HTTP Upgrade
- Bidirectional messages
- One sides closes channel
The first step to exchange message bidirectionally is handshake where opens a new connection between client and server.
Handshake using HTTP Upgrade
As I mentioned before, the WebSocket handshake uses the HTTP Upgrade header in order to change from the HTTP protocol to the WebSocket protocol.
The Upgrade header can be used to upgrade an already established client/server connection to a different protocol. To use Upgrade
header, Connection: update
must be set.
RFC 6455 - 1.2 Protocol Overview illustrates an example of WebSocket handshake.
From client:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Then server will respond as follow:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
This article focuses on two headers: Sec-WebSocket-Key
, Sec-WebSocket-Accept
. In this example, the header Sec-WebSocket-Key
is set to dGhlIHNhbXBsZSBub25jZQ==
, and Sec-WebSocket-Accept
is set to s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
. How is this value determined? This article explain the answer in detail using the protocol specification described in RFC and an example of implementation in Go.
Sec-WebSocket-Key/Sec-WebSocket-Accept
Both are registered as new HTTP header fields. Sec-WebSocket-Key
is described in RFC 6455 - 11.3.1, and Sec-WebSocket-Accept
is in RFC 6455 - 11.3.3.
They are used mainly for two purposes according to RFC 6455 and the discussion on stack overflow.
- Ensure that server understands WebSocket protocol
- Prevent clients accidentally requesting WebSocket upgrade not expecting it
RFC 6455 describes the first purpose at page-7.
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" in string form, which is unlikely to be used by network endpoints that do not understand the WebSocket Protocol.
As we will see later, the server has to concatenate the value of Sec-WebSocket-Key
with the GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". This is a specification specific to WebSocket protocol, and it is unlikely that a server that does not understand this protocol will behave in a same manner.
Also, page-57 describes the second purpose as follow:
The |Sec-WebSocket-Key| header field is used in the WebSocket opening handshake. It is sent from the client to the server to provide part of the information used by the server to prove that it received a valid WebSocket opening handshake. This helps ensure that the server does not accept connections from non-WebSocket clients (e.g., HTTP
clients) that are being abused to send data to unsuspecting WebSocket servers.
The Sec-WebSocket-Key
header field is used in WebSocket opening handshake, conversely speaking, non-WebSocket clients do not use this header.
Specification of value of Sec-WebSocket-Key
The requirement for Sec-WebSocket-Key
is described as client requirements in RFC 6455 - 4.1. Client Requirements.
The request MUST include a header field with the name |Sec-WebSocket-Key|. The value of this header field MUST be a nonce consisting of a randomly selected 16-byte value that has been base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be selected randomly for each connection.
From the above explanation, there are three requirements.
- 16-byte value
- Randomly selected
- Base64-encoded
This is how the specification is implemented in Go.
import (
"crypto/rand"
"encoding/base64"
"io"
)
func generateKey() (string, error) {
// 1. 16-byte value
p := make([]byte, 16)
// 2. Randomly selected
if _, err := io.ReadFull(rand.Reader, p); err != nil {
return "", err
}
// 3. Base64-encoded
return base64.StdEncoding.EncodeToString(p), nil
}
func main() {
k, err := generateKey()
if err != nil {
panic(err)
}
fmt.Println(k)
// Output: 08kp54j1E3z4IfuM1m75tQ==
}
https://go.dev/play/p/XdWkJ2PeB_-
How server should handle it
RFC 6455 - 1.3. Opening Handshake describes how server should handle a value set in Sec-WebSocket-Key
.
In summary, the following requirements for server:
- Take the value in the
Sec-WebSocket-Key
header - Concatenate the value with the GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
- Return a SHA-1 hash (160 bits), base64-encoded value
This is how the specification is implemented in Go.
package main
import (
"crypto/sha1"
"encoding/base64"
"fmt"
)
func main() {
key := "08kp54j1E3z4IfuM1m75tQ==" // from client
ak := computeAcceptKey(key)
fmt.Println(ak) //
}
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
func computeAcceptKey(key string) string {
// Create a new SHA-1 hash
h := sha1.New()
// Write a given key from client
h.Write([]byte(key))
// Concatenate the key with the GUID
h.Write(keyGUID)
// Base64-encoded
return base64.StdEncoding.EncodeToString(h.Sum(nil))
// Output: Dhg2s/gXkbuTiYk690dns7LmEhY=
}
https://go.dev/play/p/PQ1apDQHiHm
Conclusion
We have discussed the values that are set for Sec-WebSocket-Key
and Sec-WebSocket-Accept
.
From client:
GET /chat HTTP/1.1
... (omit)
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Then server will respond as follow:
HTTP/1.1 101 Switching Protocols
... (omit)
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Top comments (1)
Beautifully written with appropriate and crisp reference links.