로컬 약한 비밀번호 목록이 증명하는 것, 전 세계 유출 조회가 아닌 이유

검사 페이지에 「약한 비밀번호 목록에 없음」이 뜨면, 많은 사람이 「이 비밀번호는 어떤 유출에도 나온 적 없다」로 읽습니다. 두 문장은 거리가 멉니다. 페이지와 함께 내려받는 고빈도 목록은 「이미 닳아빠진 비밀번호처럼 보이는가」만 답합니다. Have I Been Pwned 같은 서비스는 다른 질문을 하고, 조회 방식도 다릅니다. 결론에 쓸 수 있는 문장을 좁히고, 그 자리에서 확인할 수 있는 단계를 적습니다.

「목록에 없음」은 「유출된 적 없음」이 아닙니다

약한 비밀번호 검사는 보통 두 가지 결과만 줍니다. 목록에 적중했거나, 목록에 없거나. 적중은 읽기 쉽습니다. 그 문자열이 이미 공개된 고빈도 목록에 있으니, 계속 쓰면 공격자가 먼저 시도하는 묶음에 스스로를 넣는 셈입니다. 문제는 「없음」입니다. 목록이 「유출」에서 왔다고 해서, 안 걸리면 「깨끗하다」고 한 겹 더 읽는 경우가 많습니다.

목록은 자기가 실은 항목만 책임집니다. 아직 공개되지 않은 내부 유출, 공개 집합에 정리되지 않은 infostealer 로그, 작은 사이트에서 혼자 털린 비밀번호는, 브라우저가 방금 받은 수백·수천 줄 텍스트에 자동으로 나타나지 않습니다. 「없음」의 완전한 읽기는 이렇습니다. 지금 이 로컬 목록, 지금 이 대조 규칙에서는 동일 문자열이나 허용된 변형을 찾지 못했다. 「전 세계」를 약속한 문장이 아닙니다.

이 문장을 분명히 쓰는 이유는, 다음 행동이 달라지기 때문입니다. 적중이면 바꾸는 것이 맞습니다. 없어도 길이, 재사용 여부, 비밀번호 관리자가 뽑은 무작위인지까지는 따로 봐야 합니다. 목록은 필터이지, 무죄 증명서가 아닙니다.

목록이 막는 추측은 어느 층인가

공격자는 a부터 zzzz…까지 끝까지 가는 일이 드뭅니다. 더 흔한 두 경로는, 이미 유출된 아이디와 비밀번호를 다른 사이트에 넣어 보는 것(자격 증명 재사용)과, 공개된 약한 비밀번호 순위에서 앞에서부터 시도하는 것입니다. 123456, password, qwerty가 이런 목록의 맨 앞에 반복해서 나오는 이유는 「짧아 보여서」가 아니라, 선택 빈도가 높아 몇 번만 시도해도 맞힐 수 있기 때문입니다.

고빈도 목록이 겨냥하는 층이 바로 여기입니다. 이미 남용된 비밀번호를 표시해, 아직 바꿀 수 있을 때 버리게 합니다. 「이 문자열의 탐색 공간이 얼마나 넓은가」에는 답하지 않고, 「아직 공개되지 않은 어떤 덤프에 이 항목이 있는가」에도 답하지 않습니다. 뒤의 두 질문은 각각 엔트로피 추정과, 규모가 훨씬 큰 유출 말뭉치의 몫이며, 「흔한 약한 비밀번호처럼 보이는가」와 한 결론으로 묶이면 안 됩니다.

그래서 길지 않은 목록도 쓸모가 있습니다. 온라인 로그인은 거의 항상 시도 횟수를 제한합니다. 잠기기 전에 시도할 수 있는 횟수가 정해져 있으니, 공격자는 순위가 높은 비밀번호부터 넣습니다. NIST SP 800-63B-4의 블록리스트 절은, 목록의 목적이 제한이 소진되기 전에 맞춰질 가능성이 큰 비밀번호를 막는 것이라고 적습니다. 그 목표를 넘긴 뒤의 증분은 작고, 온라인 공격 자체가 이미 스로틀되기 때문입니다. 로컬 고빈도 목록은 이 목표와 맞습니다. 「우주에 한 번이라도 유출된 모든 비밀번호」를 담도록 설계된 것이 아닙니다.

