Presidio 한국어 개인정보 탐지: 주민번호·전화번호 마스킹
우리가 매일 쓰는 서비스 뒤에서는 수많은 데이터가 오고 갑니다. 그중 가장 철저하게 보호해야 하는 것이 개인정보입니다. 이름, 전화번호, 주민등록번호 같은 정보는 한 번 유출되면 일상에 큰 피해를 줄 수 있기 때문에, 개발자와 기업에게는 이를 안전하게 다루는 일이 가장 중요한 과제 중 하나입니다.
문제는 고객 게시판 글이나 상담 채팅 속에 주민등록번호나 전화번호가 무심코 섞여 들어온다는 점입니다. 이걸 사람이 일일이 찾아 지우기는 사실상 불가능합니다. 이때 쓸 수 있는 도구가 마이크로소프트가 오픈소스로 공개한 개인정보 탐지·익명화 프레임워크 Presidio입니다. 이 글에서는 Presidio 한국어 환경에서 주민등록번호와 전화번호를 탐지하고 마스킹하는 필터를 실제 코드로 만들어 보겠습니다.
Presidio란? 개인정보 탐지와 마스킹을 한 번에
Presidio는 텍스트 속 민감한 개인정보를 자동으로 찾아내고 가리는 도구입니다. 크게 두 부분으로 나뉩니다. 텍스트에서 개인정보의 위치와 종류를 찾아내는 분석기(Analyzer)와, 찾아낸 부분을 마스킹하거나 다른 값으로 바꾸는 익명화기(Anonymizer)입니다. 이미지 속 개인정보를 가리는 기능도 별도로 제공합니다.
Presidio는 정규식 패턴, 개체명 인식(NER) 모델, 주변 문맥 단어를 함께 활용해 개인정보를 판단합니다. 오픈소스라 라이선스 비용 없이 자체 서버에 설치할 수 있어, 데이터를 외부로 보내지 않고 처리하려는 기업에 특히 잘 맞습니다.
Presidio 한국어 지원, 그대로 쓸 수 있을까?
Presidio에 기본으로 들어 있는 인식기는 주로 영어권 데이터, 예를 들어 미국 사회보장번호나 영문 이름 등에 맞춰져 있습니다. 그래서 설치만 하고 한국어 문장을 넣으면 주민등록번호나 010 휴대전화 번호를 제대로 잡지 못합니다.
대신 Presidio는 언어와 인식기를 직접 추가할 수 있도록 설계되어 있습니다. spaCy의 한국어 모델을 연결하고, 주민등록번호와 전화번호용 인식기를 직접 등록하면 Presidio 한국어 개인정보 필터로 충분히 활용할 수 있습니다.
한국어 주민등록번호와 전화번호 탐지 원리
주민등록번호: 생년월일과 성별 코드로 걸러내기
주민등록번호는 앞 6자리 생년월일과 뒤 7자리로 이루어지고, 중간에 하이픈이 있거나 붙어서 입력됩니다. 뒷자리 첫 숫자는 성별과 출생 세기를 나타내는 코드입니다. 따라서 단순히 13자리 숫자를 찾는 대신, 월이 01~12, 일이 01~31 범위인지와 성별 코드가 1~8인지를 정규식으로 확인하면 주문번호 같은 임의의 숫자를 상당 부분 걸러낼 수 있습니다.
주의할 점이 있습니다. 예전에는 주민등록번호 마지막 자리를 공개된 계산식으로 검증할 수 있었지만, 2020년 10월부터 새로 부여되거나 변경된 번호는 성별 코드 뒤 6자리가 임의번호로 바뀌어 이 계산식이 맞지 않습니다. 검증 공식으로 걸러내도록 만들면 최근 발급된 번호를 놓칠 수 있으므로, 검증 실패를 이유로 탐지에서 빼지 않는 것이 안전합니다.
전화번호: 다양한 입력 형태를 모두 잡기
휴대전화 번호는 010-1234-5678처럼 표준 형식으로만 입력되지 않습니다. 010 1234 5678처럼 띄어 쓰거나 01012345678처럼 붙여 쓰는 경우가 많기 때문에, 하이픈과 공백이 있어도 없어도 모두 잡을 수 있도록 패턴을 유연하게 만드는 것이 핵심입니다.
한국어 조사 때문에 \b가 동작하지 않는 문제
정규식에서 숫자의 경계를 표시할 때 흔히 \b를 씁니다. 그런데 한국어는 “010-1234-5678이고”처럼 숫자 바로 뒤에 조사가 붙습니다. 파이썬 정규식은 한글도 단어 문자로 취급하기 때문에 숫자와 조사 사이를 경계로 보지 않고, 결국 \b를 쓴 패턴은 이 전화번호를 찾지 못합니다. 그래서 한국어 패턴에서는 \b 대신 앞뒤에 숫자가 없을 때만 일치하도록 (?<!\d)와 (?!\d)를 쓰는 것이 좋습니다.
Presidio 한국어 개인정보 필터 만들기
파이썬 환경에서 Presidio 분석기와 익명화기, spaCy 한국어 모델을 설치합니다.
pip install presidio-analyzer presidio-anonymizer
python -m spacy download ko_core_news_sm
설치가 끝나면 한국어 NLP 엔진을 연결하고, 주민등록번호와 휴대전화 번호 인식기를 등록한 뒤 마스킹까지 실행합니다.
from presidio_analyzer import AnalyzerEngine, PatternRecognizer, Pattern from presidio_analyzer.nlp_engine import NlpEngineProvider from presidio_anonymizer import AnonymizerEngine from presidio_anonymizer.entities import OperatorConfig1) 한국어 NLP 엔진 (spaCy 한국어 모델) nlp_engine = NlpEngineProvider(nlp_configuration={
"nlp_engine_name": "spacy",
"models": [{"lang_code": "ko", "model_name": "ko_core_news_sm"}],
}).create_engine() 2) 주민등록번호: 생년월일(월 0112, 일 0131) + 성별 코드(1~8) + 6자리 rrn = PatternRecognizer(
supported_entity="KR_RRN",
supported_language="ko",
patterns=[Pattern(
name="kr_rrn",
regex=r"(?<!\d)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])[-\s]?[1-8]\d{6}(?!\d)",
score=0.5)],
) 3) 휴대전화: 010-1234-5678 / 010 1234 5678 / 01012345678 phone = PatternRecognizer(
supported_entity="KR_PHONE",
supported_language="ko",
patterns=[Pattern(
name="kr_mobile",
regex=r"(?<!\d)01[016789][-\s]?\d{3,4}[-\s]?\d{4}(?!\d)",
score=0.5)],
) analyzer = AnalyzerEngine(nlp_engine=nlp_engine, supported_languages=["ko"])
analyzer.registry.add_recognizer(rrn)
analyzer.registry.add_recognizer(phone)
anonymizer = AnonymizerEngine()text = "제 연락처는 010-1234-5678이고 주민번호는 900101-1234567입니다."
results = analyzer.analyze(text=text, language="ko", entities=["KR_RRN", "KR_PHONE"])
masked = anonymizer.anonymize(
text=text,
analyzer_results=results,
operators={
"KR_PHONE": OperatorConfig("mask", {"masking_char": "", "chars_to_mask": 4, "from_end": True}),
"KR_RRN": OperatorConfig("mask", {"masking_char": "", "chars_to_mask": 7, "from_end": True}),
},
)
print(masked.text)
여러 형태의 문장을 넣어 실행한 결과는 다음과 같습니다.
입력: 제 연락처는 010-1234-5678이고 주민번호는 900101-1234567입니다. 출력: 제 연락처는 010-1234-****이고 주민번호는 900101-*******입니다.입력: 급해서 01098765432로 연락주세요
출력: 급해서 0109876****로 연락주세요 입력: 핸드폰 010 2222 3333 입니다
출력: 핸드폰 010 2222 **** 입니다입력: 주문번호 2024123112345678은 개인정보가 아닙니다.
출력: 주문번호 2024123112345678은 개인정보가 아닙니다.
조사가 붙은 번호, 붙여 쓴 번호, 띄어 쓴 번호 모두 마스킹되었고, 16자리 주문번호는 주민등록번호로 오인하지 않았습니다.
테스트하며 알게 된 Presidio 한국어 주의점
Presidio에는 “주민번호”, “연락처” 같은 문맥 단어가 근처에 있으면 탐지 점수를 올려 주는 기능이 있습니다. 그런데 한국어에서는 이 기능이 기대만큼 동작하지 않을 수 있습니다. spaCy 한국어 모델이 단어를 “주민번+호”, “연락+처”처럼 형태소 단위로 쪼개 표현하기 때문에, 등록한 문맥 단어와 일치하지 않는 경우가 생깁니다.
그래서 한국어 필터에서는 문맥 단어에 의존하기보다, 정규식 자체를 촘촘하게 설계하고 기본 점수와 판단 기준을 서비스에 맞게 정하는 편이 더 예측 가능합니다.
오탐지와 미탐지 사이에서 균형 잡기
세상에 100% 완벽한 개인정보 필터는 없습니다. 주민등록번호와 비슷한 숫자를 개인정보로 오인해 가리는 오탐지가 생길 수 있고, 형식을 살짝 바꿔 입력한 번호를 놓치는 미탐지도 생길 수 있습니다.
정규식을 너무 엄격하게 만들면 미탐지가 늘고, 너무 느슨하게 만들면 오탐지가 늘어나는 트레이드오프가 있습니다. 개인정보 유출이 치명적인 서비스라면 오탐지를 조금 감수하더라도 넓게 잡는 편이 낫고, 실제 사용자 입력 데이터로 꾸준히 테스트하며 패턴을 다듬어야 합니다.
마스킹 방식 선택 팁
익명화 단계에서는 정보를 통째로 삭제하는 것보다 일부만 가리는 마스킹이 쓰기 편한 경우가 많습니다. 전화번호 뒷자리 4개를 별표로 바꾸거나 주민등록번호 뒷자리 7자리를 모두 가리면, 원래 형태는 유지하면서도 사람을 식별할 수 없게 만들 수 있습니다.
Presidio는 마스킹 외에도 다른 값으로 바꾸는 치환, 완전히 지우는 삭제, 해시 처리 같은 익명화 방식을 기본으로 제공합니다. 상담 로그처럼 사람이 다시 읽어야 하는 데이터는 마스킹을, 통계 분석용 데이터는 해시나 치환을 쓰는 식으로 목적에 맞게 고르면 됩니다.
오픈소스로 자체 운영할 때의 장점
클라우드 기반 상용 개인정보 보호 솔루션은 처리량에 따라 매달 비용이 발생하고, 데이터를 외부 서비스로 보내야 하는 경우도 많습니다. Presidio는 오픈소스라 라이선스 비용이 없고, 사내 서버에 직접 설치하면 개인정보가 담긴 원본 데이터가 외부로 나가지 않습니다.
초기 구축과 패턴 튜닝에 개발 시간은 들지만, 처리량이 많은 서비스일수록 장기적으로 비용을 줄이고 데이터를 직접 통제할 수 있다는 장점이 커집니다.
자주 묻는 질문
Presidio는 영어가 아닌 한국어에서도 잘 작동하나요?
기본 인식기는 영어권에 맞춰져 있어 그대로는 한국어 개인정보를 잘 찾지 못합니다. 하지만 다국어 확장을 염두에 두고 설계되어 있어, 이 글처럼 한국어 모델과 한국어 전용 인식기를 추가하면 한국어 환경에서도 활용할 수 있습니다.
속도와 서버 부하는 괜찮나요?
정규식 기반 인식기는 가볍게 동작하지만, NER 모델까지 함께 쓰면 문장 길이와 처리량에 따라 부하가 커질 수 있습니다. 처리량이 많다면 Presidio를 별도 API 서버로 분리해 운영하고, 필요한 인식기만 켜서 쓰는 것이 좋습니다.
개발 지식이 없어도 사용할 수 있나요?
파이썬 기본 문법과 패키지 설치 정도는 알아야 합니다. 이 글의 예제 코드를 그대로 실행해 보고, 서비스에 필요한 패턴을 하나씩 추가해 가면서 익히는 방법을 추천합니다.
댓글 0
첫 댓글을 남겨보세요.