DEV Community

Kazuki Higashiguchi
Kazuki Higashiguchi

Posted on

How decided a value set in Sec-WebSocket-Key/Accept header

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

A diagram describing a connection using WebSocket

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 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

  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.

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

  1. 16-byte value
  2. Randomly selected
  3. 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==
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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==
Enter fullscreen mode Exit fullscreen mode

Then server will respond as follow:

    HTTP/1.1 101 Switching Protocols
    ... (omit)
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
pankhudib profile image
Pankhudi Bhonsle

Beautifully written with appropriate and crisp reference links.