暗号とその失敗

レッスン 1-2

暗号が守るのは中身を読まれないことだけで、それも正しく使ったときに限ります。

目安 60 分 トラック 1 基盤の 2 本目

読み終えたらnonce の使い回しが、読まれないことと書き換えられないことの両方を壊す理由を説明できる

この頁の流れ

  1. 1なぜ通るのか
  2. 2脆いコードと直したコード
  3. 3ログでの見え方と検知
  4. 4直し方と確かめ方
  5. 5関わる事例
  6. 6確認クイズ
  7. 7説明課題
  8. 8一次資料
  9. 9次へ

AES-GCM は、正しく使えば読まれないことと書き換えられないことを同時に守ります。ただし nonce を 1 度でも使い回すと、その 2 つがまとめて崩れます。nonce の作り方、鍵の置き場、暗号文と行の結び付け、記録での見え方、直ったことを試験で確かめる方法までを扱います。

出典を確かめた日 2026年10月27日

なぜ通るのか

会員のカード番号を、データベースに入れる前に AES-GCM で暗号化しているとします。鍵は設定ファイルに書き、nonce (1 回だけ使う使い捨ての値) は「秘密ではないから」と、固定の 12 バイトにしました。試験は通り、復号もできます。

この作りは、暗号を使っていても守れていません。暗号の失敗の多くは、暗号の計算そのものではなく、使い方の約束を破った所で起きるからです。

共通鍵暗号、公開鍵暗号、ハッシュ、署名という部品そのものの仕組みは、コンピュータサイエンスの回「公開鍵暗号方式」で扱います。このレッスンは、その部品を仕事のコードで使ったときに、どこで壊れ、どう気づき、どう止めるかに絞ります。

AES-GCM が守る 2 つ

AES-GCM は、暗号化と改ざん検知を 1 回でこなす方式です。NIST の SP 800-38D が定めています。入れる物と出てくる物は次の通りです。

部品役目
鍵秘密にする。これが漏れたら、すべてが終わる
nonce秘密ではないが、同じ鍵で 2 度使ってはいけない
付け足すデータ暗号化しないが、書き換えを検知する対象に入れる
タグ暗号文に付く改ざん検知の印。合わなければ復号を止める

復号の側は、タグが合わないと中身を返さずに止まります。だから AES-GCM を正しく使えば、読まれないことと、書き換えられないことの 2 つが守られます。

nonce の使い回しで何が崩れるか

SP 800-38D は、同じ鍵で同じ nonce が 2 度使われる確率を 2 の -32 乗以下に抑えることを求め、この約束を守ることが GCM の安全にとって決定的だと書いています。破ると、次の 2 つがまとめて崩れます。

崩れる物何が起きるか
読まれないこと同じ nonce の 2 つの暗号文を重ねると、2 つの平文の差が出る。片方を知る相手には、もう片方が読める
書き換えられないこと改ざん検知に使う内側の鍵 (ハッシュ用の部分鍵) を、攻め手が暗号文から割り出せる見込みが高い。割り出されると、好きな暗号文に正しいタグを付けられる

1 つ目は、鍵の流れの使い回しとして CS の回でも触れている話です。このレッスンで強調したいのは 2 つ目です。SP 800-38D の付録 A は、nonce が繰り返されると改ざん検知の保証は実質的に失われ、攻め手は復号の結果をほぼ思い通りにできると書いています。「暗号化されているから、少なくとも読まれはしない」どころか、「書き換えても気づけない」状態になります。

nonce を毎回ランダムに作る場合、SP 800-38D は、1 つの鍵で暗号化してよい回数の合計を 2 の 32 乗までとしています。それより多く使う物は、鍵を入れ替えるか、数え上げで重ならない nonce を作る方式にします。

鍵の置き場で崩れる

計算が正しくても、鍵を暗号文の隣に置けば、暗号は 1 枚の扉を足しただけになります。データベースと鍵の両方に届く攻め手には、暗号化は何も守りません。

