一覧SC-05-3 · 共通鍵暗号

AES-GCMによる認証付き暗号化

どの値が秘密になり、どの値が認証されるかを説明します。

説明

認証付き暗号は秘匿と改変検出を組み合わせます。AES-GCMはnonce、平文、追加認証データAADから暗号文とタグを作ります。AADは暗号化しませんが、復号時に同じ値を与えないと検証に失敗します。

注文本文を暗号化してから、本文・タグ・宛先を一つずつ変更します。最後に圧縮前後の長さを比較し、暗号化しても残る長さの情報を確認します。

どの値が秘密になり、どの値が認証されるかを説明します。

1本文と認証対象のヘッダーを用意する

公開試料鍵を使います。nonceは12バイト、タグはこのAPIでは16バイトです。同じ鍵でnonceを再利用してはいけません。

from sc05 import * import zlib key = bytes(16) nonce = bytes(12) plain = b"order=7&amount=100" aad = b"tenant=red" sealed = AESGCM(key).encrypt(nonce,plain,aad)本文を暗号化してAADも認証する print(len(plain),len(sealed),sealed.hex())

暗号文の長さは平文と同じで、末尾に16バイトのタグが加わります。AAD自体はsealedへ格納されません。

2暗号文とタグを分けて表示する

末尾16バイトをタグとして切り出します。

print(sealed[:-16]暗号文部分を取得する.hex()) print(sealed[-16:]認証タグを取得する.hex()) print(open_gcm(認証して復号するkey,nonce,sealed,aad))

同じnonceとAADなら元の本文へ戻ります。タグを確認する前の平文をアプリへ渡さない実装が必要です。

3暗号文を変更する

先頭1ビットを反転し、元のタグのまま提示します。

print(open_gcm(key,nonce,flip(sealed,0)暗号文の先頭を変更する,aad))

InvalidTagとなり、変更された本文は返りません。暗号文もタグの検証対象です。

4タグを変更する

暗号文を変えず、末尾のタグだけ1ビット変更します。

print(open_gcm(key,nonce,flip(sealed,len(sealed)-1)タグの末尾を変更する,aad))

タグだけの改変も拒否されます。本文が同じだから受け入れてよい、とはなりません。

5宛先テナントを変更する

暗号文を別のテナントへ持ち込んだ場合です。検証側が期待するAADをblueに変えます。

print(open_gcm(key,nonce,sealed,b"tenant=blue"検証側の宛先情報を変更する)) print(aad)

ヘッダーは平文のままですが、変更したヘッダーでは検証できません。AADは秘匿する情報の置き場ではありません。

6nonceを変更する

同じ暗号文に別のnonceを付けた場合も確認します。

print(open_gcm(key,flip(nonce,0)nonceの1ビットを変更する,sealed,aad))

nonceが異なると検証に失敗します。nonceは秘密でなくても、暗号文と正しく対応付けて保存する必要があります。

7圧縮による長さの違いを測る

繰返しの多い576バイトを圧縮します。以降の暗号化では新しいnonceを使います。

data = b"order=7&amount=100;" * 32 packed = zlib.compress(data)繰返しを圧縮する raw_box = AESGCM(key).encrypt(bytes.fromhex("000000000000000000000001"),data,aad) zip_box = AESGCM(key).encrypt(bytes.fromhex("000000000000000000000002"),packed,aad) print圧縮前後と暗号化後の長さを比較する(len(data),len(packed),len(raw_box),len(zip_box))

GCMは平文長に16バイトを加えた長さになるため、圧縮の差が暗号文長にも残ります。内容が秘密でも長さは別途考える必要があります。

8暗号化後の圧縮と比較する

同じ圧縮関数を暗号文へ適用します。

print(len(raw_box),len(zlib.compress(raw_box)暗号文を圧縮した長さを測る)) print(zlib.decompress(認証・復号後に展開するAESGCM(key).decrypt(bytes.fromhex("000000000000000000000002"),zip_box,aad)) == data)

この試料では暗号文の繰返しが減り、先に圧縮したときの効果は得られません。秘密と攻撃者が選べる文を一緒に圧縮すると、長さが推測の手掛かりになる場合もあります。

まとめ

暗号文とタグとAADを一組として検証します。

AADは認証されますが暗号化されません。

暗号化しても長さの情報が残り、圧縮の順序も影響します。

操作履歴と解説を振り返れます。

Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻

1
from sc05 import * import zlib key = bytes(16) nonce = bytes(12) plain = b"order=7&amount=100" aad = b"tenant=red" sealed = AESGCM(key).encrypt(nonce,plain,aad) print(len(plain),len(sealed),sealed.hex())
18 34 6cfabeab128b94b49245adcc1fc6c349c7a5d25b52606d0b75f796cbc2ee18662d4e
2
print(sealed[:-16].hex()) print(sealed[-16:].hex()) print(open_gcm(key,nonce,sealed,aad))
6cfabeab128b94b49245adcc1fc6c349c7a5
d25b52606d0b75f796cbc2ee18662d4e
b'order=7&amount=100'
3
print(open_gcm(key,nonce,flip(sealed,0),aad))
InvalidTag
4
print(open_gcm(key,nonce,flip(sealed,len(sealed)-1),aad))
InvalidTag
5
print(open_gcm(key,nonce,sealed,b"tenant=blue")) print(aad)
InvalidTag
b'tenant=red'
6
print(open_gcm(key,flip(nonce,0),sealed,aad))
InvalidTag
7
data = b"order=7&amount=100;" * 32 packed = zlib.compress(data) raw_box = AESGCM(key).encrypt(bytes.fromhex("000000000000000000000001"),data,aad) zip_box = AESGCM(key).encrypt(bytes.fromhex("000000000000000000000002"),packed,aad) print(len(data),len(packed),len(raw_box),len(zip_box))
608 34 624 50
8
print(len(raw_box),len(zlib.compress(raw_box))) print(zlib.decompress(AESGCM(key).decrypt(bytes.fromhex("000000000000000000000002"),zip_box,aad)) == data)
624 635
True

演習完了