은행 현장의 언어 · 가리고도 따라갈 수 있게 · 이론
가리고 나서도 그 사건을 따라갈 수 있는가
한 줄 요약
로그와 반출 파일에서 개인정보를 지우는 일은 "안 보이게 만들기" 가 아니라, 조사에 필요한 연결성은 남기고 사람은 못 알아보게 만들기 다. 그 경계를 정하는 것이 마스킹 규칙과 결정적 가명값이고, 그것이 제대로 됐는지는 기계로 다시 재서 증명한다.
왜 이게 필요했나
장애 조사를 하다 보면 애플리케이션 로그를 통째로 받아 보게 된다. 그런데 이체 API 의 로그에는 자유 문장이 섞여 있고, 그 문장 안에 계좌번호와 카드번호, 전화번호와 이메일이 그대로 적혀 있는 경우가 흔하다. 개발자가 나쁜 마음으로 적은 것이 아니라, 장애를 빨리 찾으려고 "요청 전문을 통째로" 찍은 줄 하나가 몇 년째 남아 있는 것이다.
이 로그를 분석 담당이나 공급사에 넘기려면 개인정보를 걷어 내야 한다. [개인정보 보호법](https://www.law.go.kr/%EB%B2%95%EB%A0%B9/%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%EB%B3%B4%ED%98%B8%EB%B2%95)은 목적을 이룬 개인정보를 지체 없이 파기하도록 하고([제21조](https://www.law.go.kr/%EB%B2%95%EB%A0%B9/%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%20%EB%B3%B4%ED%98%B8%EB%B2%95/%EC%A0%9C21%EC%A1%B0)), 조사에 필요한 것은 사람의 신원이 아니라 "같은 계좌에서 몇 번 실패했는가" 같은 연결성이다. 그런데 계좌번호를 전부 별표로 덮어 버리면 그 연결성까지 사라진다. 같은 사람이 세 번 실패한 것인지, 세 사람이 한 번씩 실패한 것인지 구분할 수 없게 된다. 가려야 하는데, 이어 볼 수는 있어야 한다. 이 두 요구를 동시에 만족시키는 것이 이 주제의 전부다.
어떻게 동작하나
스크러빙은 세 단계로 나뉜다. 찾고, 가르고, 바꾼다.
찾기 는 정규식이다. [re 모듈](https://docs.python.org/3/library/re.html)로 계좌번호 모양, 16자리 숫자, 휴대전화 모양, 이메일 모양을 훑는다. 문제는 정규식이 "모양" 만 본다는 것이다. 은행 로그에는 16자리 숫자가 카드번호 말고도 널려 있다. 정산 일련번호, 전문 추적번호, 배치 작업 식별자가 전부 16자리다. 이것까지 별표로 덮으면 조사에 필요한 식별자가 사라진다.
가르기 가 여기서 들어온다. 카드번호에는 검사숫자가 붙어 있다. 오른쪽 끝에서 두 번째 자리부터 한 칸 걸러 두 배로 만들고, 두 배가 10 이상이면 자릿수를 더한다(또는 9를 뺀다). 모두 더한 값이 10의 배수면 통과다. 흔히 뤼른(Luhn) 알고리즘이라 부르고, 카드번호 체계 표준인 ISO/IEC 7812-1 이 이 검사숫자 방식을 규정한다(표준 원문은 유료라 이 글에서는 링크하지 않는다). 무작위 16자리 숫자가 우연히 이 검사를 통과할 확률은 10분의 1이므로, 검사숫자를 붙이면 오탐이 열 배 줄어든다. 완전히 없어지지는 않는다는 뜻이기도 하다.
바꾸기 는 두 가지를 함께 한다. 하나는 사람이 눈으로 확인할 수 있게 뒤 네 자리만 남기는 마스킹이고, 다른 하나는 기계가 이어 보게 하는 가명 토큰이다. 토큰은 [hmac 모듈](https://docs.python.org/3/library/hmac.html)로 만든다. 값 자체를 해시하면 안 된다. 계좌번호의 경우의 수는 적어서, 단순 SHA-256 은 사전 공격으로 되돌려진다. 비밀 키를 쓰는 HMAC 이어야 키를 모르는 쪽에서 되돌릴 수 없다. 키는 [secrets 모듈](https://docs.python.org/3/library/secrets.html)이나 openssl rand 로 만들고, 로그와 같은 곳에 두지 않는다.
토큰의 메시지에는 용도 를 함께 넣는다. acct|91231457820 과 card|91231457820 이 서로 다른 값이 되게 하는 것이다. 이렇게 해 두면 어느 한 쪽의 토큰 표가 새어 나가도 다른 쪽을 맞춰 볼 수 없다.
원본 "계좌 912-31-457820 카드 9012345678901234 일련번호 9900112233445566" │ │ │ │ │ └ 검사숫자 실패 → 카드 아님 → 그대로 둔다 │ └ 검사숫자 통과 → ************1234 └ ***-**-**7820 + tokens.acct = ["acct_1f3c…"] (같은 계좌면 언제나 같은 값)현장에서 만나는 모습
첫째, 두 번 돌리면 달라지는 스크러버 다. 파이프라인은 재시도를 한다. 이미 가려진 줄을 다시 스크러빙했는데 *--**7820 에서 별표를 또 세거나, 토큰을 다시 계산해 다른 값이 나오면 대사가 깨진다. 이미 처리된 표시를 남기고, 규칙이 자기 출력에 다시 걸리지 않도록 만든다.
둘째, 가림과 삭제를 헷갈리는 것 이다. 뒤 네 자리를 남기는 마스킹은 되돌릴 수 없지만, 이미 계좌번호 목록을 가진 사람에게는 후보를 좁혀 주는 단서가 된다. 소수의 계좌만 다루는 파일에서는 뒤 네 자리만으로 한 사람이 특정되기도 한다. 그래서 반출 파일에서는 마스킹한 값 자체를 조사 키로 쓰지 않고 토큰을 쓴다.
셋째, 키 관리가 없는 가명화 다. 키를 로그와 같은 디렉터리에 두거나, 코드에 박아 두거나, 아무도 키가 바뀐 시점을 모르면 가명값의 의미가 매번 달라진다. 보고서에 키 식별자(키의 해시 앞자리)를 적어 두면 "이 토큰들은 어느 키로 만든 것인가" 를 나중에 말할 수 있다.
실무에서 진짜 중요한 것
- 정규식은 후보만 준다. 검사숫자나 형식 규칙으로 한 번 더 걸러야 조사에 필요한 식별자를 죽이지 않는다.
- 같은 값은 같은 토큰, 다른 용도는 다른 토큰. 조사 가능성과 분리 가능성을 동시에 얻는 유일한 방법이다.
- 멱등성은 기능이다. 두 번 돌려 바이트가 같은지 시험으로 못 박아 둔다.
- 검사 결과에 원본을 적지 마라. 누출 보고서에 유출된 값을 그대로 쓰면 보고서가 또 하나의 유출본이 된다.
- 정책을 파일로 둔다. 무엇을 내보내지 않는지, 무엇을 몇 자리 남기는지, 얼마나 보관하는지를 사람이 아니라 파일이 기억하게 한다.
다음 실습에서 할 것
합성 이체 로그 640줄을 직접 만들고, 후보를 찾고, 검사숫자로 카드와 일련번호를 가른 뒤, 마스킹과 가명 토큰을 붙인 반출본을 만든다. 그러고 나서 원본 계좌번호를 한 번도 쓰지 않고 사고 고객의 사건을 토큰만으로 추적하고, 두 번 돌려 같은지와 남은 누출이 0인지를 확인해 정책과 보고서로 마무리한다. 채점기는 여러분의 키로 토큰을 다시 계산하고, 여러분의 프로그램을 자기가 만든 번호로 직접 실행해 본다.