NIST는 문자열 전체를 대조하라고 하지, 복잡도 규칙을 더하라고 하지 않습니다

미국 국립표준기술연구소는 SP 800-63B-4에서 「이미 흔하고, 예측 가능하거나, 유출된 비밀번호와 대조하라」를 검증 측이 해야 할 단계로 적습니다. 비밀번호를 만들거나 바꿀 때, 후보의 문자열 전체를 블록리스트와 비교해야 하며, 가운데에 어떤 단어가 들어 있는지, 기호가 빠졌는지를 보는 방식이 아닙니다. 적중하면 거부하고, 왜 거부됐는지 사용자에게 알려야 합니다.

같은 절은 지난 십여 년 동안 가장 쉽게 오해된 비밀번호 정책도 다시 씁니다. 검증 측은 「대문자, 숫자, 기호를 동시에 넣어야 한다」는 조합 규칙을 더 이상 강제하지 않습니다. 이 인증자가 유출됐다는 증거가 없다면 주기적 변경도 요구하지 않습니다. 단일 요소 비밀번호의 길이 하한은 15자, 다중 요소 중 하나일 때는 8자입니다. 이 문장들은 비밀번호를 받는 웹사이트를 향한 것이며, 브라우저의 자가 검사 페이지를 향한 것이 아닙니다. 그래도 결과를 읽는 사람에게는 쓸모가 있습니다. 복잡도 체크박스 자체는 안전을 증명하지 못합니다. 먼저 막아야 하는 것은, 공개 목록에 이미 나온 문자열 전체입니다.

NIST는 거부한 뒤에 바꿀 안내를 주라고도 합니다. passwordPassword1로 바꾸는 식의, 한눈에 보이는 고쳐 쓰기를 피하기 위해서입니다. 로컬 목록이 대소문자, 끝자리 두세 숫자, 흔한 Leet(@를 a로, 0을 o로)를 추가로 다루면, 바로 이런 고쳐 쓰기를 겨냥한 것입니다. 그래도 휴리스틱이지 암호학적 증명은 아닙니다. P@ssw0rd2026!는 잡힐 수 있고, 규칙에 안 적힌 변형은 통과합니다.

지금 쓰는 실제 비밀번호로 공개 실험을 하지 마세요. 목록을 대조해야 하면, 이미 버리기로 한 예전 비밀번호나, 버려도 되는 테스트 문자열을 쓰세요. 확인하는 것은 규칙과 트래픽이지, 사용 중인 비밀번호를 다시 노출하는 일이 아닙니다.

로컬 짧은 목록, 해시 접두사 조회, 전체 오프라인 말뭉치

「유출된 적 있는지 봤다」에는 적어도 세 가지 방법이 있습니다. 섞어 읽으면 「목록에 없음」이 「유출된 적 없음」이 됩니다. 차이는 구호가 아니라, 데이터가 어느 길을 갔는지, 서버가 무엇을 볼 수 있는지입니다.

첫째는 로컬 짧은 목록입니다. 페이지가 공개 텍스트를 현재 탭에 내려받고, 스크립트가 메모리에서 집합을 만든 뒤, 입력한 문자열 전체(그리고 소수의 변형)를 찾습니다. 나가는 것은 목록 파일 자체이지, 검사할 비밀번호가 아닙니다. 덮는 범위는 그 텍스트의 줄 수와 같습니다. MakePwd가 페이지와 함께 제공하는 leaked-top10k.txt를 예로 들면, 파일 이름에는 top10k가 있지만 열어 줄을 세면—지금은 고빈도 항목이 800여 줄이며, 만 줄도 아니고 전 세계도 아닙니다. 목록을 불러오지 못하면 구현은 내장 최소 집합(예를 들어 password, 123456)으로 물러나고, 범위는 더 좁아지며, 검사는 이 기기에 남습니다.

