結城浩さんの『暗号技術入門』を Go 言語を使って学ぶ記事の第3回です。前回は AES について解説しました。
今回は第4章「ブロック暗号のモード」です。そもそも、ブロック暗号の「モード」とは何なのでしょうか。
ブロック暗号の「モード」とは
前回の終わりにも書いたように、僕らが暗号化したい平文の長さは、たいていの場合ブロック暗号のブロック長 (AES なら 128 ビット) より長いことが多いです。そのため、ブロック暗号を繰り返し使って、長い文章全体を暗号化する必要があります。このブロック暗号を繰り返す方法のことをブロック暗号の「モード」と呼びます。
ブロック暗号のモードにはいくつもの種類がありますが、今回と次回でその中でも比較的使用が推奨されている CBC モードとCTR モードについて説明します。今回は CBC モードについてです。
なお、この記事中の Go のソースコードのバージョンはすべて 1.6 です。
Go におけるブロック暗号モード
Go ではブロック暗号のモードはすべて crypto/cipher
パッケージに定義されている BlockMode
という統一のインターフェースを介して利用するようになっています。 CryptoBlocks()
で src
のデータを dst
に暗号化または復号します。
1 | type BlockMode interface { |
Block
インターフェースと違い、 BlockMode
インターフェースには CryptBlocks()
メソッド1つしかありません。これは、そもそも暗号化と復号で別のオブジェクトを用いる必要があるからです。たとえば、あとで見るように、 CBC モードでの暗号化には NewCBCEncrypter()
によるオブジェクトを使って、復号には NewCBCDecrypter()
によるオブジェクトを使って CryptBlocks()
を呼ぶ必要があります。
CBC (Cipher Block Chaining) モード
CBC モードとは
CBC (Cipher Block Chaining) モードは、1つ前の暗号文ブロックと平文ブロックの内容を混ぜ合わせてから暗号化をおこなう方法です。暗号文ブロックをチェーンのように連鎖させることが名前の由来になっています。
では CBC モードの具体的なアルゴリズムを見てみましょう。
CBC モードのアルゴリズム
以下の図のように、 CBC モードでは1つ前の暗号文ブロックと平文ブロックの XOR をとってから暗号化をおこないます。 “block cipher encryption” の中身には、たとえば AES を用いるのであればその処理が入ります。
ただし、最初の平文ブロックを暗号化するときには「1つ前の暗号文ブロック」が存在しません。そこで、その代わりに「初期化ベクトル (initialization vector; IV)」というランダムなビット列を用意し、それを用います。
最終的に、生成される暗号文ブロックをつなげたものが暗号化されたデータになります。
逆に、復号化は以下の図のようになります。 XOR の対称的な性質により、復号化したブロックと1つ前の暗号文ブロックの XOR を再びとれば、元の平文が得られます。
CBC モードを使ってみる
Go 言語を使い、実際に CBC モードで長い平文を暗号化してみます。ブロック暗号としては AES を使います。
http://play.golang.org/p/_q54T5eDwe
1 | package main |
標準ライブラリのみを用いて直接暗号化をおこなおうとすると、結構面倒なことがわかります。実際にはアプリケーションが使いやすいようなラッパー処理を書き、それを用いることになると思います。
Go での実装
では標準ライブラリの crypto/cipher
において、 CBC モードがどのように実装されているのか見てみましょう。
CBC モードを表すのは以下の cbc
構造体です。
1 | type cbc struct { |
cbc
構造体のコンストラクタは newCBC()
です。書き換えの影響を受けないように、 cbc.iv
には引数の iv
を完全にコピーしたものが入れられます。
1 | func newCBC(b Block, iv []byte) *cbc { |
cbc
構造体は直接 BlockMode
インターフェースを実装してわけではありません。実際はそのエイリアスの cbcEncrypter
/ cbcDecrypter
型が暗号化 / 復号処理を実装しています。同じ構造体に複数の別名型をつけ、それぞれで違うメソッドを実装するというのは、参考になる実装方法ですね。
暗号化用オブジェクトは NewCBCEncrypter()
で生成します。この戻り値が BlockMode
インターフェースを実装した cbcEncrypter
オブジェクトになります。
1 | type cbcEncrypter cbc |
では、 cbcEncrypter
の CryptBlocks()
メソッドの実装を見てみましょう。やっているのは先に説明したアルゴリズム通り、平文ブロック (src[:x.BlockSize]
) と1つ前の暗号文ブロック (iv
) の XOR をとり (xorBytes()
)、それを暗号化する (x.b.Encrypt()
) ことです。
1 | func (x *cbcEncrypter) CryptBlocks(dst, src []byte) { |
一方、復号用オブジェクトは NewCBCDecrypter()
で生成します。この戻り値が BlockMode
インターフェースを実装した cbcDecrypter
オブジェクトになります。
1 | type cbcDecrypter cbc |
続いて cbcDecrypter
の CryptBlocks()
メソッドの実装を見てみます。 To avoid making a copy each time, we loop over the blocks BACKWARDS.
というコメントがある通り、復号処理は暗号文の末尾から逆順でおこなっているようです。しかしながら、「コピーを防ぐため」ということの意図はよくわかりませんでした。
1 | func (x *cbcDecrypter) CryptBlocks(dst, src []byte) { |
CBC モードについては以上です。次回はブロック暗号でよく使われるもう1つのモード、 CTR モードについて書く予定です。