はてなブログに移行しました
はじめに
Goで暗号化・復号の処理を書いたときに(主にパディングで)手こずったのでメモ。
暗号化
流れ
AES-128-CBCによる暗号化は以下のような流れで行う。
- 秘密鍵 (128 bit) の用意
- IV (128 bit) の生成
- パディング
- 暗号化
秘密鍵 (128 bit) の用意
省略。
IV (128 bit) の生成
IVは長さ16のランダムな byte
配列になる。この長さはAESにおけるブロックサイズなので、定数 aes.BlockSize
が使える。
iv
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
を追加する方式。
padding
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()
を使って実際に暗号化処理を行う。
encrypt
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キャストした個数分だけ削ればよい。
decrypt
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コマンドと比較する。
main.go
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=
同じ結果が得られた。