NIST の SP 800-57 Part 1 は、1 つの鍵は原則として 1 つの目的にだけ使うこと、鍵ごとに使ってよい期間 (暗号期間) を決めることを求めています。期間を決めておけば、鍵が漏れたときに読まれる範囲を限れます。

脆いコードと直したコード

ラボの中で比べます。Python の作業用の仮想環境に、cryptography という部品を 1 つだけ入れて動かします (AES-GCM は Python に付いている部品だけでは使えないため)。外とは通信しません。

nonce を固定するか、毎回作るか

脆いコード nonce を固定で使い回す

Python

NONCE = b"\x00" * 12

def seal(key, data):
    return AESGCM(key).encrypt(
        NONCE, data, None)
直したコード 毎回新しく作り、暗号文に添える

Python

def seal(key, data):
    nonce = os.urandom(12)
    ct = AESGCM(key).encrypt(
        nonce, data, None)
    return nonce + ct

def unseal(key, blob):
    return AESGCM(key).decrypt(
        blob[:12], blob[12:], None)

脆い方は、同じ平文を 2 回暗号化すると、まったく同じ暗号文が出ます。それだけで「この 2 人のカード番号は同じだ」と分かってしまいます。直した方は、nonce を OS の乱数から毎回作り、暗号文の先頭に添えて保存します。nonce は秘密ではないので、添えて構いません。cryptography の説明書も、同じ鍵で nonce を使い回さないよう強く書いています。

鍵をコードに置くか、外から渡して行と結び付けるか

脆いコード 鍵を書き込み、行と結び付けない

Python

KEY = bytes.fromhex("8f1c...")

def save_card(db, user_id, card):
    db[user_id] = seal(KEY, card)
直したコード 鍵は外から渡し、行の ID を結び付ける

Python

def save_card(db, key, user_id, card):
    nonce = os.urandom(12)
    aad = user_id.encode()
    ct = AESGCM(key).encrypt(
        nonce, card, aad)
    db[user_id] = nonce + ct

def load_card(db, key, user_id):
    blob = db[user_id]
    return AESGCM(key).decrypt(
        blob[:12], blob[12:],
        user_id.encode())

直した方では、鍵はコードの外 (鍵の管理の仕組みや、秘密の値を入れる置き場) から渡します。さらに、利用者の ID を「付け足すデータ」として結び付けています。こうすると、データベースを書き換えられる攻め手が、自分の行の暗号文を他人の行へ写しても、復号の時点で止まります。暗号文は読めなくても、「入れ替える」ことで悪さができる場面を塞ぐためです。

ログでの見え方と検知

直したコードで、書き換えた暗号文や、別の行から写した暗号文を開くと、cryptography は次の例外を出します。

タグが合わないとき

記録

cryptography.exceptions.InvalidTag

この例外は、改ざん検知が働いた証拠です。ふだんの利用ではほぼ出ないので、急に増えたら、データの書き換えか、鍵の取り違えを疑います。守る側は次を見張ります。

見る物なぜ効くか
コードの中の固定の nonce や鍵書いた時点で見つけられる。秘密の値の検査に鍵の形を入れる
同じ鍵での nonce の重なりnonce は秘密ではないので、記録に残して重なりを数えられる
復号の失敗 (InvalidTag) の数ふだんは出ない。増えたら書き換えか鍵の誤りを疑う
鍵の作られた日と使った回数暗号期間と、ランダムな nonce の上限 (2 の 32 乗回) を超える前に入れ替える

鍵そのものを記録に出さないことも同じくらい大事です。例外の中身に鍵や平文を入れて記録すると、記録の置き場が新しい漏れ口になります。

直し方と、直ったことの確かめ方

自分の PC の中だけで行います。鍵はラボのために作った使い捨ての物だけを使い、仕事の鍵は持ち込みません。

  1. 作業用のフォルダに仮想環境を作り、cryptography を入れる
  2. ラボ用の鍵を AESGCM.generate_key で作り、脆い seal で同じ平文を 2 回暗号化して、暗号文がまったく同じになることを確かめる
  3. 直した seal に置き換え、同じ平文でも nonce と暗号文が毎回変わることを確かめる
  4. 2 人分のカード番号を save_card で保存し、2 つの行の暗号文を入れ替えてから load_card を呼び、InvalidTag で止まることを確かめる
  5. 下の試験を足し、固定の nonce や、付け足すデータを外したコードに戻すと試験が落ちることも確かめる
  6. 終わったら、仮想環境と鍵を作業用のフォルダごと消す
