Goで暗号化する備忘録(AES-128-CBC)
※ この記事は、2021年3月にQiitaに投稿した記事を移植したものです。
はじめに
Goで暗号化・復号の処理を書いたときに(主にパディングで)手こずったのでメモ。
暗号化
流れ
AES-128-CBCによる暗号化は以下のような流れで行う。
- 秘密鍵 (128 bit) の用意
- IV (128 bit) の生成
- パディング
- 暗号化
秘密鍵 (128 bit) の用意
省略。
IV (128 bit) の生成
IVは長さ16のランダムな byte
配列になる。この長さはAESにおけるブロックサイズなので、定数 aes.BlockSize
が使える。
import ( "crypto/aes" "crypto/rand" ) func GenerateIV() ([]byte, error) { iv := make([]byte, aes.BlockSize) if _, err := rand.Read(iv); err != nil { return nil, err } return iv, nil }
パディング
平文 []byte
の長さが16の倍数ではない可能性がある場合、16の倍数にするためにパディングする必要がある(パディングする場合は16の倍数でも16 byte足す必要がある)。
PKCS#7は l
bytes のパディングを行うときに l
個の l
を追加する方式。
import ( "bytes" "crypto/aes" ) func Pkcs7Pad(data []byte) []byte { length := aes.BlockSize - (len(data) % aes.BlockSize) trailing := bytes.Repeat([]byte{byte(length)}, length) return append(data, trailing...) }
暗号化
ここまでで用意した GenerateIV()
と Pkcs7Pad()
を使って実際に暗号化処理を行う。
import ( "crypto/aes" "crypto/cipher" ) func Encrypt(data []byte, key []byte) (iv []byte, encrypted []byte, err error) { iv, err = GenerateIV() if err != nil { return nil, nil, err } block, err := aes.NewCipher(key) if err != nil { return nil, nil, err } padded := Pkcs7Pad(data) encrypted = make([]byte, len(padded)) cbcEncrypter := cipher.NewCBCEncrypter(block, iv) cbcEncrypter.CryptBlocks(encrypted, padded) return iv, encrypted, nil }
復号
復号するときも BlockMode
構造体を作って CryptBlocks()
を呼ぶのは暗号化時と同じ。
パディングの削除は、最後の要素をintキャストした個数分だけ削ればよい。
import ( "crypto/aes" "crypto/cipher" ) func Pkcs7Unpad(data []byte) []byte { dataLength := len(data) padLength := int(data[dataLength-1]) return data[:dataLength-padLength] } func Decrypt(data []byte, key []byte, iv []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } decrypted := make([]byte, len(data)) cbcDecrypter := cipher.NewCBCDecrypter(block, iv) cbcDecrypter.CryptBlocks(decrypted, data) return Pkcs7Unpad(decrypted), nil }
動作確認
opensslコマンドと比較する。
import ( "encoding/base64" "encoding/hex" "fmt" ) func main() { text := "this is test message" fmt.Println("Input:", text) plain := []byte(text) keyString := "645E739A7F9F162725C1533DC2C5E827" key, _ := hex.DecodeString(keyString) fmt.Println("Key:", keyString) iv, encrypted, _ := Encrypt(plain, key) fmt.Println("IV:", hex.EncodeToString(iv)) fmt.Println("Encrypted:", base64.StdEncoding.EncodeToString(encrypted)) decrypted, _ := Decrypt(encrypted, key, iv) fmt.Printf("Decrypted: %s", decrypted) }
$ go run main.go Input: this is test message Key: 645E739A7F9F162725C1533DC2C5E827 IV: 71fbf00383b6e214dc08b8b94183cf30 Encrypted: z41UoV93PIS0OYElzUd7nwA9TO6XxSDlf9N+P4nFuJw= Decrypted: this is test message
opensslコマンド。echo
に -n
をつけないと改行で結果がずれる。
$ echo -n 'this is test message' | openssl aes-128-cbc -e -base64 -nosalt -K 645E739A7F9F162725C1533DC2C5E827 -iv 71fbf00383b6e214dc08b8b94183cf30 z41UoV93PIS0OYElzUd7nwA9TO6XxSDlf9N+P4nFuJw=
同じ結果が得られた。