一覧SD-13-2 · エラー・ログ・機密情報

シークレットと暗号鍵の管理

表示上の伏字、取得権限、暗号化の役割を具体的なデータで説明できます。

説明

画面やログで値を伏せることと、プロセスが値を読めなくなることは別です。またデータを暗号化しても、同じ主体が暗号文と鍵の両方を自由に読めれば保護範囲は限られます。

公開秘密値を子プロセスの環境へ渡し、マスク後にも参照可能か調べます。後半ではAES-GCMの暗号文と鍵の取得権限を分け、対象レコードとの対応と改変時の拒否を確認します。

表示上の伏字、取得権限、暗号化の役割を具体的なデータで説明できます。

1子プロセスへ渡す環境を限定する

親の全環境を複製せず、試料変数だけを渡します。

from sd13 import * env = {"LAB_TOKEN":"PUBLIC-DEMO-SECRET"} result = subprocess.run([sys.executable,"-c","import os; print(sorted(os.environ)); print(len(os.environ['LAB_TOKEN']))"],env=env子へ渡す環境を限定する,capture_output=True,text=True,check=True) print(result.stdout,end="")

子には試料変数とPython起動時の環境調整項目だけが渡ります。環境変数は子プロセスが読める値であり、秘密保管庫そのものではありません。

2表示を伏せても元の値は残ることを確認する

表示用の文字列だけを星印へ変えます。

display = "***" print(display,len(env["LAB_TOKEN"])マスク前の値が残ることを確認する)

表示は伏字ですが、元の値は18文字として残ります。マスクは露出する表示を減らす処理です。

3保存する本文を暗号化する

公開の固定鍵・nonceでレコード7の試料を暗号化します。運用時には同じ鍵でnonceを再利用しません。

lab = Secrets()固定試料の暗号文と鍵表を作る print(lab.db.execute("SELECT id,length(nonce),length(cipher) FROM records").fetchall())

nonceは12バイト、暗号文には16バイトの認証タグが加わっています。鍵を取得する表は本文と分けています。

4鍵の取得権限を変える

読取り担当と復号担当を比較します。

print(lab.fetch("reader")鍵の権限がない主体を試す) key = lab.fetch("decrypt-worker")復号担当として鍵を取得する print(len(key))

readerはNone、復号担当には32バイトの鍵です。この試料はアプリ側の取得条件を示し、実際の保管庫では別の認証・権限境界で強制します。

5鍵とレコードの対応情報を使って復号する

AADにrecord:7を指定します。AADは暗号化されませんが、認証対象です。

print(AESGCM(key).decrypt(lab.nonce,lab.cipher,b"record:7"対象レコードの識別情報を認証する))

公開本文が戻ります。鍵、nonce、暗号文だけでなく、対象に結び付けたAADも一致する必要があります。

6対象違いと暗号文の改変を拒否する

AADだけの変更と、暗号文1バイトの変更を比較します。

for cipher,aad in ((lab.cipher,b"record:8"),(bytes([lab.cipher[0]^1暗号文の1ビットを反転する])+lab.cipher[1:],b"record:7")): try:print(AESGCM(key).decrypt(lab.nonce,cipher,aad)) except Exception as e:print(type(e).__name__)

どちらもInvalidTagです。認証に失敗した本文を処理の後段へ流していません。

7取得済みの鍵は権限変更だけでは消えない

表から鍵を消した後も、取得済み変数にはコピーがあります。

lab.db.execute("DELETE FROM keys新しい鍵取得を止める") print(lab.fetch("decrypt-worker")) print(AESGCM(key)取得済みコピーで復号を試す.decrypt(lab.nonce,lab.cipher,b"record:7"))

新しい取得結果はNoneですが、保持済みの鍵では復号できます。失効設計では取得停止だけでなく、鍵の寿命と既存コピーの扱いも考えます。

まとめ

マスクと秘密値のアクセス制限を分けます。

暗号文と鍵の取得権限を分離します。

取得済みコピーまで含めて寿命を設計します。

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

Python 3.15 / 固定の公開試料

1
from sd13 import * env = {"LAB_TOKEN":"PUBLIC-DEMO-SECRET"} result = subprocess.run([sys.executable,"-c","import os; print(sorted(os.environ)); print(len(os.environ['LAB_TOKEN']))"],env=env,capture_output=True,text=True,check=True) print(result.stdout,end="")
['LAB_TOKEN', 'LC_CTYPE', '__CF_USER_TEXT_ENCODING']
18
2
display = "***" print(display,len(env["LAB_TOKEN"]))
*** 18
3
lab = Secrets() print(lab.db.execute("SELECT id,length(nonce),length(cipher) FROM records").fetchall())
[(7, 12, 37)]
4
print(lab.fetch("reader")) key = lab.fetch("decrypt-worker") print(len(key))
None
32
5
print(AESGCM(key).decrypt(lab.nonce,lab.cipher,b"record:7"))
b'public account sample'
6
for cipher,aad in ((lab.cipher,b"record:8"),(bytes([lab.cipher[0]^1])+lab.cipher[1:],b"record:7")): try:print(AESGCM(key).decrypt(lab.nonce,cipher,aad)) except Exception as e:print(type(e).__name__)
InvalidTag
InvalidTag
7
lab.db.execute("DELETE FROM keys") print(lab.fetch("decrypt-worker")) print(AESGCM(key).decrypt(lab.nonce,lab.cipher,b"record:7"))
None
b'public account sample'

演習完了