外に出せる一枚をつくる
目標
個人情報が混ざった顧客データで調査を終え、外に出してもよい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 이메일이 든 파일 → 막아야 한다
전화번호가 든 파일 → 막아야 한다
집계만 든 파일 → 통과시켜야 한다
ステップ
- 原本を作成してください(上の準備ブロック)。
slim.csv: 調査に必要な列だけを残してください。行数はそのままです。pseudo.sh: 値を1つ受け取り、仮名値を1行出力してください。キーはPII_KEY環境変数で受け取ります。masked.csv: 電話番号を010-****-1234の形に、メールアドレスも隠してください。report.csv: 理由別の件数です。合計が原本の行数と同じである必要があります。scan.sh: ファイルを1つ受け取り、個人情報があれば0以外のコードで終了させてください。access.log: 照会するたびに、時刻・対象・目的を1行ずつ書いてください。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として、何を持ち出し何を持ち出さないか、マスキング方式をなぜそのように選んだかを書いてください。
マスキング方式は、元に戻せるかで分かれます。それぞれの方式をなぜその場所に使ったのかを書いてください。この文書自体にも、個人情報が入ってはいけません。