Got Some \W+ech?

Could be Japanese. Could be English. Android, セキュリティ, 機械学習などをメインに、たまにポエムったり雑感記載したりします。

GoでAESアルゴリズム(CBCモード)を使った実装をする

前回ではAESアルゴリズムを用いた暗号化・復号をGoで実装してみたの続きです。

今日の内容

ただAESアルゴリズムによる暗号のみだと、攻撃者は暗号文を操作 => 平文を操作することができます。これを解決するための1つのやり方が、SSL/TLSにも用いられているCBC(Cipher Block Chaining)モードです。モードとは、固定長以上の平文をブロックにわけ、各ブロックを暗号化アルゴリズムで暗号化する手法をさします。

CBCモードとはなんぞや

CBCモードは、1つ前の暗号文ブロックと平文ブロックをXORしたものを暗号化することを繰り返す手法になります。最初の暗号文ブロックを生成する際には、ランダムに生成した初期化ベクトル(IV)を平文ブロックとXORする形になります。このIVは復号時にも利用なので別途保存する必要があり、最終的に生成される暗号文にappendして保存するのが一般的らしいです。

image

また、復号時には1つ前の暗号文ブロックと、復号された暗号文ブロックをXORしたものが平文ブロックになります。ここでもIVが必要なので、保存した暗号文からIVを取得します。

image

これにより、攻撃者による暗号文の操作したとしても目的通りの結果にさせないことが可能になります。

CBCモードにおける暗号文ブロックの変化に対する動き

  • 破損時(暗号文ブロックの値が何らかの理由により変わった場合): 破損した暗号文ブロックにより復号される平文ブロックと、その後に復号される平文ブロックに影響します。
  • ビット欠落: 欠落した暗号文ブロックにより復号される平文ブロックとその後の平文ブロックのすべて

GoによるAES CBCモードの実装

ではGoでの実装方法を見てみましょう.

func EncryptByCBCMode(key []byte, plainText string) ([]byte, error) {
    if len(plainText) % aes.BlockSize != 0 {
        panic("Plain text must be multiple of 128bit")
    }

    block, err := aes.NewCipher(key); if err != nil {
        return nil, err
    }

    cipherText := make([]byte,  aes.BlockSize + len(plainText)) // 初期化ベクトルを保存するためにaes.BlockSizeを加えている
    iv := cipherText[:aes.BlockSize] // Unique iv is required
    _, err = rand.Read(iv); if err != nil {
        return nil, err
    }

    cbc := cipher.NewCBCEncrypter(block, iv)
    cbc.CryptBlocks(cipherText[aes.BlockSize:], []byte(plainText))

    return cipherText, nil
}
func DecryptByCBCMode(key []byte, cipherText []byte) (string ,error) {
    block , err := aes.NewCipher(key); if err != nil {
        return "", err
    }

    if len(cipherText) < aes.BlockSize {
        panic("cipher text must be longer than blocksize")
    } else if len(cipherText) % aes.BlockSize != 0 {
        panic("cipher text must be multiple of blocksize(128bit)")
    }
    iv := cipherText[:aes.BlockSize] // assuming iv is stored in the first block of ciphertext
    cipherText = cipherText[aes.BlockSize:]
    plainText := make([]byte, len(cipherText))

    cbc := cipher.NewCBCDecrypter(block, iv)
    cbc.CryptBlocks(plainText, cipherText)
    fmt.Println(plainText)
    return string(plainText), nil
}
func main() {
    cipherText, _ = EncryptByCBCMode(key, "1234567891234567") // 16bye
    fmt.Println(cipherText)
    cipherText, _ = EncryptByCBCMode(key, "12345678912345671234123412341234") // 32byte
    fmt.Println(cipherText)
        // iv(32 byte) + 16byte

    plainText, _ = DecryptByCBCMode(key, cipherText)
    fmt.Println(plainText)
        // 12345678912345671234123412341234
}

以上。ただ、結局16の倍数 Byteの固定長平文しか暗号化できない。ブロック暗号の仕様上、しょうがないのでが、任意の長さの平文を暗号化したいのは当然の欲求だ。その場合、(16 - 平文%16)バイト文のパディングを追加する必要がある。次回は、それをかく。