둘째는 Have I Been Pwned의 Pwned Passwords 범위 조회입니다. 공식 문서는 말뭉치를 「수억 개, 유출에 나온 적 있는 비밀번호」로 적습니다. 클라이언트는 먼저 이 기기에서 SHA-1(또는 NTLM)을 계산하고, 해시의 앞 5개 16진 문자만 https://api.pwnedpasswords.com/range/{prefix}로 보낸 뒤, 돌아온 접미사와 자기가 계산한 전체 해시를 이 기기에서 대조합니다. 평문과 전체 해시는 설계상 서버로 가지 않습니다. 이것이 k-익명성입니다. 서버는 약 백만 개의 접두사 통 중 어디에 떨어졌는지만 압니다. 그래도 외부 인터페이스를 조회하는 것이며, 조회하는 대상이 비밀번호 원문이 아닐 뿐입니다. 2025년 11월 HIBP는 한 번에 약 13억 개의 중복 없는 비밀번호를 넣었고(이 서비스에 새로웠던 것은 약 6.25억 개), 2026년 6월에는 stealer 로그에서 약 1.24억 개의 중복 없는 비밀번호를 더했습니다. 이 숫자는 말뭉치가 커지고 있다는 뜻이지, 「한 번 조회하면 영원히 깨끗하다」는 뜻이 아닙니다.

셋째는 말뭉치 전체를 자기 기기나 내부망에 내려받아 완전히 오프라인으로 대조하는 것입니다. HIBP도 이런 내려받기를 제공합니다. 범위는 둘째에 가깝지만, api.pwnedpasswords.com으로 접두사를 보내지 않습니다. 대가는 갱신, 저장, 그리고 해시 인덱스를 직접 유지하는 일입니다.

방법 이 기기를 떠나는 데이터 「없음」을 이렇게만 쓸 수 있음
로컬 고빈도 목록 목록 파일; 검사할 비밀번호는 나가면 안 됨 이 목록(및 구현된 변형)에 없음
HIBP 범위 조회 SHA-1 / NTLM의 5자 접두사 현재 Pwned Passwords 말뭉치에 없음
전체 오프라인 말뭉치 조회 요청 없음 내려받은 그 버전 말뭉치에 없음

세 가지 모두 「어떤 유출에도 한 번도 나온 적 없다」로 다시 쓸 수 없습니다. 공개되지 않은 덤프, 아직 수록되지 않은 로그, 어떤 제3자에게도 넘긴 적 없는 비밀번호는, 이 집합들의 의무 범위 밖입니다. 로컬 목록은 그중 가장 좁은 방법이며, 장점은 검사할 비밀번호가 설계상 탭을 떠나지 않아도 된다는 점입니다.

적중과 미적중, 각각 어떤 문장만 쓸 수 있는가

적중이면 결론을 단단히 쓸 수 있습니다. 이 문자열은 이미 공개 고빈도 목록에 있거나, 목록이 알아보는 고쳐 쓰기만 한 것입니다. 계속 쓰면 자격 증명 재사용과 온라인 추측이 먼저 만나는 집합에 남는 것입니다. 올바른 동작은 바꾸고, 다른 사이트에서 같은 문자열을 재사용하지 않는 것입니다.

없으면 결론에 범위를 붙여야 합니다. 「이 목록의 흔한 약한 비밀번호처럼 보이지는 않는다」는 받쳐 줍니다. 「털린 적 없다」「모든 사이트에서 재사용해도 된다」「엔트로피가 충분하다」는 받쳐 주지 않습니다. 20자의 무작위 비밀번호도 목록에 없고, 어떤 비공개 유출에만 나온 비밀번호도 목록에 없습니다. 결과는 같아 보여도 의미가 다릅니다.

「길어 보이는데도 약함으로 표시해야 하는」 경우도 있습니다. 목록 적중은 엔트로피보다 앞섭니다. password는 알파벳 8자라 문자 집합으로 추정하면 0이 아닌 비트 수가 나오지만, 누구나 먼저 시도합니다. 로컬 검사가 적중 뒤에 등급을 약함으로 바꾸는 근거는 목록이지, 그 비트 수가 틀린 것이 아닙니다. 반대로, 목록에 없고 엔트로피가 높아도 「흔한 약한 비밀번호처럼 보이지 않고, 무차별 대입 비용이 높아 보인다」까지만이지 「이미 안전하다」로 바뀌지 않습니다. 재사용, 피싱, 상대 사이트의 평문 저장은 이번 대조의 범위 밖입니다.

