How decided a value set in Sec-WebSocket-Key/Accept header
Tags: webdev, websocket, go
Published: 2021-12-08 11:21:36

## Key takeaways

- When WebSocket handshaking, client uses `Sec-WebSocket-Key` header and server uses `Sec-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](https://en.wikipedia.org/wiki/Duplex_(telecommunications)#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](https://datatracker.ietf.org/doc/html/rfc6455).

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](https://en.wikipedia.org/wiki/WebSocket), describe a communication using WebSocket.

![A diagram describing a connection using WebSocket](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7qiia95sgoly2rya998.png)

There are three main interactions between client and server.

1. Handshake using HTTP Upgrade
2. Bidirectional messages
3. 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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](https://datatracker.ietf.org/doc/html/rfc6455#section-1.2) 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](https://datatracker.ietf.org/doc/html/rfc6455#section-11.3.1), and `Sec-WebSocket-Accept` is in [RFC 6455 - 11.3.3](https://datatracker.ietf.org/doc/html/rfc6455#section-11.3.3).

They are used mainly for two purposes according to RFC 6455 and the discussion on [stack overflow](https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for).

1. Ensure that server understands WebSocket protocol
2. Prevent clients accidentally requesting WebSocket upgrade not expecting it

RFC 6455 describes the first purpose at [page-7](https://www.rfc-editor.org/rfc/rfc6455#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](https://www.rfc-editor.org/rfc/rfc6455#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](https://datatracker.ietf.org/doc/html/rfc6455#section-4.1).

> 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.

1. 16-byte value
2. Randomly selected
3. Base64-encoded

This is how the specification is implemented in Go.

```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](https://datatracker.ietf.org/doc/html/rfc6455#section-1.3) 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.

```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=