🔐
Goで対称暗号化(AES-256-GCM)を実装する
はじめに
最近、Go言語でデータの暗号化・復号化を実装する機会がありました。
「どの暗号化方式が安全なのか」「実装時の注意点は何か」を調べる中で、OWASPでは対称暗号化の場合、AES方式の256ビット鍵をGCMで利用することが推奨されていると分かりました。
実際に実装してみたので、同じような実装を検討している方の参考になれば幸いです。
OWASP Cryptographic Storage Cheat Sheet
対称暗号化と非対称暗号化の違い
暗号化方式には大きく分けて2つの方式があるようです。
- 対称暗号化: 同じ鍵を使って暗号化と復号化を行う方式
- 非対称暗号化: 「公開鍵」と「秘密鍵」の2つの異なる鍵を使用する方式
本記事では、アプリケーション内でのデータ保存を想定しているため、処理速度に優れた対称暗号化を選択しました。
実装例
以下のコードを encryption.go
などのファイル名で保存して go run encryption.go
を実行してください。
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
const secretKey = "sk_0123456789abcdefghijklmnopqrstuvwxyz"
// AES-256-GCMで暗号化(バイト配列を返す)
func encryptAES256GCM(plaintext string, key []byte) ([]byte, error) {
plainbytes := []byte(plaintext)
// AESブロック暗号を作成
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("failed to create new AES cipher: %w", err)
}
// GCMを作成
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("failed to create new GCM: %w", err)
}
// nonceを生成
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, fmt.Errorf("failed to generate nonce: %w", err)
}
// 暗号化
// Sealはnonce + 暗号化データ + 認証タグを含む結果を返す
ciphertext := gcm.Seal(nonce, nonce, plainbytes, nil)
return ciphertext, nil
}
// AES-256-GCMで復号化(バイト配列から復号化)
func decryptAES256GCM(cipherbytes []byte, key []byte) (string, error) {
// AESブロック暗号を作成
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("failed to create new AES cipher: %w", err)
}
// GCMを作成
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("failed to create new GCM: %w", err)
}
// nonceサイズを取得
nonceSize := gcm.NonceSize()
// データサイズの検証
if len(cipherbytes) < nonceSize {
return "", fmt.Errorf("cipherbytes too short")
}
// nonceと暗号化データを分離
nonce, ciphertextData := cipherbytes[:nonceSize], cipherbytes[nonceSize:]
// 復号化実行
plaintext, err := gcm.Open(nil, nonce, ciphertextData, nil)
if err != nil {
return "", fmt.Errorf("failed to decrypt (data may have been tampered with): %w", err)
}
return string(plaintext), nil
}
// 256-bit (32-byte) の鍵を生成
func generateKey() ([]byte, error) {
key := make([]byte, 32) // 256-bit
_, err := rand.Read(key)
if err != nil {
return nil, fmt.Errorf("failed to generate key: %w", err)
}
return key, nil
}
func main() {
// 鍵を生成
key, err := generateKey()
if err != nil {
fmt.Printf("Key generation error: %v\n", err)
return
}
// 暗号化対象のデータ
plaintext := secretKey
fmt.Println("Original data :", plaintext)
// 暗号化
encrypted, err := encryptAES256GCM(plaintext, key)
if err != nil {
fmt.Printf("Encryption error: %v\n", err)
return
}
// 復号化
decrypted, err := decryptAES256GCM(encrypted, key)
if err != nil {
fmt.Printf("Decryption error: %v\n", err)
return
}
fmt.Println("Decrypted result:", decrypted)
// 検証
if plaintext == decrypted {
fmt.Println("✅ Encryption and decryption successful")
} else {
fmt.Println("❌ Failed")
}
}
Discussion