直ったことを固める試験

Python

def test_nonce_changes(key):
    a = seal(key, b"same")
    b = seal(key, b"same")
    assert a[:12] != b[:12]
    assert a != b

def test_swapped_rows_fail(key):
    db = {}
    save_card(db, key, "u1", b"4111")
    save_card(db, key, "u2", b"5500")
    a, b = db["u1"], db["u2"]
    db["u1"], db["u2"] = b, a
    with pytest.raises(InvalidTag):
        load_card(db, key, "u1")

試験は「同じ平文から同じ暗号文が出ない」ことと「入れ替えた暗号文は開かない」ことを固定します。あとで誰かが nonce を固定に戻したり、付け足すデータを外したりしても、ここで止まります。

あわせて、鍵の置き場を見直します。データベースと同じ場所や同じ権限で鍵に届くなら、暗号化の意味は薄れます。鍵は別の仕組みに預け、使える人と機械を絞り、暗号期間を決めて入れ替えます。

関わる事例

マリオットは2018年11月30日、傘下のスターウッドの予約データベースへの不正なアクセスを公表しました。発表によると、決済カードの番号は AES-128 で暗号化されていました。それでも、復号に要る 2 つの要素の両方が持ち出された可能性を否定できない、と書いています。暗号化しても、鍵に当たる物が同じ攻め手の手の届く所にあれば、守りにはなりません。

コインチェックでは、2018年1月26日の午前0時2分から午前8時26分までの間に、預かっていた NEM のうち5億2630万10XEM が外部へ送金されました。同社の報告によると、攻め手は NEM の秘密鍵を盗み、それを使って送金したと想定されています。同社は、NEM をインターネットにつながったままのホットウォレットで管理していたため、不正な送金を防げなかったと書いています。鍵を、つながったままの機械に置いていたことが、被害の大きさを決めました。

確認クイズ

1 問目

AES-GCM で、nonce を毎回同じ固定の値にしていました。起きることとして正しいのはどれですか。

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。

2 問目

カード番号を AES-GCM で暗号化して保存しています。データベースを書き換えられる攻め手が、自分の行の暗号文を他人の行に写しました。何で止めますか。

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。

説明課題

次のことを、自分の言葉で 3 行にまとめてください。書けたら、このレッスンは修了です。

  • nonce の使い回しが、読まれないことと書き換えられないことの両方を壊す理由
模範解答を見る

AES-GCM は、同じ鍵と同じ nonce からは同じ鍵の流れを作るので、使い回すと 2 つの暗号文を重ねて平文の差が出て、片方を知る相手にもう片方が読める。さらに、改ざん検知に使う内側の鍵が同じ nonce の暗号文から割り出せるようになり、攻め手は好きな暗号文に正しいタグを付けられる。だから 1 度の使い回しで、読まれないことと書き換えられないことがまとめて崩れる。

一次資料

どれも 2026年10月27日に開いて、題と中身を確かめました。

次へ

次のレッスンは「ハッシュ、改ざん検知の符号、署名」です。暗号化しない物の書き換えを、ハッシュと鍵でどう見つけるのか、ただのハッシュではなぜ足りないのかを扱います。

次のレッスン 1-3 ハッシュ、改ざん検知の符号、署名

トラック 1 基盤の中で

前のレッスン 1-1 脅威モデリング

学んだことを次につなげる

ゆめさくセキュリティは、攻撃の仕組みを知り、自分の PC の中で壊して直し、守る側の判断を身につけるための学びの場です。

ラボはすべて自分の PC の中 (127.0.0.1) で閉じます。自分が管理していない相手に試すことは扱いません。