ログから個人情報を消しても事案を追いかける
目標
送金APIのログから個人情報の候補を探し、チェックディジットで誤検知をふるい落とし、マスキングと決定的な仮名トークンで持ち出し版を作ります。そのうえで、事故に遭った顧客の事案をトークンだけで追跡し、2回回しても同じ結果になり、残った漏えいがないことを証明します。
なぜ重要なのか
障害調査に使うログには自由記述の文が混ざっていて、その中に口座番号とカード番号がそのまま書かれています。そのまま渡すことはできませんが、すべてアスタリスクで覆うと、「同じ口座で3回失敗した」という事実まで消えます。 正規表現は形しか見ないので、16桁の清算連番までカードと誤解します。チェックディジットをもう一度かけると、誤検知が10分の1に減り、調査に必要な識別子を殺しません。 隠した値にHMACの決定的トークンを付けると、同じ口座は常に同じトークンになってつなげて見られ、鍵がなければ元に戻せません。スクラビングのパイプラインはリトライされるので、2回回しても結果が同じでなければなりません。
ステップ
- /root/mask/gen_logs.pyを作成して実行し、/root/mask/app.log(JSON Lines 500行以上)と/root/mask/customers.csv(顧客30人以上)を作ってください。口座30個以上、チェックディジットを通過するカード40個以上、通過しない16桁の数20個以上を混ぜ、最も多く出る口座1つが2位より3行以上多くなるようにします。
- /root/mask/find_candidates.pyで4種類の候補をすべて探し、/root/mask/candidates.jsonに書いてください(total・by_kind・items)。
- /root/mask/luhn.pyを作成し(有効なら終了コード0)、カード候補を/root/mask/candidates_luhn.jsonのcard_verifiedとnot_cardに分けてください。
- /root/mask/scrub.pyで/root/mask/app.scrubbed.logを作ってください。口座・カード・電話・メールを、下の参考のルールどおりに隠し、カードではない16桁の数はそのまま残します。
- /root/mask/keys/scrub.keyを作成し、scrub.pyが各行にtokens(acct・card)を付けるようにしたうえで、/root/mask/app.scrubbed.logを作り直してください。
- app.logで最も多く出た口座のトークンを求め、持ち出し版だけを見て、/root/mask/trace.jsonにtop_account_token・event_count・req_ids・first_ts・last_tsを書いてください。
- /root/mask/leakscan.pyを作成し、scrub.pyを持ち出し版に再び回しても結果が同じかどうかを確認したうえで、検査結果を/root/mask/leak_report.jsonに残してください。
- /root/mask/policy.jsonと/root/mask/scrub_report.jsonを作ってください。レポートの数字は実際のファイルで数え、policy_sha256は、ポリシーファイルをsha256で測った値です。
参考
- ログ1行のキーはts・req_id・event・actor・channel・amount・msgの7つで、tsは2026-03-04T10:22:31+09:00の形です。
- 探す形: 口座
9\d{2}-\d{2}-\d{6}、カード候補(?<!\d)\d{16}(?!\d)、電話01[016789]-\d{3,4}-\d{4}、メール[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,} - 隠すルール: 口座
912-31-457820→***-**-**7820、カード9012345678901234→************1234、電話010-3388-7412→010-****-7412、メールnara.kim@example.com→n***@example.com。チェックディジットに失敗する16桁の数には手を触れません。 - トークン: 鍵ファイルの内容の前後の空白を取り除いたバイトをHMAC-SHA256の鍵として使い、メッセージは
용도|숫자만남긴값、16進数の先頭16桁にacct_・card_を付けます(プレースホルダーは、順に用途と数字だけを残した値です)。すべての行にtokens.acctとtokens.cardを置きます(なければ空の配列)。 - 鍵の作り方:
openssl rand -hex 32 > /root/mask/keys/scrub.key - 採点ツールは、作成したプログラムを自分で作った番号とサンプルファイルで直接実行し、トークンを作成した鍵で再計算して突き合わせます。
- よくある間違い: 16桁の連番まで隠して調査の手がかりをなくす、マスキングしたあとに再度スクラビングしてアスタリスクを重ねる、漏えいレポートに流出した値をそのまま書く。
- ここに出てくる人・口座・カード・連絡先はすべて合成です。実在の機関の番号帯を使わないでください。
合成した送金ログのスナップショットを作る
/root/mask/gen_logs.pyを作成して実行し、/root/mask/app.logと/root/mask/customers.csvを作ってください。
現場ではデータが先にありますが、ここでは自分たちで作ります。口座・カード・連絡先は作り話の値でなければならず、カード番号はチェックディジットが合っている必要があります。16桁なのにチェックディジットが間違っている清算連番も混ぜてください。次のステップの誤検知が、まさにそれです。
正規表現で候補を1つ残らず探す
/root/mask/find_candidates.pyで、口座・カード・電話・メールの候補を探し、/root/mask/candidates.jsonに書いてください。
読むファイルは、ステップ1で作った/root/mask/app.logです。このステップは、再現率がすべてです。誤検知は次のステップで取り除くので、16桁の数字は、まずすべてカード候補に入れてください。lineは1から数え、valueは、その行に実際にある文字列でなければなりません。
チェックディジットでカードと連番を分ける
/root/mask/luhn.pyを作成し、カード候補を/root/mask/candidates_luhn.jsonのcard_verifiedとnot_cardに分けてください。
分ける対象は、ステップ2で作った/root/mask/candidates.jsonのcard候補です。右端から2桁目から1桁おきに2倍にし、2倍が10以上なら9を引きます。すべて足して10で割り切れれば合格です。採点ツールは自分で作った番号でluhn.pyを直接実行するので、終了コードの契約を守る必要があります。
ルールどおりに隠し、連番は残す
/root/mask/scrub.pyを作成して、/root/mask/app.scrubbed.logを作ってください。カードではない16桁の数は、そのまま残します。
読むファイルは/root/mask/app.logで、結果は/root/mask/app.scrubbed.logです。ラボ案内の参考にある4つの置換の形を、正確に守ってください。行数は元と同じでなければならず、amountのような数値フィールドには手を触れません。採点ツールは、元のデータから期待値を再計算し、1文字まで突き合わせます。
同じ口座に同じ仮名トークンを付ける
/root/mask/keys/scrub.keyを作成し、scrub.pyが各行にtokensを付けるようにしたうえで、/root/mask/app.scrubbed.logを作り直してください。
ステップ4で作った/root/mask/scrub.pyを書き直して使います。値だけをハッシュすると、取りうる値が少ないので、辞書攻撃で元に戻されます。秘密鍵を使うHMACでなければなりません。用途をメッセージに入れないと、口座とカードが同じ数字のときに同じトークンになります。
トークンだけで事故に遭った顧客の事案を追う
最も多く出た口座のトークンで持ち出し版を調査して、/root/mask/trace.jsonを作ってください。
口座を選ぶところまでは、元のデータを見ます。そのあとは、app.scrubbed.logのtokens.acctだけを見て数えます。結果ファイルに口座番号を再び書くと、ここまでしたことが無意味になります。
2回回しても同じか、残った漏えいはないか
/root/mask/leakscan.pyを作成し、scrub.pyを持ち出し版に再び回して結果が同じかどうかを確認したうえで、/root/mask/leak_report.jsonを残してください。
すでに隠した値がルールに再びかからないか、トークンがすでにあれば再計算しないかを見てください。検査器は、16桁だからといってすべて捕まえてはいけません。連番まで漏えいとして数えると、誰も使わなくなります。
持ち出しポリシーとスクラビングレポートを残す
/root/mask/policy.jsonと/root/mask/scrub_report.jsonを作ってください。レポートの数字は、実際のファイルで数えます。
ポリシーのmaskの値は残す文字数で、key_idは/root/mask/keys/scrub.keyのsha256の先頭12桁です。レポートのmaskedは重複を含む件数、tokensは異なるトークンの数で、policy_sha256はポリシーファイル全体を測った値です。