# 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 を「遅いな」と感じるような贅沢さんになってしました ^^/