2022-05-23

Golang において Base64 エンコーディングした文字列を Cookie で扱う

yoneyama

はじめに

バックエンドエンジニアの @y0n3yama です。

Golang において、Base64 エンコード した文字列を Cookie で扱う際に迷った点と調査してわかったことについて書いていきます。

やろうとしていたこと

ある API リクエスト処理の中で、

  1. 動的な文字列を生成したのち Base64 にエンコード
  2. 1の文字列を SetCookie してレスポンス
  3. 後続のAPIリクエストで再度参照

ということをしようとしていました。 RFC6265 では、 Cookie に保存する値はBase64 エンコードするべきとされています。

To maximize compatibility with user agents, servers that wish to store arbitrary data in a cookie-value SHOULD encode that data, for example, using Base64 [RFC4648].

Golangでのbase64エンコード

Golangではビルトインの encoding/base64 パッケージを利用してエンコード/デコードをすることになります。

  1. エンコード func (enc *Encoding) EncodeToString(src []byte) string

  2. デコード func (enc *Encoding) DecodeString(s string) ([]byte, error)

なお、エンコードの方式は4つ用意されています。

  1. StdEncoding - 通常の Base64 エンコード(パディングあり)
  2. URLEncoding - URLセーフな Base64 エンコード(パディングあり)
  3. RawStdEncoding - 通常の Base64 エンコード(パディングなし)
  4. RawURLEncoding - URLセーフな Base64 エンコード(パディングなし)

この4つのうちどれを利用するか迷いました。調べた結果と見解について述べていきます。

Cookieで扱うという観点から4つの選択肢から選ぶにあたって、考えるべきは以下2つの軸になります。 ① 通常の Base64 エンコード or URLセーフな Base64 エンコード ② パディングあり or なし

それぞれの軸について考える前に、前提としての Set-Cookie の仕様について確認しておきます。

cookie-name は任意の US-ASCII 文字の集合で、制御文字、空白、タブを除いたものです。( ) < > @ , ; : \ “ / [ ] ? = { } のような区切り文字も含めることができません。 cookie-value は任意で二重引用符で囲むことができ、制御文字、ホワイトスペース、二重引用符、カンマ、セミコロン、バックスラッシュを除くすべての US-ASCII 文字が利用できます。 Set-Cookie

上記の仕様を踏まえると、 ①については、特にURLエンコードする必要性はないため、通常のbase64エンコーディングので問題なし。 ②については、Cookieにおけるパディング記号 = の性質を意識しておきたいところです。 これは個人的に勘違いしていたのですが、 valueであれば = を許容されているので、パディングありで問題はない です。

また、パディングの必要性については以下のようにデコード時の曖昧さを省くためであるため、あるにこしたことはなさそうです。

In some circumstances, the use of padding ("=") in base-encoded data is not required or used. In the general case, when assumptions about the size of transported data cannot be made, padding is required to yield correct decoded data.

RFC4648

ということで、StdEncoding を利用する判断をしました。 参考までにソースコードを共有しておきます。

    sampleStr := []byte("てすと.")
    encodedStr := base64.StdEncoding.EncodeToString(sampleStr) 
    fmt.Printf("encodeされた文字列: %s", encodedStr) // 44Gm44GZ44GoLg==

    fmt.Println()

    // Cookieにセット(省略)

    // Cookieから取得(省略)

    decodedStr, err := base64.StdEncoding.DecodeString(encodedStr)
    if err != nil {
        // エラーハンドリング
        panic(err)
    }
    fmt.Printf("decodeされた文字列: %s", decodedStr) // てすと.

Go Playgound

おわりに

おそらく他の方式でも動作自体はしそう(?)な結果となりましたが、 意図しない結果を招かないためにも、複数の選択肢が存在する中でもある程度の根拠を持った選択をしていくのはこれからも意識していきたい次第です。 少しでも参考になりましたら幸いです。 積極採用中!

最新の記事