동료에게 건넬 가장 짧은 문장: 적중 = 즉시 바꿀 것; 없음 = 이 목록만 제외한 것이며, 다음은 재사용 여부와 무작위로 뽑았는지를 본다.

그 자리에서 확인하기: 목록 파일을 보고, Network를 본다

「로컬 대조, 업로드 없음」이라는 문구는 스스로를 증명하지 못합니다. 그 자리에서 볼 수 있는 것은 두 가지입니다. 브라우저가 어떤 목록을 내려받았는지, 그리고 입력한 문자열이 업무 데이터로 나갔는지입니다.

먼저 목록을 봅니다. 개발자 도구 Network를 열고, 파일 이름에 leaked 또는 top이 들어간 요청을 걸러 응답을 텍스트로 저장합니다. 편집기에서 빈 줄이 아닌 줄을 세고, 앞 몇 줄이 123456, password 같은 공개 고빈도 비밀번호인지 뽑아 봅니다. 여러분이 센 줄 수가 「목록에 없음」이라는 문장의 분모입니다. 파일 이름의 10k만 보고 만 줄이라고 쓰지 마세요.

이어서 검사할 비밀번호를 봅니다. 실제 업무에 나오지 않을 카나리아를 준비합니다. 예를 들어 canary-audit-2026-x7k를 입력창에 넣습니다. Preserve log를 켜고, 필터에 이 문자열을 붙여넣습니다. XHR / Fetch의 요청 줄, 쿼리 문자열, 요청 본문에 나오면 안 됩니다. 방문 통계의 query와 body에도 나오면 안 됩니다. 정적 자원, 글꼴, 그 목록 텍스트는 나와도 됩니다. 목록이 나가는 것은 예상된 동작이고, 검사할 비밀번호가 나가는 것이 실패입니다.

  1. Network를 열고 Preserve log를 켠 뒤, 검사를 한 번 끝낸 다음 목록 파일 응답을 찾아 줄 수를 셉니다.
  2. 카나리아 비밀번호로 한 번 더 검사하고, 필터에서 이 문자열을 검색합니다. 어떤 요청이든 적중하면 평문이 탭을 떠난 것입니다.
  3. 적중이 없으면 방문 통계 요청을 열어, 제목과 경로는 나와도 되고 입력창 전문은 나오면 안 됨을 확인합니다.

이 단계가 증명하는 범위는 좁습니다. 이번 조작에서 검사할 비밀번호가 관찰된 HTTP 필드로 현재 탭을 떠나지 않았고, 목록 범위는 여러분이 센 줄 수와 같다는 것. 확장 프로그램이 입력창을 읽지 않았다는 증명이 아니며, 다음 주 갱신 뒤에도 같은 동작이라는 증명도 아닙니다. 브라우저나 버전을 바꾼 뒤에는 카나리아 검색을 다시 할 가치가 있습니다.

엔트로피 추정과 목록은 다른 질문입니다

검사 페이지는 등급, 비트 수, 「목록 적중 여부」를 함께 주는 경우가 많습니다. 이를 하나의 점수로 보면, 「비트 수는 낮은데 목록에 적중」하거나 「목록에는 없는데 아주 짧음」일 때 혼란이 납니다. 묻는 질문이 다르고, 서로를 상쇄해서도 안 됩니다.

엔트로피 추정은 이렇게 묻습니다. 공격자가 문자 집합으로 무차별 대입한다면 평균 얼마나 걸릴까. 흔한 쓰기는 길이에 log2(문자 집합 크기)를 곱한 뒤, 반복 문자와 연속 반복에 감점을 주는 방식입니다. 임계값을 ≥ 80 bits면 매우 강함, ≥ 60이면 강함, ≥ 40이면 보통, 나머지는 약함으로 적었다면, 그것은 이 추정 함수의 구간이지 실험실에서 잰 실제 해독 시간이 아닙니다. 오프라인과 온라인의 시간선은 추측 속도도 다릅니다. 예를 들어 오프라인은 초당 1010회, 온라인은 초당 103회로 두고, 평균 경우(탐색 공간의 절반)를 취합니다. 속도를 바꾸면 시간은 바뀌고, 비밀번호 자체는 바뀌지 않습니다.

