LoginSignup
5
3

More than 1 year has passed since last update.

Goで暗号化する備忘録(AES-128-CBC)

Last updated at Posted at 2021-03-22

はてなブログに移行しました

はじめに

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=

同じ結果が得られた。

参考文献

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3