Scrub the personal data out of the logs and still follow the incident
한국어 원문으로 표시합니다.
목표
이체 API 로그에서 개인정보 후보를 찾아 검사숫자로 오탐을 걸러 내고, 마스킹과 결정적 가명 토큰으로 반출본을 만든다. 그러고도 사고 고객의 사건을 토큰만으로 추적하고, 두 번 돌려도 같은 결과가 나오며 남은 누출이 없음을 증명한다.
왜 중요한가
장애 조사에 쓰는 로그에는 자유 문장이 섞여 있고 그 안에 계좌번호와 카드번호가 그대로 적혀 있다. 그대로 넘길 수는 없지만, 전부 별표로 덮으면 "같은 계좌에서 세 번 실패했다" 는 사실까지 사라진다. 정규식은 모양만 보므로 16자리 정산 일련번호까지 카드로 오해한다. 검사숫자를 한 번 더 걸면 오탐이 열 배 줄고, 조사에 필요한 식별자를 죽이지 않는다. 가린 값에 HMAC 결정적 토큰을 달면 같은 계좌는 언제나 같은 토큰이 되어 이어 볼 수 있고, 키가 없으면 되돌릴 수 없다. 스크러빙 파이프라인은 재시도되므로 두 번 돌려도 결과가 같아야 한다.
단계
- /root/mask/gen_logs.py 를 만들어 실행해 /root/mask/app.log(JSON Lines 500줄 이상)와 /root/mask/customers.csv(고객 30명 이상)를 만든다. 계좌 30개 이상, 검사숫자를 통과하는 카드 40개 이상, 통과하지 못하는 16자리 수 20개 이상을 섞고, 가장 많이 나오는 계좌 하나가 2위보다 3줄 이상 많게 한다.
- /root/mask/find_candidates.py 로 네 종류의 후보를 모두 찾아 /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 으로 잰 값이다.
참고
- 로그 한 줄의 키는 ts·req_id·event·actor·channel·amount·msg 일곱 개이고, 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자리인데 검사숫자가 틀린 정산 일련번호도 섞으세요 — 다음 단계의 오탐이 바로 그것입니다.
정규식으로 후보를 하나도 빠짐없이 찾기
/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 후보입니다. 오른쪽 끝에서 두 번째 자리부터 한 칸 걸러 두 배로 만들고, 두 배가 10 이상이면 9를 뺍니다. 모두 더해 10으로 나누어떨어지면 통과입니다. 채점기는 자기가 만든 번호로 luhn.py 를 직접 실행하므로 종료 코드 계약을 지켜야 합니다.
규칙대로 가리고 일련번호는 남기기
/root/mask/scrub.py 를 만들어 /root/mask/app.scrubbed.log 를 만드세요. 카드가 아닌 16자리 수는 그대로 둡니다.
읽을 파일은 /root/mask/app.log 이고 결과는 /root/mask/app.scrubbed.log 입니다. 지시문 참고의 네 가지 치환 모양을 정확히 지키세요. 줄 수는 원본과 같아야 하고, amount 같은 숫자 필드는 건드리지 않습니다. 채점기는 원본에서 기대값을 다시 계산해 한 글자까지 대조합니다.
같은 계좌에 같은 가명 토큰 달기
/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 만 보고 셉니다. 결과 파일에 계좌번호를 다시 적으면 지금까지 한 일이 무의미해집니다.
두 번 돌려도 같은지, 남은 누출이 없는지
/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 은 정책 파일 전체를 잰 값입니다.