TT Lab
はじめる
学ぶ 学習パス コース

顧客データを扱う

外に出せる一枚をつくる

TT Labで続きを見る

目標

個人情報が混ざった顧客データで調査を終え、外に出してもよい1枚を作ります。4つの原則(最小収集・仮名化・集計での持ち出し・記録)を、それぞれファイルとして残します。

環境

/root/piiの下で作業します。python3、openssl、jqが入っています。原本は自分で作ります。以下は準備であって、課題ではありません。

mkdir -p /root/pii && cd /root/pii
python3 - <<'PY'
import csv, random
random.seed(7)
names = ['김민준','이서연','박지호','최수아','정하윤','강도윤','조서준','윤지우']
codes = ['TIMEOUT','DECLINED','INVALID_CARD','NETWORK','OK']
rows = []
for i in range(1, 201):
    n = random.choice(names)
    rows.append({
        'order_id': 'ORD-%04d' % i,
        'created_at': '2026-09-%02dT%02d:%02d:00Z' % (
            random.randint(1, 7), random.randint(0, 23), random.randint(0, 59)),
        'name': n,
        'email': 'user%03d@example.com' % i,
        'phone': '010-%04d-%04d' % (random.randint(1000, 9999), random.randint(1000, 9999)),
        'address': '서울시 어딘가 %d길 %d' % (random.randint(1, 90), random.randint(1, 300)),
        'card_last4': '%04d' % random.randint(1000, 9999),
        'amount': random.randint(1000, 90000),
        'status': random.choice(['FAILED', 'FAILED', 'PAID']),
        'error_code': random.choice(codes),
    })
with open('orders.csv', 'w', newline='', encoding='utf-8') as f:
    w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
    w.writeheader(); w.writerows(rows)
print(len(rows), '행')
PY

調査の目的は1つです。決済の失敗がどんな理由で起きるのかを突き止めることです。名前と住所は、その目的に必要ありません。

作るもの

slim.csv     조사에 필요한 열만 남긴 사본
pseudo.sh    같은 사람을 묶어야 할 때 쓰는 가명값 생성기
masked.csv   육안 확인용 부분 마스킹본
report.csv   밖으로 나가는 유일한 파일 — 사유별 집계
scan.sh      반출 전 검사기
access.log   무엇을 왜 봤는지
handoff.md   무엇을 내보내고 무엇을 안 내보내는지, 그 근거

採点方法

ステップ3とステップ6では、採点ツールが作成したスクリプトを直接実行します。

pseudo.sh  같은 값(대소문자·공백만 다름) → 같은 결과여야 한다
           키가 다르면 → 다른 결과여야 한다
           출력 길이 → 32자 이상이어야 한다
scan.sh    이메일이 든 파일 → 막아야 한다
           전화번호가 든 파일 → 막아야 한다
           집계만 든 파일 → 통과시켜야 한다

ステップ

  1. 原本を作成してください(上の準備ブロック)。
  2. slim.csv: 調査に必要な列だけを残してください。行数はそのままです。
  3. pseudo.sh: 値を1つ受け取り、仮名値を1行出力してください。キーはPII_KEY環境変数で受け取ります。
  4. masked.csv: 電話番号を010-****-1234の形に、メールアドレスも隠してください。
  5. report.csv: 理由別の件数です。合計が原本の行数と同じである必要があります。
  6. scan.sh: ファイルを1つ受け取り、個人情報があれば0以外のコードで終了させてください。
  7. access.log: 照会するたびに、時刻・対象・目的を1行ずつ書いてください。
  8. handoff.md: 何を持ち出し、何を持ち出さないか、マスキング方式をなぜそのように選んだかを書いてください。

参考

ステップ3でよく引っかかる場所が3つあります。正規化なしでハッシュすると、大文字小文字が違うだけで同じ人がまとまりません。キーなしでハッシュすると、電話番号やメールアドレスは、候補を作って総当たりできます。そして、8文字に切って使うと衝突が起きます。採点ツールが3つを別々に確認するので、どれに引っかかったのかがすぐわかります。

ステップ7のaccess.logにも、個人情報を書かないでください。監査ログも流出経路です。

個人情報が混ざった原本

原本を作成してください(上の準備ブロック)。

指示文の準備ブロックをそのまま実行します。調査に必要な列と個人情報の列が一緒に入っているので、選び分ける練習になります。

必要な列だけを取得する

slim.csvとして、調査に必要な列だけを残してください。行数はそのままです。

SELECT *ではなく、調査に使う列だけを選びます。決済失敗の理由を見るのに、名前と住所は必要ありません。行は減らさず、列だけを減らしてください。減らしすぎて使えなくしてはいけません。

同じ人をまとめる必要があるとき

pseudo.shとして、値を1つ受け取り、仮名値を1行出力するスクリプトを作成してください。キーはPII_KEY環境変数で受け取ります。

openssl dgst -sha256 -hmac "$PII_KEY" -rを使います。ハッシュする前に小文字に変えて空白を取り除く(正規化)と、同じ人が同じ値にまとまります。出力は切らないでください。

目視確認用の部分マスキング

masked.csvとして、電話番号を010-****-1234の形にし、メールアドレスも隠してください。

電話番号は、下4桁だけを残します(010-****-1234)。元に戻すことはできませんが推測は可能なので、人が目で照合する必要があるときだけ使います。

外に出るのは集計だけ

report.csvとして、理由別の件数を書いてください。合計が原本の行数と同じである必要があります。

原本200行の代わりに、「理由別の件数」の表1枚です。合計が原本の行数と同じであってこそ、抜けがないと言えます。

持ち出し前の検査ツール

scan.shとして、ファイルを1つ受け取り、個人情報があれば0以外のコードで終了するスクリプトを作成してください。

ファイルを1つ受け取り、個人情報があれば0以外のコードで終了します。すべてを止めるのも失敗です。集計だけが入ったファイルは通す必要があります。誤検知が出れば、誰も使いません。

何をなぜ見たかを残す

access.logとして、照会するたびに、時刻・対象・目的を1行ずつ書いてください。

あとで「誰がこれを見たのか」という質問が来たときに、自分を守る唯一の手段です。目的が抜けると、守りになりません。そして、このファイルにも個人情報を書かないでください。

何を持ち出し、何を持ち出さないか

handoff.mdとして、何を持ち出し何を持ち出さないか、マスキング方式をなぜそのように選んだかを書いてください。

マスキング方式は、元に戻せるかで分かれます。それぞれの方式をなぜその場所に使ったのかを書いてください。この文書自体にも、個人情報が入ってはいけません。