なぜ対称鍵暗号を学ぶか
対称鍵暗号 (symmetric-key cryptography) は、暗号化と復号に同じ鍵を使う方式です。RSA や 楕円曲線暗号 (ECC) のような公開鍵暗号が「鍵をどう共有するか」を解決するのに対し、対称鍵暗号は「共有した鍵で大量のデータを高速に暗号化する」役割を担います。AES はハードウェア支援 (AES-NI) 込みで公開鍵暗号の数百〜数千倍高速であり、TLS・ディスク暗号化・VPN で実際にデータを守っているのはほぼすべて対称鍵暗号です。
現代の主役は 2 つです。AES (Advanced Encryption Standard) は 2001 年に NIST が標準化したブロック暗号で、AES-NI 命令を持つ CPU では 1 バイトあたり 1 サイクル未満で動作します。ChaCha20 は Daniel J. Bernstein が設計したストリーム暗号で、AES-NI のないモバイル / 組み込み環境でも定数時間・高速に動作するため、TLS 1.3 では AES-256-GCM / AES-128-GCM / ChaCha20-Poly1305 の 3 つが標準暗号スイートに並んでいます。
一方で対称鍵暗号は「アルゴリズムを正しく選ぶ」だけでは安全になりません。モード (ECB/CBC/CTR/GCM) の選択ミス、IV/nonce の再利用、認証なし暗号化という 3 大事故パターンが現場では繰り返されています。本記事では、AES のラウンド構成と GF(2^8) の数学、ブロック暗号モードの設計、ChaCha20-Poly1305 との比較を整理し、Python で (1) cryptography ライブラリによる実用 AEAD コード、(2) 教育用のフルスクラッチ AES-128 を実装します。暗号領域全体の俯瞰は暗号技術ロードマップを参照してください。
対称鍵 vs 公開鍵:ハイブリッド暗号という役割分担
まず全体像です。実際のシステムは対称鍵と公開鍵のハイブリッド構成で動きます。
| 項目 | 対称鍵暗号 (AES / ChaCha20) | 公開鍵暗号 (RSA / ECC) |
|---|---|---|
| 鍵 | 送受信者が同じ鍵を共有 | 公開鍵と秘密鍵のペア |
| 速度 | 非常に高速 (GB/s 級) | 低速 (対称鍵の数百〜数千倍遅い) |
| 鍵長 | 128〜256 bit | RSA 2048〜4096 bit / ECC 256〜512 bit |
| 課題 | 鍵配送問題 | 速度・大量データに不向き |
| 主な用途 | データ本体の暗号化 | 鍵交換・署名・認証 |
対称鍵暗号の弱点は鍵配送問題です。安全にデータを送るには鍵の共有が必要ですが、その鍵を送る安全な経路がない、という循環に陥ります。これを解決するのが Diffie-Hellman 鍵交換 や RSA / ElGamal による鍵カプセル化です。
TLS 1.3 の実際の流れはこうです。
- 鍵交換: ECDHE (X25519 / P-256) で一時的な共有秘密を確立(前方秘匿性のため毎回使い捨て)
- 鍵導出: 共有秘密を HKDF に通してセッション鍵(対称鍵)を導出
- 本体暗号化: セッション鍵を使い AES-GCM または ChaCha20-Poly1305 でアプリケーションデータを暗号化
つまり公開鍵暗号は「最初の数百バイト」だけを担当し、残りの全データは対称鍵暗号が守ります。本記事はこの第 3 段を深掘りします。
AES のラウンド構成
AES は 128 bit (16 byte) ブロックを単位に、SPN (Substitution-Permutation Network) 構造で暗号化するブロック暗号です。16 byte の内部状態を 4×4 バイト行列(state、列優先)とみなし、4 種類の変換を ラウンドとして繰り返します。ラウンド数は鍵長で決まります。
| 鍵長 | ラウンド数 \(N_r\) | ラウンド鍵の数 |
|---|---|---|
| 128 bit | 10 | 11 |
| 192 bit | 12 | 13 |
| 256 bit | 14 | 15 |
1 ラウンドは次の 4 変換の合成です(最終ラウンドのみ MixColumns を省略)。
SubBytes — 非線形置換
各バイトを S-box で置換します。S-box は「GF(2^8) 上の逆元 \(x^{-1}\) 」と「GF(2) 上のアフィン変換」の合成で、
\[ S(x) = A \cdot x^{-1} \oplus c \quad (c = \mathtt{0x63}) \tag{1} \]と定義されます(\(x = 0\) のときは \(x^{-1} = 0\) と約束)。逆元写像は唯一の非線形要素であり、差分解読法・線形解読法への耐性はここから生まれます。アフィン変換は代数的な単純さ(補間攻撃への弱さ)を隠すためのものです。
ShiftRows — 行の巡回シフト
state の第 \(r\) 行を左に \(r\) バイト巡回シフトします(\(r = 0, 1, 2, 3\) )。これにより 1 つの列にあった 4 バイトが 4 つの異なる列へ拡散します。
MixColumns — 列の線形変換
各列を GF(2^8) 上の 4 次元ベクトルとみなし、固定行列を乗算します。
\[ \begin{pmatrix} b_0 \\ b_1 \\ b_2 \\ b_3 \end{pmatrix} = \begin{pmatrix} 02 & 03 & 01 & 01 \\ 01 & 02 & 03 & 01 \\ 01 & 01 & 02 & 03 \\ 03 & 01 & 01 & 02 \end{pmatrix} \begin{pmatrix} a_0 \\ a_1 \\ a_2 \\ a_3 \end{pmatrix} \tag{2} \]この行列は MDS (Maximum Distance Separable) 行列で、「入力 1 バイトの変化が出力 4 バイトすべてに波及する」最大拡散を保証します。ShiftRows(行方向)と MixColumns(列方向)を 2 ラウンド重ねると、1 バイトの変化が state 全体 16 バイトに波及します(完全拡散)。
AddRoundKey — 鍵の混入
state とラウンド鍵のバイトごとの XOR です。唯一鍵が関与する変換であり、XOR なので暗号化と復号で同じ演算になります。
鍵スケジュール (Key Expansion)
元の鍵から \(N_r + 1\) 個のラウンド鍵を生成します。AES-128 では 16 byte の鍵を 4 ワードとして、ワード \(w_i\) (\(i \ge 4\) )を
\[ w_i = w_{i-4} \oplus \begin{cases} \mathrm{SubWord}(\mathrm{RotWord}(w_{i-1})) \oplus \mathrm{Rcon}_{i/4} & (i \equiv 0 \bmod 4) \\ w_{i-1} & (\text{otherwise}) \end{cases} \tag{3} \]で拡張します。RotWord は 1 バイト巡回シフト、SubWord は S-box 適用、Rcon はラウンド定数 \(x^{j-1} \in \mathrm{GF}(2^8)\) です。
GF(2^8) 上の演算
AES の数学的基盤は位数 256 の有限体 (ガロア体) \(\mathrm{GF}(2^8)\) です。1 バイト \(b_7 b_6 \cdots b_0\) を GF(2) 係数の多項式
\[ b(x) = b_7 x^7 + b_6 x^6 + \cdots + b_1 x + b_0 \tag{4} \]と同一視します。加算は XOR そのものです(係数ごとの mod 2 加算)。乗算は多項式の積を既約多項式
\[ m(x) = x^8 + x^4 + x^3 + x + 1 \quad (= \mathtt{0x11B}) \tag{5} \]で割った余りとして定義します。\(m(x)\)
が既約であることにより、0 以外のすべての元が逆元を持つ「体」になります。実装上は「\(x\)
倍 (xtime) = 左シフトして、あふれたら 0x1B と XOR」を基本演算とし、任意の乗算はシフトと XOR の組み合わせに分解できます。逆元は拡張ユークリッド互除法か、位数の性質 \(a^{255} = 1\)
から
として繰り返し二乗法で計算できます。SubBytes の S-box (式 1) も MixColumns の行列乗算 (式 2) もすべてこの体の上の演算であり、後述のフルスクラッチ実装では S-box をテーブル定数ではなく式 (1), (6) から構成して、この構造を確かめます。
ブロック暗号モード:ECB の危険性から GCM まで
AES 単体は「16 byte を 16 byte に写す関数」にすぎません。実際のメッセージは 16 byte を超えるので、ブロックをどう連結するかを決める暗号利用モード (mode of operation) が必要です。モードの選択こそが実務上の安全性を左右します。
ECB — 使ってはいけないモード
ECB (Electronic CodeBook) は各ブロックを独立に暗号化します。
\[ C_i = E_K(P_i) \tag{7} \]同じ平文ブロックが常に同じ暗号文ブロックになるため、データのパターンが暗号文に透けます。ペンギンの画像を ECB で暗号化するとシルエットが残る「ECB ペンギン」が有名な失敗例です。ECB は事実上いかなる用途にも推奨されません。
CBC — 連鎖による撹拌とパディング
CBC (Cipher Block Chaining) は前の暗号文ブロックと XOR してから暗号化します。
\[ C_i = E_K(P_i \oplus C_{i-1}), \quad C_0 = IV \tag{8} \]初回は初期化ベクトル (IV) を使います。IV は秘密でなくてよいものの、予測不能(ランダム)でなければなりません。CBC はブロック単位でしか暗号化できないため、平文を 16 byte の倍数に揃える PKCS#7 パディング(不足 \(n\) byte を値 \(n\) で埋める)が必要で、このパディング検証の挙動差を突くパディングオラクル攻撃 (Vaudenay, 2002) が長年 TLS を苦しめました。TLS 1.3 で CBC 系スイートが全廃されたのはこのためです。
CTR — ブロック暗号をストリーム暗号化する
CTR (Counter) モードはカウンタ値を暗号化して鍵ストリームを作り、平文と XOR します。
\[ C_i = P_i \oplus E_K(\mathrm{nonce} \,\|\, i) \tag{9} \]パディング不要・並列化可能・復号も同じ演算、と利点が多く、現代モードの基礎です。ただし式 (9) から明らかなように、同じ nonce とカウンタの組を 2 度使うと鍵ストリームが一致し、\(C_1 \oplus C_2 = P_1 \oplus P_2\) が漏れます(two-time pad 問題)。
GCM — 認証付き暗号 (AEAD)
CTR までのモードは機密性しか提供せず、暗号文を改竄されても検出できません。CBC/CTR の暗号文はビット反転攻撃で平文を狙って書き換えられます。そこで現代の標準は認証付き暗号 (AEAD: Authenticated Encryption with Associated Data) です。
GCM (Galois/Counter Mode) は CTR モードの暗号化に、\(\mathrm{GF}(2^{128})\) 上の多項式評価 GHASH による認証タグを組み合わせます。
\[ T = \mathrm{GHASH}_H(A, C) \oplus E_K(\mathrm{nonce} \,\|\, 0) \tag{10} \]ここで \(H = E_K(0^{128})\) は認証用サブキー、\(A\) は暗号化しないが改竄検知したい関連データ (AAD)(ヘッダなど)です。復号側はタグを再計算して一致しなければ平文を一切返さずにエラーにします。
nonce の扱いが GCM 最大の急所です。GCM の nonce は 96 bit で、同じ鍵で同じ nonce を 2 度使うと、(a) CTR の鍵ストリーム再利用で平文の XOR が漏れ、さらに (b) GHASH のサブキー \(H\) が代数的に復元されて以後すべての認証タグが偽造可能になります (Joux の “forbidden attack”)。nonce はカウンタ管理(メッセージ番号)にするか、ランダム生成なら同一鍵での暗号化回数を \(2^{32}\) 回以下に抑えます。誤用耐性が必要なら AES-GCM-SIV という選択肢もあります。
| モード | 機密性 | 完全性 | パディング | 並列化 | 備考 |
|---|---|---|---|---|---|
| ECB | × | × | 必要 | 可 | 使用禁止 |
| CBC | ○ | × | 必要 | 復号のみ | パディングオラクルの温床 |
| CTR | ○ | × | 不要 | 可 | nonce 再利用で即崩壊 |
| GCM | ○ | ○ | 不要 | 可 | 現代の標準 (AEAD) |
ChaCha20-Poly1305:ARX 構造のストリーム暗号
ChaCha20 は Bernstein の Salsa20 を改良したストリーム暗号で、Poly1305 MAC と組み合わせた ChaCha20-Poly1305 が RFC 8439 で標準化されています。TLS 1.3・SSH・WireGuard が採用し、AES-GCM と並ぶもう 1 つのデファクト AEAD です。
ARX 構造
ChaCha20 の内部状態は 32 bit ワード 16 個(512 bit)の 4×4 行列で、定数 ("expand 32-byte k")・256 bit 鍵・カウンタ・96 bit nonce で初期化されます。基本演算は ARX (Addition-Rotation-XOR) の 3 種類のみです。
これを 4 ワードに対して 4 回繰り返す quarter round を、列方向と対角方向に交互に計 20 ラウンド適用し、初期状態を加算したものが 64 byte の鍵ストリームブロックになります。AES の S-box のようなテーブル参照が存在しないため、キャッシュタイミング攻撃に対して構造的に定数時間です。
AES-GCM との使い分け
| 項目 | AES-256-GCM | ChaCha20-Poly1305 |
|---|---|---|
| 種別 | ブロック暗号 + GCM モード | ストリーム暗号 + Poly1305 |
| 基本演算 | S-box・GF(2^8) / GF(2^128) | ARX (加算・回転・XOR) |
| 鍵長 / nonce | 256 bit / 96 bit | 256 bit / 96 bit |
| AES-NI あり | 極めて高速 (~1 GB/s 超) | 高速 |
| AES-NI なし | 低速 + テーブル参照でタイミング攻撃の懸念 | 高速かつ定数時間 |
| ハードウェア | サーバ・PC 向き | モバイル・組み込み・IoT 向き |
| 標準化 | NIST SP 800-38D | RFC 8439 |
| 採用例 | TLS, IPsec, ディスク暗号化 | TLS, SSH, WireGuard, Signal |
要点は「AES-NI があれば AES-GCM、なければ ChaCha20-Poly1305」です。ソフトウェア実装の AES はテーブル参照のキャッシュ挙動が秘密情報に依存し得るのに対し、ChaCha20 は CPU の基本命令だけで完結します。Google が Android 向けに ChaCha20 系を推進した理由もここにあります。安全性の面では、どちらも 2026 年現在有効な攻撃は知られておらず、成熟度で並んでいます。
Python 実装
1. 実用: cryptography ライブラリの AESGCM / ChaCha20Poly1305
本番コードでは cryptography.hazmat.primitives.ciphers.aead の高水準 AEAD API を使います。モード選択やパディングの余地がなく、誤用しにくい設計です。
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305
# --- AES-256-GCM ---
key = AESGCM.generate_key(bit_length=256) # 内部は os.urandom
aesgcm = AESGCM(key)
nonce = os.urandom(12) # 96 bit。同じ鍵で絶対に再利用しない
aad = b"header: message-id=42" # 暗号化しないが改竄検知する関連データ
ciphertext = aesgcm.encrypt(nonce, b"secret message", aad)
plaintext = aesgcm.decrypt(nonce, ciphertext, aad) # タグ不一致なら InvalidTag
assert plaintext == b"secret message"
# 暗号文はタグ 16 byte を含むため平文より 16 byte 長い
print(f"暗号文長: {len(ciphertext)} byte (平文 14 + タグ 16)")
# --- ChaCha20-Poly1305: API は完全に同型 ---
key2 = ChaCha20Poly1305.generate_key() # 常に 256 bit
chacha = ChaCha20Poly1305(key2)
nonce2 = os.urandom(12)
ct2 = chacha.encrypt(nonce2, b"secret message", aad)
assert chacha.decrypt(nonce2, ct2, aad) == b"secret message"
# --- 改竄検知のデモ ---
from cryptography.exceptions import InvalidTag
tampered = bytes([ciphertext[0] ^ 0x01]) + ciphertext[1:]
try:
aesgcm.decrypt(nonce, tampered, aad)
except InvalidTag:
print("改竄を検知: 平文は一切返らない")
実務での約束事は 3 つです。
- 鍵は
AESGCM.generate_key()かos.urandom(32):randomモジュールは疑似乱数なので鍵生成に使ってはいけません。 - nonce は 12 byte を毎回新規生成(またはカウンタ管理)。保存・送信は平文のままで構いませんが、同じ鍵での再利用だけは絶対に避けます。
- 低水準 API (
Cipher+modes.CBCなど) は原則使わない:cryptography.hazmat.primitives.ciphersのCipherで CBC/CTR を直接組むのは、パディングやタグ管理を自前で背負うことを意味します。AEAD API で足りるなら常にそちらを選びます。
なお nonce 再利用の危険は実感しておく価値があります。CTR 系である GCM / ChaCha20 は、同じ (鍵, nonce) で 2 つの平文を暗号化すると ct1 XOR ct2 == pt1 XOR pt2 が成立し、平文の構造が漏れます。
2. 教育用: AES-128 ラウンド関数のフルスクラッチ実装
学習用に、S-box をテーブル定数からではなく式 (1), (6) の定義通りに構成し、AES-128 の 1 ブロック暗号化を標準ライブラリのみで実装します。FIPS-197 Appendix C.1 の公式テストベクトルで検証済みです(このコードは教育用であり、サイドチャネル対策が皆無のため本番使用は厳禁)。
# 教育用: AES-128 のラウンド関数をフルスクラッチ実装 (FIPS-197 準拠)
# --- GF(2^8) 上の乗算: 既約多項式 x^8 + x^4 + x^3 + x + 1 (0x11B) ---
def gf_mul(a, b):
"""GF(2^8) の乗算: シフトと XOR の繰り返し (carry-less)"""
r = 0
for _ in range(8):
if b & 1:
r ^= a # 係数が 1 なら足す (XOR)
b >>= 1
carry = a & 0x80
a = (a << 1) & 0xFF
if carry:
a ^= 0x1B # x^8 を x^4 + x^3 + x + 1 で置換
return r
def gf_inv(a):
"""GF(2^8) の逆元: a^254 (式 6)。繰り返し二乗法で計算"""
if a == 0:
return 0
result, base, exp = 1, a, 254
while exp:
if exp & 1:
result = gf_mul(result, base)
base = gf_mul(base, base)
exp >>= 1
return result
# --- S-box の構成: 逆元 + アフィン変換 (式 1) ---
def make_sbox():
sbox = []
for x in range(256):
b, c = gf_inv(x), 0x63
y = 0
for i in range(8):
bit = ((b >> i) ^ (b >> ((i + 4) % 8)) ^ (b >> ((i + 5) % 8))
^ (b >> ((i + 6) % 8)) ^ (b >> ((i + 7) % 8)) ^ (c >> i)) & 1
y |= bit << i
sbox.append(y)
return sbox
SBOX = make_sbox()
assert SBOX[0x00] == 0x63 and SBOX[0x53] == 0xED # FIPS-197 の既知値
# --- 4 つのラウンド関数 (state は 16 byte のリスト、column-major) ---
def sub_bytes(state):
"""SubBytes: 各バイトを S-box で置換 (非線形層)"""
return [SBOX[b] for b in state]
def shift_rows(state):
"""ShiftRows: 第 r 行を左に r バイト巡回シフト (行間の拡散)"""
return [state[(i + 4 * (i % 4)) % 16] for i in range(16)]
def mix_columns(state):
"""MixColumns: 各列に式 (2) の MDS 行列を GF(2^8) 上で乗算"""
out = []
for c in range(4):
col = state[4 * c:4 * c + 4]
out += [
gf_mul(col[0], 2) ^ gf_mul(col[1], 3) ^ col[2] ^ col[3],
col[0] ^ gf_mul(col[1], 2) ^ gf_mul(col[2], 3) ^ col[3],
col[0] ^ col[1] ^ gf_mul(col[2], 2) ^ gf_mul(col[3], 3),
gf_mul(col[0], 3) ^ col[1] ^ col[2] ^ gf_mul(col[3], 2),
]
return out
def add_round_key(state, rk):
"""AddRoundKey: ラウンド鍵と XOR (鍵の混入)"""
return [s ^ k for s, k in zip(state, rk)]
# --- 鍵スケジュール (式 3): AES-128 は 10 ラウンド、11 個のラウンド鍵 ---
RCON = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]
def key_expansion(key):
words = [list(key[4 * i:4 * i + 4]) for i in range(4)]
for i in range(4, 44):
temp = list(words[i - 1])
if i % 4 == 0:
temp = temp[1:] + temp[:1] # RotWord
temp = [SBOX[b] for b in temp] # SubWord
temp[0] ^= RCON[i // 4 - 1] # Rcon
words.append([t ^ w for t, w in zip(temp, words[i - 4])])
return [sum(words[4 * r:4 * r + 4], []) for r in range(11)]
def aes128_encrypt_block(plaintext, key):
"""AES-128 で 1 ブロック (16 byte) を暗号化"""
round_keys = key_expansion(key)
state = add_round_key(list(plaintext), round_keys[0])
for r in range(1, 10):
state = sub_bytes(state)
state = shift_rows(state)
state = mix_columns(state)
state = add_round_key(state, round_keys[r])
# 最終ラウンドは MixColumns なし
state = sub_bytes(state)
state = shift_rows(state)
state = add_round_key(state, round_keys[10])
return bytes(state)
# --- FIPS-197 Appendix C.1 のテストベクトルで検証 ---
key = bytes(range(16)) # 000102...0e0f
pt = bytes.fromhex("00112233445566778899aabbccddeeff")
ct = aes128_encrypt_block(pt, key)
print(f"暗号文: {ct.hex()}")
assert ct.hex() == "69c4e0d86a7b0430d8cdb78070b4c55a" # FIPS-197 の期待値
print("FIPS-197 テストベクトルと一致: 実装は正しい")
実行結果:
暗号文: 69c4e0d86a7b0430d8cdb78070b4c55a
FIPS-197 テストベクトルと一致: 実装は正しい
読みどころは 3 点です。
- S-box が定義から導出できる:
make_sbox()は式 (1) のアフィン変換と式 (6) の逆元計算だけで、FIPS-197 掲載のテーブルと完全一致するテーブルを生成します。S-box は「魔法の定数」ではなく GF(2^8) の構造そのものです。 gf_invは繰り返し二乗法: \(a^{254}\) の計算は RSA のべき乗剰余や ECC のスカラー倍算 と同じ「指数の 2 進展開」で動きます。暗号全域を貫く共通アルゴリズムです。- この実装がなぜ本番不可か:
SBOX[b]のテーブル参照はアクセス位置が秘密情報に依存し、キャッシュタイミング攻撃 (Bernstein, 2005) の標的になります。AES-NI やビットスライス実装が使われるのは、まさにこの問題を回避するためです。
鍵長とセキュリティレベル比較
対称鍵の鍵長 \(k\) bit に対する総当たり攻撃は \(2^k\) 回の試行を要し、この \(k\) がセキュリティレベルの基準になります。公開鍵暗号は数学的攻撃(数体篩・Pollard rho)があるため、同レベルに達するにはずっと長い鍵が必要です(ECC 記事の鍵長比較も参照)。
| セキュリティレベル | 対称鍵 | RSA / DH | ECC | 位置づけ |
|---|---|---|---|---|
| 112 bit | 3DES (廃止) | 2048 bit | 224 bit | 現行最低ライン |
| 128 bit | AES-128 | 3072 bit | 256 bit | 現代の標準 |
| 192 bit | AES-192 | 7680 bit | 384 bit | 高セキュリティ |
| 256 bit | AES-256 | 15360 bit | 512 bit | 長期・政府機密 |
量子計算機の影響も対称鍵と公開鍵で非対称です。RSA / ECC は Shor のアルゴリズムで多項式時間で破られるのに対し、対称鍵への量子攻撃は Grover のアルゴリズムによる平方根加速のみで、実効セキュリティが半分になるにとどまります(AES-128 → 実効 64 bit、AES-256 → 実効 128 bit)。**「量子時代に備えるなら AES-256」**と言われるのはこのためで、対称鍵暗号は耐量子暗号 (PQC) 移行後もそのまま使われ続けます。
AES-192 が実務でほぼ見られないのは、多くのプロトコル (TLS など) が 128 / 256 の 2 択しか定義していないためです。また AES-256 の関連鍵攻撃 (2009) は理論的興味に留まり、実用上の脅威にはなっていません。
まとめ
- 対称鍵暗号はハイブリッド構成の本体側: DH / RSA / ECC が確立した共有鍵で、AES / ChaCha20 が大量データを高速に暗号化する。
- AES は SubBytes / ShiftRows / MixColumns / AddRoundKey の 4 変換を 10〜14 ラウンド繰り返す SPN 構造で、すべての演算が GF(2^8)(既約多項式 \(x^8 + x^4 + x^3 + x + 1\) )の上で定義される。S-box は逆元 \(a^{254}\) とアフィン変換から構成できる。
- モードの選択が実務の安全性を決める: ECB は使用禁止、CBC はパディングオラクル、CTR / GCM は nonce 再利用が即致命傷。現代の標準は AEAD の AES-GCM と ChaCha20-Poly1305。
- ChaCha20 は ARX 構造でテーブル参照がなく、AES-NI のない環境では AES より速くかつ定数時間。「AES-NI があれば AES-GCM、なければ ChaCha20-Poly1305」が使い分けの基本。
- Python では
cryptography.hazmat.primitives.ciphers.aeadのAESGCM/ChaCha20Poly1305を使う。鍵はgenerate_key()、nonce は 12 byte を毎回新規生成。低水準CipherAPI の自前組み立ては避ける。 - セキュリティレベルは AES-128 ≈ RSA-3072 ≈ ECC-256。量子攻撃 (Grover) に対しては鍵長効果が半減するため、長期用途は AES-256 を選ぶ。
関連記事
- 暗号技術ロードマップ:DLP・素因数分解・楕円曲線から PQC まで - 暗号領域全体の学習マップ。本記事はこのハブの対称鍵パート(段階 2)を深掘りしたもの。
- 楕円曲線暗号(ECC)の数学と Python 実装 - ハイブリッド暗号の鍵交換側。ECDH で確立した鍵が AES-GCM に渡される。
- RSA 暗号の理論・鍵生成・暗号化・復号を Python 実装 - 公開鍵暗号の代表。RSA が遅いからこそハイブリッド構成で対称鍵暗号が本体を担う。
- Diffie-Hellman 鍵交換の理論と実装 - セッション鍵(対称鍵)を安全に共有するプロトコル。TLS 1.3 の前段。
- 繰り返し二乗法による高速べき乗剰余計算の Python 実装 - GF(2^8) の逆元計算 \(a^{254}\) にもそのまま使われる基礎アルゴリズム。
- 楕円曲線 ElGamal 暗号の原理と Python による簡易実装 - 公開鍵側のもう 1 つの方式。ハイブリッド暗号の鍵カプセル化に対応。
- Diffie-Hellman 鍵交換プロトコル:理論と Python 実装 - 鍵配送問題を解く DH の入門記事。
- OAuth 2.0/OIDC 入門 - 対称鍵 (HS256) と公開鍵 (RS256/ES256) の署名が JWT で使い分けられる実例。
- ゼロトラストセキュリティの概要と導入 - AES-GCM / mTLS を組織のセキュリティ設計に組み込む文脈。
関連ツール
- ハッシュ生成ツール (DevToolBox) - HKDF / HMAC の前提となるハッシュ計算の確認に
- Base64 エンコード/デコード (DevToolBox) - 暗号文・鍵のエンコード確認に
- 進数変換ツール (CalcBox) - GF(2^8) のバイト値の 2 進 / 16 進表記の確認に
参考文献
- NIST (2001). “Advanced Encryption Standard (AES)”. FIPS PUB 197.
- Daemen, J., & Rijmen, V. (2002). The Design of Rijndael: AES — The Advanced Encryption Standard. Springer.
- Dworkin, M. (2007). “Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC”. NIST SP 800-38D.
- Nir, Y., & Langley, A. (2018). “ChaCha20 and Poly1305 for IETF Protocols”. RFC 8439.
- Bernstein, D. J. (2005). “Cache-timing attacks on AES”.
- Joux, A. (2006). “Authentication Failures in NIST version of GCM”. Comments on NIST SP 800-38D draft.
- Vaudenay, S. (2002). “Security Flaws Induced by CBC Padding — Applications to SSL, IPSEC, WTLS…”. EUROCRYPT 2002.