목록은 이렇게 묻습니다. 이 문자열이 이미 공개적으로 남용됐는가. 비트 수를 쓰지 않습니다. 목록에 적중한 비밀번호는 대문자와 기호가 섞여 있어도 약함으로 표시해야 합니다. 목록에 없는 6자 비밀번호는 엔트로피가 낮고, 길이 자체가 약함으로 누릅니다. 이는 목록과 무관합니다. 8자 미만일 때 「보안이 낮다」는 안내를 주는 근거는 길이이지, 유출 라이브러리가 아닙니다.

그래서 한 번을 끝까지 읽으려면 적어도 세 곳을 봅니다. 목록에 적중했는지, 길이가 너무 짧은지, 엔트로피가 어느 구간인지. 세 곳을 다 읽어야 「이 문자열이 지금 어떻게 보이는가」를 말할 수 있습니다. 그래도 비밀번호 관리자를 대신하지 않고, 「사이트마다 무작위 한 줄」을 대신하지도 않습니다.

열자마자 쓰는 검사 페이지로 범위를 익히기

경계를 분명히 적어 둔 페이지로 연습하고 싶다면, MakePwd의 비밀번호 강도 검사부터 시작하면 됩니다. 열자마자 쓸 수 있고, 가입이 필요 없으며, 비밀번호 보관함도 없습니다. 제품 설명대로 검사할 비밀번호는 올리지 않고 analytics에도 쓰지 않습니다. 대조는 현재 탭이 내려받은 공개 고빈도 목록에서 일어나며, Have I Been Pwned가 아니고 전 세계 자격 증명 조회도 아닙니다. 적중하면 강제로 약함입니다. 128자를 넘으면 정확 일치만 하고, 끝자리 숫자와 Leet는 돌리지 않습니다.

연습할 때는 버려도 되는 테스트 문자열을 쓰고, 지금 쓰는 비밀번호는 넣지 마세요. Network를 동시에 엽니다. 목록 파일과 방문 통계는 보일 수 있고, 입력창 전문은 보이면 안 됩니다. 새 비밀번호를 따로 뽑아야 하면 비밀번호 생성기도 열자마자 쓸 수 있습니다. 무작위 모드 길이는 6–128자, 기본값은 16자이며, 8자 미만이면 보안이 낮다고 안내합니다. 생성과 검사는 모두 브라우저에서 끝나고, 평문은 기본적으로 이 기기를 떠나지 않습니다.

이 연습의 목적은 어떤 사이트가 「가장 많이 조회한다」고 증명하는 것이 아닙니다. 동료에게 세 문장을 설명할 수 있게 하는 것입니다. 목록이 몇 줄인지 세어 봤다. 카나리아로 Network를 검색했다. 「없음」은 「이 목록에 없다」로만 쓴다. 「로컬 약한 비밀번호 검사」를 주장하는 어떤 페이지로 바꿔도 단계는 그대로 옮길 수 있습니다.

다음에 결과를 읽을 때 기억할 세 가지

첫째, 적중이면 바꾸고, 없으면 현재 이 목록만 제외한 것이며 「유출된 적 없음」으로 읽지 마세요. 둘째, 로컬 짧은 목록, HIBP의 5자 해시 접두사, 전체 오프라인 말뭉치는 서로 다른 데이터 경로이며, 「없음」의 의미는 경로를 따라갑니다. 셋째, 엔트로피와 목록을 서로 상쇄하지 마세요. 짧은 비밀번호는 목록에 없을 수 있고, 길고 흔한 비밀번호는 목록에 있을 수 있습니다.

「평문이 업무 데이터로 이 탭을 떠났는가」를 더 묻고 싶다면, 브라우저에서 암호화할 때, 평문이 나가지 않았는지 직접 확인하는 법을 읽으세요. 이 글은 「약한 비밀번호 목록이 무엇을 덮는가」만 결론에 쓸 수 있는 범위로 줄입니다.