# OpenSSL の AES の GCM モードで作った暗号を go で復号する

# はじめに

クライアント側で暗号化したデータを gin で復号しようとしてちょっと詰まったのでメモを残しておきます

こちら (opens new window) に encription と decription のサンプルがあるのですが

	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err.Error())
	}

	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		panic(err.Error())
	}

	plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		panic(err.Error())
	}

  fmt.Printf("%s\n", plaintext)

key を引数にして aes で block cipher を作って、そこから aesgcm を作って、aesgcm.Open() に nonce と暗号文を渡すと復号してくれるみたいです

nonce って要するに IV の事だとして、aad と tag はどうやって指定するんだろ? と、傍と困ってしまったわけです

# aesgcm.Open()

なんか引数が4つあって、nonce と ciphertext 以外に nil を渡しているので、そこにそれぞれ aad と tag がはいるんだろうとおもってドキュメントを探します

AEAD のインターフェース (opens new window)が見つかりました

  Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error)

後ろが aad で前が dst、どっちもなきゃないでそういうものなので、それで nil にできたわけですね、では tag はどこに?

# tag は ciphertext の後ろにつなげる

わからないので aes gcm tag とかでググりまくってて見つけたのが gcm のコード (opens new window)で、214行目を見た瞬間に脳天をハンマーで殴られたような衝撃を感じました

  tag := ciphertext[len(ciphertext)-g.tagSize:]
  ciphertext = ciphertext[:len(ciphertext)-g.tagSize]

なななぁんとおおおお、go の gcm は暗号文の後ろにtagがくっついているという暗黙の前提があったんですね!
って、あのなあ、このインターフェース考えた奴ちょっとこっちこいゴルァ!少なくともドキュメント梨がゆるされるものかどうか少し反省 time をとってほしい希ガス

# 正解

というわけで ciphertext の後ろに tag を append して渡してあげると、無事に復号してくれました

  ct := append(ciphertext, tag...)
	plaintext, err := aesgcm.Open(nil, nonce, ct, aad)

# (2021.08.11追記) rust の場合

aes_gcm の Crate のドキュメント (opens new window) 見てたらやっぱり引数に tag が無い

不思議な事に Type Definitions には tag (opens new window) があるんですよね

また go みたいに chipertext にくっつけてるのかな?と思って見てたら、in-place Usage (opens new window) のサンプルコードにこれまでに見てきた中でもトップレベルに不安になるコメント^1を発見

let mut buffer: Vec<u8, 128> = Vec::new(); // Buffer needs 16-bytes overhead for GCM tag

多分、IV の前だか後ろだか(どうかしたらど真ん中とか)に tag をくっつけるという OpenSSL はもちろん go とも互換性のないような実装になってるみたいですね

もうやだ!なんで aes のガロアカウンターモードなんていう、高度な数学を駆使するカッコいいライブラリを作るような、羨ましくも知性と教養に満ち溢れているはずのご立派なエンジニア様方が tag をこんなに好き勝手にするんだろ? tag は独立した引数にしておいてくれないと言語またいだ interoperability がないじゃん!

# おまけ

cipher や tag を Base64 で文字列にして http POST で server に投げるときにもはまりポイントがあるのできをつけましょうというメモです

# Base64URL にする

Base64 の + とか(わざわざこれを Base64のエンコーデッドなアルファベットに入れてくださった先人の頭を蹴っ飛ばしたい思いです)をリクエストの body に入れて post すると消えてしまうので、+- に、/_ に変換する Base64URL (opens new window) という形にします

OpenSSL の Base64 を探してみたのですが Base64URL は無さそうだったのと(Base64 そのものは OpenSSL にあります)、探す手間でできちゃいそうだったのでこんなかんじに

int plus2minus(unsigned char *buff, int buff_len){
    for (int i=0; i< buff_len; i++){
        if (buff[i] == 0){
            return 1;
        }
        if (buff[i] == '+'){
            buff[i] = '-';
        }
        if (buff[i] == '/'){
            buff[i] = '_';
        }        
    }
    return 1;
}

コードがアセアセしてる顔文字みたいでかわいいですね

# base64Url の go 側のデコードは URLEncoding を使う

go 側ではモジュール encoding/base64 が URL Encoding を持ってますのでそれを使います

decoded, err := base64.URLEncoding.DecodeString(base64urlstr)

# performance

gin の handler で見てると Base64url のポストを受けて復号が終わるまで Scaleway の C1 サーバ(初代の ARM BareMetal Server です。RPi よりちょっと速い感じ)でも 500μs 程度みたいです
gin 使うようになって 1ms を「遅いな」と感じるような贅沢さんになってしました ^^/


Last Updated: 2021/8/11 2:17:15