Chapter 14
ブロック暗号と AES — GF(2^8) が実装に現れる
この章の位置づけ. 05 章で、1 バイトを 1 個の要素として四則演算できる体\(\mathrm{GF}(2^8)\)を作った。現在の標準的なブロック暗号である AES は、この体の上で逆元を取り、行列を掛けるという代数の操作で平文をかき混ぜる。 代数的な操作を使うのは、混ぜ方の性質を数学的に調べられるからである。AES では、代表的な攻撃である差分解読法や線形解読法に使える「特性」の確率の上限を証明できる(5 節)。
この章で使う既出の用語(定義は各リンク先). 逆元(02 章 1 節)、\(\mathrm{GF}(2^8)\)と AES の多項式 0x11B・xtime・0x57 × 0x13 の計算・定理 2 の系(05 章)、 シングルトン限界・MDS 符号(06 章 4 節)、CRC が改ざん検知に使えない理由(09 章 7 節)、 暗号化\(E_K\)・復号\(D_K\)・選択平文攻撃・パディングオラクル攻撃・単一換字式暗号・混同と拡散・S-box・IV(12 章)、ストリーム暗号・ChaCha20(13 章)
1. ブロック暗号の枠組み
ブロック暗号は、決まった長さのデータ(ブロック。AES では 128 ビット = 16 バイト)を単位に暗号化する:
鍵\(K\)を固定すると、\(E_K\)は\(2^{128}\)通りの平文を\(2^{128}\)通りの暗号文に 1 対 1 に対応させる。違う平文が同じ暗号文になると復号できないので、1 対 1 でなければならない。
設計原理: 混同と拡散
12 章 6 節で述べた 2 つの性質を、ラウンドと呼ぶ処理を何回も繰り返して作り出す。
- 混同: 鍵と暗号文の関係を複雑にする。XOR や行列の掛け算のような線形な変換だけでは連立 1 次方程式で解かれてしまう(13 章 3 節)ので、非線形な変換が必要である。
- 拡散: 平文の 1 ビットの変化を暗号文全体に広げる。これは線形な混ぜ合わせで効率よく作れる。
2 つの主要な構造
Feistel 構造(DES など): ブロックを左半分\(L\)と右半分\(R\)に分け、1 ラウンドで
とする。\(F\)はラウンド鍵\(K_i\)を使う何らかの関数である。
- 長所: 関数\(F\)が逆に計算できなくてもよい。\((L', R')\)から\(R = L'\)がそのまま分かり、\(L = R' \oplus F(L', K_i)\)で\(L\)も戻る。\(F\)の逆関数は使わないので、復号は同じ\(F\)をラウンド鍵の順を逆にして使えば済む。
SPN 構造(Substitution-Permutation Network。AES など): ブロック全体に、「小さな単位(バイトなど)ごとの非線形な置き換え」(混同)と「ビットやバイトの並べ替えと線形な混ぜ合わせ」(拡散)を交互にかける。
- 長所: 各バイトの処理が独立なので並列に計算でき、拡散も速い。ただし各部品が逆に計算できる必要がある。
2. AES の全体構造
AES は、ベルギーの Daemen と Rijmen が提案した Rijndael を、2001 年に米国の NIST が標準化したものである。鍵長によってラウンド数が決まる。
| 鍵長 | ラウンド数 |
|---|---|
| AES-128 | 10 |
| AES-192 | 12 |
| AES-256 | 14 |
状態 (State)
128 ビットの入力を 16 バイト\(b_0, b_1, \dots, b_{15}\)とし、\(4\times4\)のバイトの行列に列ごとに上から詰める。これを状態という:
処理の流れ
AES-128 の暗号化は次の順に進む。
- 最初にAddRoundKey(ラウンド鍵 0 を XOR)をかける。
- ラウンド 1〜9 では、それぞれ次の 4 つをこの順にかける。
- SubBytes — 各バイトを S-box で置き換える(混同、3 節)
- ShiftRows — 行ごとに左へ巡回シフトする(拡散、4 節)
- MixColumns — 各列に行列を掛ける(拡散、5 節)
- AddRoundKey — そのラウンドの鍵を XOR する(鍵を入れる、6 節)
- 最終ラウンド 10 では、MixColumns を省いて SubBytes、ShiftRows、AddRoundKey をかける。理由は 7 節で述べる。
ラウンド鍵はラウンド 0〜10 の 11 個が必要で、元の鍵から 6 節の鍵スケジュールで作る。
3. SubBytes — GF(2^8) の逆元
上の図で入力バイトを選ぶと、以下の 2 段階の計算と S-box の出力が表示される。
定義
各バイト\(a\)を次の 2 段階で変換する。
段階 1: \(\mathrm{GF}(2^8)\)(既約多項式\(x^8+x^4+x^3+x+1\) = 0x11B)での逆元を取る:
0 には逆元が無いので、0 だけは 0 に対応させる。\(\mathrm{GF}(2^8)\)では 0 以外の\(a\)が\(a^{255} = 1\)を満たす(05 章 4 節の定理 2 の系)ので、\(a \cdot a^{254} = 1\)、つまり\(a^{-1} = a^{254}\)である。
段階 2: ビット単位のアフィン変換(XOR による 1 次式に定数を足したもの)をかける。\(b\)の第\(i\)ビット(\(i = 0\)が最下位、\(i = 7\)が最上位)を\(b_i\)と書き、出力\(c\)の各ビットを
で決める。\(d_i\)は定数\(d = \texttt{0x63} = 01100011_2\)の第\(i\)ビットで、\(d_0 = d_1 = d_5 = d_6 = 1\)、ほかは 0 である。
計算例: a = 0x53
段階 1: \(\texttt{0x53}\)の逆元は\(\texttt{0xCA}\)である。05 章 3 節の方法で\(\texttt{0x53} \times \texttt{0xCA}\)を計算すると 1 になることで確かめられる。\(b = \texttt{0xCA} = 11001010_2\)なので、\(b_7 b_6 b_5 b_4 b_3 b_2 b_1 b_0 = 1\,1\,0\,0\,1\,0\,1\,0\)である。
段階 2: 各ビットを計算すると次のようになる。
| \(i\) | XOR するビット | 値 | \(d_i\) | \(c_i\) |
|---|---|---|---|---|
| 0 | \(b_0\) \(b_4\) \(b_5\) \(b_6\) \(b_7\) | 0 ⊕ 0 ⊕ 0 ⊕ 1 ⊕ 1 | 1 | 1 |
| 1 | \(b_1\) \(b_5\) \(b_6\) \(b_7\) \(b_0\) | 1 ⊕ 0 ⊕ 1 ⊕ 1 ⊕ 0 | 1 | 0 |
| 2 | \(b_2\) \(b_6\) \(b_7\) \(b_0\) \(b_1\) | 0 ⊕ 1 ⊕ 1 ⊕ 0 ⊕ 1 | 0 | 1 |
| 3 | \(b_3\) \(b_7\) \(b_0\) \(b_1\) \(b_2\) | 1 ⊕ 1 ⊕ 0 ⊕ 1 ⊕ 0 | 0 | 1 |
| 4 | \(b_4\) \(b_0\) \(b_1\) \(b_2\) \(b_3\) | 0 ⊕ 0 ⊕ 1 ⊕ 0 ⊕ 1 | 0 | 0 |
| 5 | \(b_5\) \(b_1\) \(b_2\) \(b_3\) \(b_4\) | 0 ⊕ 1 ⊕ 0 ⊕ 1 ⊕ 0 | 1 | 1 |
| 6 | \(b_6\) \(b_2\) \(b_3\) \(b_4\) \(b_5\) | 1 ⊕ 0 ⊕ 1 ⊕ 0 ⊕ 0 | 1 | 1 |
| 7 | \(b_7\) \(b_3\) \(b_4\) \(b_5\) \(b_6\) | 1 ⊕ 1 ⊕ 0 ⊕ 0 ⊕ 1 | 0 | 1 |
\(c_7 \cdots c_0 = 11101101_2 = \texttt{0xED}\)である。
なぜ逆元を使うのか
ブロック暗号に対する代表的な攻撃は次の 2 つで、どちらも S-box がどれだけ線形な関数に近いかで効きやすさが決まる。
- 差分解読法: 入力を\(\Delta\)だけ変えた(XOR した)とき、出力の変化が\(\Delta'\)になる確率(差分確率)が高い組\((\Delta, \Delta')\)を探し、ラウンドをまたいでつなげる攻撃である。逆元の写像では、どの\((\Delta, \Delta')\)(\(\Delta \neq 0\))でも、256 通りの入力のうちその変化を起こすのは高々 4 個で、差分確率は最大\(4/256 = 2^{-6}\)である。
- 線形解読法: 「入力の何ビットかの XOR」と「出力の何ビットかの XOR」が一致する確率\(p\)が\(1/2\)からずれている組を探す攻撃である。ずれ\(|p - 1/2|\)をバイアス、その 2 倍\(|2p - 1|\)を相関という。逆元の写像では、どの組でも相関は最大\(2^{-3}\)、バイアスは最大\(2^{-4}\)である。
これらは、8 ビットの 1 対 1 の写像として知られている中で最良の水準である。見た目が複雑な表を適当に作るのではなく、性質が分かっている代数的な写像を選び、その強さを数値で示すのが AES の設計の考え方である。
段階 2 のアフィン変換を加える理由: 逆元の写像だけだと、S-box は\(\mathrm{GF}(2^8)\)の式として\(a^{254}\)という単純な形で書ける。また\(0 \mapsto 0\)、\(1 \mapsto 1\)という、入力と出力が同じになる点(不動点)がある。 体の演算とは無関係なビット単位の変換を重ねることで、S-box を\(\mathrm{GF}(2^8)\)の単純な式で書けないようにし、不動点も無くしている。 ただし、ビット単位で見ると入力と出力の間には 2 次の関係式が多数(39 本)残る。これを使って暗号全体を連立方程式として解く代数的攻撃も研究されたが、現在まで AES に対する実用的な攻撃にはなっていない。
実装
実際には毎回逆元を計算せず、256 通りの入力それぞれについて 2 段階の結果をあらかじめ計算した256 バイトの表を引く。この表をS-boxという。上の計算例のとおり、表の\(\texttt{0x53}\)番目には\(\texttt{0xED}\)が入っている。
ただし表を引く実装では、どの位置を読んだかによって CPU のキャッシュの状態が変わり、処理時間の差から鍵を推定されるキャッシュタイミング攻撃のおそれがある。そのため最近の実装は、CPU の AES 専用命令(AES-NI など)や、表を使わずビット演算だけで計算する方法(ビットスライス)を使う。
4. ShiftRows — 列をまたいで混ぜる
第\(i\)行(\(i = 0, 1, 2, 3\))を左に\(i\)バイト巡回シフトする:
必要な理由: 次の MixColumns は列の中だけを混ぜる。ShiftRows が無いと、4 つの列は何ラウンド経っても混ざらず、128 ビットの暗号ではなく 32 ビットの暗号が 4 つ並んでいるのと同じになってしまう。 ShiftRows で各列のバイトを別々の列に移すことで、次のラウンドの MixColumns が全体を混ぜられるようになる。
5. MixColumns — GF(2^8) 上の行列の掛け算
各列(4 バイト)を\(\mathrm{GF}(2^8)\)の要素 4 つのベクトルとみなし、決まった行列を掛ける:
演算はすべて\(\mathrm{GF}(2^8)\)で行い、足し算は XOR、掛け算は05 章 3 節の方法による。必要な掛け算は\(\texttt{02}\)倍と\(\texttt{03}\)倍だけである。
- \(\texttt{02}\)倍(多項式としては\(x\)倍): 05 章の xtime である。1 ビット左シフトし、最上位ビットが 1 だった場合は\(\texttt{0x1B}\)を XOR する。
- \(\texttt{03}\)倍: \(\texttt{03} = \texttt{02} \oplus \texttt{01}\)なので\((\texttt{02}\cdot b) \oplus b\)である。
計算例
1 行目は\(c_0 = \texttt{02}\cdot b_0 \oplus \texttt{03}\cdot b_1 \oplus b_2 \oplus b_3\)である。入力の列を\((b_0, b_1, b_2, b_3) = (\texttt{DB}, \texttt{13}, \texttt{53}, \texttt{45})\)とすると
- \(\texttt{02}\cdot\texttt{DB}\): \(\texttt{DB} = 11011011_2\)を左シフトして下位 8 ビットを取ると\(10110110_2 = \texttt{B6}\)、最上位が 1 だったので\(\texttt{B6} \oplus \texttt{1B} = \texttt{AD}\)。
- \(\texttt{03}\cdot\texttt{13}\): \(\texttt{02}\cdot\texttt{13} = \texttt{26}\)(最上位が 0 なのでシフトだけ)、\(\texttt{26} \oplus \texttt{13} = \texttt{35}\)。
- \(c_0 = \texttt{AD} \oplus \texttt{35} \oplus \texttt{53} \oplus \texttt{45} = \texttt{8E}\)。
残りの行も同様に計算すると、出力の列は\((\texttt{8E}, \texttt{4D}, \texttt{A1}, \texttt{BC})\)になる。
この行列の設計意図
この行列はMDS 行列である。入力 4 バイトと出力 4 バイトを並べた 8 バイトを 1 つの符号語とみなすと、この行列は長さ 8、情報 4 バイトの MDS 符号を作っている。06 章 4 節のシングルトン限界を等号で満たすので、最小距離は\(8 - 4 + 1 = 5\)である。 つまり 2 つの入力の列が違えば、「入力で違うバイトの数」と「出力で違うバイトの数」の和は必ず 5 以上になる。特に、入力 4 バイトのうち 1 バイトだけを変えると、出力の 4 バイトはすべて変わる(\(1 + 4 = 5\))。
この性質により、AES は 2 ラウンドで完全拡散する。1 ラウンド目の MixColumns で 1 バイトの変化が列の 4 バイトに広がり、2 ラウンド目で ShiftRows によって 4 つの列に分かれてから MixColumns で 16 バイト全体に広がる。
差分解読法や線形解読法の効きやすさは、変化が通過する S-box の個数(活性 S-boxの数)で決まる。S-box を 1 つ通るごとに差分確率は最大で\(2^{-6}\)倍になるので、活性 S-box が多いほど攻撃に必要なデータが指数的に増える。 MDS の性質から、連続する 4 ラウンドで活性 S-box は最低 25 個あることが証明でき、4 ラウンド分の差分確率は\((2^{-6})^{25} = 2^{-150}\)以下になる。ブロック長 128 ビットに対して\(2^{-128}\)より小さければ攻撃に使えないので、10 ラウンドは十分な余裕を持たせた設計である。 実際、現在知られている AES-128 への最良の攻撃も、総当たりよりわずかに速い程度にとどまっている。
誤り訂正符号の MDS という概念が、暗号の拡散の設計にそのまま使われている。
6. 鍵スケジュール
AES-128 の 128 ビットの鍵から、ラウンド 0〜10 の 11 個のラウンド鍵(各 128 ビット)を作る。
4 バイトを 1 語とし、\(W_i\)と書く。\(W_0, W_1, W_2, W_3\)は元の鍵を 4 語に切ったものである。\(i = 4, 5, \dots, 43\)について
で作る。\(W_{4r}, \dots, W_{4r+3}\)がラウンド\(r\)の鍵で、\(r = 0\)は元の鍵そのものである。
- RotWord: 4 バイトを 1 バイト左に巡回させる。\((a_0,a_1,a_2,a_3) \to (a_1,a_2,a_3,a_0)\)
- SubWord: 各バイトに S-box をかける。
- Rcon: ラウンド定数\(\mathrm{Rcon}_j = (\texttt{02}^{\,j-1}, 0, 0, 0)\)。\(\texttt{02}^{\,j-1}\)は\(\mathrm{GF}(2^8)\)でのべき乗で、\(\mathrm{Rcon}_1 = (\texttt{01},0,0,0)\)、\(\mathrm{Rcon}_2 = (\texttt{02},0,0,0)\)、\(\mathrm{Rcon}_3 = (\texttt{04},0,0,0)\)、…である。上の式では\(j = i/4\)で、\(i = 4\)なら\(\mathrm{Rcon}_1\)を使う。
計算例(FIPS 197 の例): 鍵が2b7e1516 28aed2a6 abf71588 09cf4f3c(16 進)のとき、\(W_0 = \texttt{2b7e1516}\)、\(W_3 = \texttt{09cf4f3c}\)である。\(W_4\)は
| 手順 | 値 |
|---|---|
| \(W_3\) | 09 cf 4f 3c |
| RotWord | cf 4f 3c 09 |
| SubWord(各バイトに S-box) | 8a 84 eb 01 |
Rcon\(_1\) = 01 00 00 00を XOR | 8b 84 eb 01 |
\(W_0\) = 2b 7e 15 16を XOR | a0 fa fe 17 |
で、\(W_4 = \texttt{a0fafe17}\)となる。\(W_5 = W_1 \oplus W_4\)、…と続けていく。
Rcon がラウンドごとに違う理由: どのラウンドも同じ処理だと、ラウンドを 1 つずらして対応付けるスライド攻撃が成り立ってしまう。ラウンドごとに違う定数を入れて、この対称性を壊している。
7. 復号と、最終ラウンドに MixColumns が無い理由
復号は、各操作の逆を逆の順番でかける。
- InvSubBytes: S-box の逆の表を引く(アフィン変換を戻してから逆元を取る)。
- InvShiftRows: 第\(i\)行を右に\(i\)バイト巡回シフトする。
- InvMixColumns: 次の逆行列を掛ける。
5 節の出力\((\texttt{8E}, \texttt{4D}, \texttt{A1}, \texttt{BC})\)にこの行列を掛けると、入力\((\texttt{DB}, \texttt{13}, \texttt{53}, \texttt{45})\)に戻る。
- AddRoundKey: XOR なので、同じラウンド鍵をもう一度 XOR すれば戻る。
最終ラウンドに MixColumns が無い理由: 最後に MixColumns をかけても、その後には AddRoundKey しか無い。MixColumns は線形なので、\(\mathrm{MC}(x) \oplus k = \mathrm{MC}\bigl(x \oplus \mathrm{MC}^{-1}(k)\bigr)\)と書き直せる。 攻撃者は暗号文に自分で\(\mathrm{MC}^{-1}\)をかけ、鍵も\(\mathrm{MC}^{-1}(k)\)という別の鍵とみなせば、最後の MixColumns が無い暗号と同じものとして扱える。つまり最終ラウンドの MixColumns は安全性に何も寄与しない。 省くと、暗号化と復号の処理の並びをそろえやすくなり、実装上の利点がある。
8. 利用モード — ここを間違えると全部台無し
ブロック暗号は 1 回に 16 バイトしか暗号化できない。長いデータを暗号化するには、ブロックの使い方である利用モードが必要である。以下、平文を 16 バイトずつ\(P_1, P_2, \dots\)、暗号文を\(C_1, C_2, \dots\)とする。
| モード | 暗号化の方法 | 評価 | ||
|---|---|---|---|---|
| ECB | 各ブロックを独立に暗号化: \(C_i = E_K(P_i)\) | 使ってはいけない | ||
| CBC | 前の暗号文と XOR してから暗号化: \(C_i = E_K(P_i \oplus C_{i-1})\)、\(C_0 = \mathrm{IV}\) | パディングオラクル攻撃(12 章 3 節)に注意 | ||
| CTR | IV とブロック番号\(i\)を並べた値$`\mathrm{IV} \,\ | \, i$を暗号化し、それを鍵ストリームにする: $C_i = P_i \oplus E_K(\mathrm{IV}\,\ | \,i)`$(13 章 1 節のストリーム暗号の形) | 並列に計算できる |
| GCM | CTR モードに、改ざん検知用の認証タグ(暗号文から鍵を使って計算する短い値)を加えたもの | 推奨(認証付き暗号) |
\(\|\)はビット列を並べてつなぐこと(連結)を表す。
ECB を使ってはいけない理由: ECB では同じ平文ブロックが必ず同じ暗号文ブロックになるので、平文のパターンが暗号文に残る。 画像データを 16 バイトずつ ECB で暗号化すると、同じ色が続く領域は同じ暗号文ブロックの繰り返しになり、色の境界は境界として残る。Linux のマスコットのペンギン画像を ECB で暗号化すると、色は変わってもペンギンの形がはっきり見えるという有名な例がある。 これは12 章 4 節の単一換字式暗号が頻度分析で破れるのと同じ失敗で、ブロック単位の「換字」になっている。
暗号化だけでは改ざんを防げない: CTR モードでは\(P_i = C_i \oplus Z_i\)なので、攻撃者が暗号文の 1 ビットを反転させると、復号した平文の同じビットが反転する。内容が分からなくても、狙った位置を書き換えられる。 そのため現在は、暗号化と改ざん検知を同時に行う認証付き暗号(AEAD)、たとえば AES-GCM や ChaCha20-Poly1305 を使う。これは09 章 7 節で CRC が改ざん検知に使えないと述べたのと同じ問題で、改ざん検知には鍵を使う MAC(18 章)が必要である。
9. まとめ
| 要素 | 数学的な正体 | 役割 |
|---|---|---|
| SubBytes | \(\mathrm{GF}(2^8)\)の逆元とアフィン変換 | 混同(非線形) |
| ShiftRows | バイトの巡回シフト | 拡散(列をまたぐ) |
| MixColumns | \(\mathrm{GF}(2^8)\)上の MDS 行列の掛け算 | 拡散(列の中) |
| AddRoundKey | XOR | 鍵を入れる |
| 鍵スケジュール | S-box と\(\mathrm{GF}(2^8)\)でのべき乗 | ラウンド鍵を作る |
AES の安全性は\(\mathrm{GF}(2^8)\)の代数的な性質に支えられており、05 章で作った体が標準暗号の中心部分になっている。