🔐
【Go】Goで学ぶ暗号技術 ~GCMモード~
はじめに
最近暗号技術について興味があり、少しずつ学習をしています。
第1回はECBモード、第2回はCBCモード、第3回はCFBモード、第4回はOFBモード、第5回はCTRモードを取り上げました。
今回はGCMモードを紹介します。
第1回 第2回 第3回 第4回 第5回
✅この記事でわかること
- GCMモードの特徴
- GCMモードの仕組み(暗号化・復号化の流れ)
- GCMモードを使う上での注意点
📝GCMモードの特徴
- Galois/Counter Modeの略(認証付きCTRモード)
- 「Nonce + Counter」でCTRモードとして暗号化を行う
- 暗号化に加えて認証(改ざん検知)機能がある
- Galois(ガロア)演算によって認証タグ(MAC)を生成
- 暗号文と一緒に 認証タグ(例:16バイト) も出力される
- 復号時には認証タグを用いて 整合性チェック(改ざん検出) を行う
- Nonceは一意である必要がある(同じ鍵で再利用すると危険)
📊GCMモード解説
暗号化
- 暗号化した「Nonce + Counter」と平文をXOR
- Counterをインクリメントし、その値を暗号化し平文とXOR
復号化
- 暗号化した「Nonce + Counter」と暗号ブロックをXOR
- Counterをインクリメントし、その値を暗号化し暗号ブロックとXOR
⚠️GCMモードの注意点
- Nonceの再利用はNG
→crypto/randなどで毎回異なるNonceを生成 - 認証タグの検証を必ず行う
→gcm.Open()メソッドは、復号と認証タグの検証を同時に行う
🛠Goでの実装方法
暗号化の手順
- Nonce(IV)とカウンタ(CTR)をAESで暗号化
- 出力と平文をXORして暗号文を取得
- 同時にGalois認証処理で認証タグ(MAC)を生成
復号化
- 暗号文からNonceを取得
- AESで暗号化し、暗号文とXORして平文を取得
- 認証タグと復号化された内容を検証
- 認証がOKなら平文を出力、NGなら改ざんが検出されたということで復号化に失敗
実装
main.go
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"log"
)
// GCM暗号化
func encryptGCM(key, plainText []byte) ([]byte, []byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, nil, err
}
nonce := make([]byte, aesgcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, nil, err
}
encryptedText := aesgcm.Seal(nil, nonce, plainText, nil)
encryptedText = append(nonce, encryptedText...)
return encryptedText, nonce, nil
}
// GCM復号化
func decryptGCM(key, encryptedText []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
if len(encryptedText) < aesgcm.NonceSize() {
return nil, fmt.Errorf("too short encrypted text")
}
nonce := encryptedText[:aesgcm.NonceSize()]
encryptedText = encryptedText[aesgcm.NonceSize():]
decryptedText, err := aesgcm.Open(nil, nonce, encryptedText, nil)
if err != nil {
return nil, err
}
return decryptedText, nil
}
func main() {
key := []byte("gcm-example-key!")
plainText := []byte("Hello, GCM mode!")
fmt.Println("PlainText:", string(plainText))
encryptedText, nonce, err := encryptGCM(key, plainText)
if err != nil {
log.Fatal(err)
}
fmt.Println("EncryptedText:", hex.EncodeToString(encryptedText))
fmt.Println("Nonce:", hex.EncodeToString(nonce))
decryptedText, err := decryptGCM(key, encryptedText)
if err != nil {
log.Fatal(err)
}
fmt.Println("DecryptedText:", string(decryptedText))
}
出力例
ターミナル
PlainText: Hello, GCM mode!
EncryptedText: c5f281e4876139ad372c9cd757814d23e4236fff303ae498400d5c4b1fb6ff27bc585733e894a517e8fff046
Nonce: c5f281e4876139ad372c9cd7
DecryptedText: Hello, GCM mode!
まとめ
今回はGCMモードについて書いていきました。
認証付き暗号という新しい要素(改ざん検知)が加わるため、他のモードと比べて仕組みが複雑に感じられました。
この辺りをもっと学習していきたいですね!
参考
Discussion