GoでAESアルゴリズム(CBCモード)+PKCS7パディング+HMACを使った実装をする


GoでAESアルゴリズム(CBCモード)+PKCS7パディング+HMACを使った実装をするの続きです。

さて、前回はAES+CBC+PKCSパディングを使った実装例を紹介した。パディングを使って、任意の平文をブロック型暗号で暗号化することが可能になったが、一方で脆弱性がうまれてしまった。暗号文+パディングを繰り返し送ることで平文を一部推測できてしまうような脆弱性だ。この脆弱性の根本原因は、誰もが復号できてしまう部分にある。従って、復号処理のための暗号文を入力するものを認証することで解決できる。その仕組がMAC(Message Authentication Code)であり、今回はHMAC(keyed-hash MAC)を使った実装例が今回のものになる。

AES暗号化によるHMACとは

暗号文をHMAC用の共有鍵とハッシュ関数で導きだされるもの。復号をするまえにHMACを検証することで、サブジェクトの真性正を認証することができる。こ

GoにおけるHMACは?

標準パッケージ内のcrypt/hmacで実装されている。パディングは実装されていなかったのに、なぜなのか。謎である。実装自体はシンプルになる。

暗号化.go
func EncryptByCBCMode(key []byte, plainText string) ([]byte, error) {
    block, err := aes.NewCipher(key); if err != nil {
        return nil, err
    }

    paddedPlaintext := PadByPkcs7([]byte(plainText))
    cipherText := make([]byte, len(paddedPlaintext)) // cipher text must be larger than plaintext
    iv := make([]byte, aes.BlockSize)// Unique iv is required
    _, err = rand.Read(iv); if err != nil {
        return nil, err
    }

    cbc := cipher.NewCBCEncrypter(block, iv)
    cbc.CryptBlocks(cipherText, paddedPlaintext)
    cipherText = append(iv, cipherText...)

        // MAC作成
    mac := hmac.New(sha256.New, []byte("12345678912345678912345678912345")) // sha256のhmac_key(32 byte)
    mac.Write(cipherText)
    cipherText = mac.Sum(cipherText)
    return []byte(cipherText), nil
復号.go
func DecryptByCBCMode(key []byte, cipherText []byte) (string, error) {
    if len(cipherText) < aes.BlockSize + sha256.Size {
        panic("cipher text must be longer than blocksize")
    } else if len(cipherText) % aes.BlockSize != 0 {
        panic("cipher text must be multiple of blocksize(128bit)")
    }

    // macの取り出し
    macSize := len(cipherText) - sha256.Size
    macMessage := cipherText[macSize:]

        // 暗号文から想定macを計算
    mac := hmac.New(sha256.New, []byte("12345678912345678912345678912345")) // sha256のhmac_key(32 byte)
    mac.Write(cipherText[:macSize])
    expectedMAC := mac.Sum(nil)

        // MACによる認証
    if !hmac.Equal(macMessage, expectedMAC) {
        return "", errors.New("Failed Decrypting")
    }

    iv := cipherText[:aes.BlockSize]
    plainText := make([]byte, len(cipherText[aes.BlockSize:macSize]))
    block, err := aes.NewCipher(key); if err != nil {
        return "", err
    }
    cbc := cipher.NewCBCDecrypter(block, iv)
    cbc.CryptBlocks(plainText, cipherText[aes.BlockSize:macSize])

    return string(UnPadByPkcs7(plainText)), nil
}

その他

AES +CBC +パディング + hmacでためしたが、 AES +GCMで全てやりたかったことが出来るみたいなので、次回はそちらを試してみる.