P@ssw0rd! 와 correct horse battery staple. 앞의 것은 대문자·숫자·특수문자 규칙을 다 지켰는데도 유출 사전 맨 앞줄에 있는 비밀번호고, 뒤의 것은 소문자뿐인데 무작위로 고른 단어라면 훨씬 버티는 비밀번호예요. 결론부터 말하면 무작위로 만든 비밀번호의 강도는 엔트로피 = 길이 × log2(문자 종류 수) 비트로 계산되고, 길이는 곱셈으로, 문자 종류는 로그로 들어가기 때문에 복잡하게 만드는 것보다 길게 만드는 게 훨씬 효과적이에요. 특수문자를 섞은 8자는 약 52비트, 소문자만 쓴 16자는 약 75비트입니다.
이 글은 엔트로피를 직접 계산하는 법, 그 비트 수가 무차별 대입 시간으로 얼마인지, 패스프레이즈가 왜 강한지, NIST SP 800-63B가 무엇을 권하는지, 그리고 개발자가 생성기를 만들 때 Math.random() 대신 crypto.getRandomValues()를 써야 하는 이유와 모듈로 편향까지 정리했어요. 숫자는 모두 파이썬으로 직접 계산했습니다.
핵심 요약
무작위 비밀번호의 엔트로피는 길이 × log2(문자 종류 수)예요. 94종 인쇄 가능 ASCII는 글자당 약 6.55비트, 소문자는 약 4.70비트입니다.
초당 100억 번 추측하는 오프라인 공격을 가정하면 94종 8자는 평균 약 3.5일, 소문자 16자는 약 6.9만 년이 걸려요.
사람이 "규칙대로" 만든 비밀번호는 이 계산이 성립하지 않아요. 무작위 생성기나 무작위로 고른 단어 5~6개 패스프레이즈를 쓰세요.
생성기 코드는 반드시 CSPRNG(crypto.getRandomValues)로, 모듈로 편향 없이 만들어야 계산한 엔트로피가 진짜가 됩니다.
비밀번호 엔트로피 계산 공식: 길이 × log2(문자 종류)
엔트로피는 "공격자가 모든 경우를 다 시도해야 할 때 경우의 수가 2의 몇 제곱인가"를 나타내는 값이에요. 문자 종류가 N개인 집합에서 각 글자를 독립적으로, 균등하게 무작위로 L개 뽑으면 가능한 비밀번호 수는 N^L개고, 이를 비트로 바꾸면 이렇게 됩니다.
엔트로피(비트) = log2(N^L) = L × log2(N)
문자 집합별 글자당 비트는 아래와 같아요.
| 문자 집합 | 종류 수 N | 글자당 비트 log2(N) |
|---|---|---|
| 숫자 0-9 | 10 | 3.32 |
| 영문 소문자 | 26 | 4.70 |
| 소문자 + 숫자 | 36 | 5.17 |
| 영문 대소문자 | 52 | 5.70 |
| 대소문자 + 숫자 | 62 | 5.95 |
| 인쇄 가능 ASCII(공백 제외) | 94 | 6.55 |
| 다이스웨어 단어 목록(단어 1개) | 7,776 | 12.92 |
여기서 바로 보이는 게 있어요. 소문자(26종)에서 94종으로 문자 집합을 3.6배 넓혀도 글자당 비트는 4.70에서 6.55로 1.85비트만 늘어요. 반면 길이를 한 글자 늘리면 소문자 기준으로 4.70비트가 그대로 더해집니다. 1비트가 늘 때마다 경우의 수는 두 배가 되니까, 이 차이는 시간이 지나면 엄청 커져요.
길이가 복잡성보다 강한 이유, 숫자로 비교
같은 길이에서 문자 집합을 바꾼 경우와, 같은 문자 집합에서 길이를 늘린 경우를 나란히 놓으면 차이가 분명해요.
정리하면 이래요.
- 94종 8자 = 52.4비트, 소문자 12자 = 56.4비트, 소문자 16자 = 75.2비트
- 94종 8자와 같은 강도를 소문자로 내려면 52.4 ÷ 4.70 ≈ 11.2, 즉 소문자 12자면 충분해요.
- 반대로 소문자 16자와 같은 강도를 94종으로 내려면 75.2 ÷ 6.55 ≈ 11.5, 즉 94종으로도 12자는 필요합니다.
게다가 현실에서는 "복잡성 규칙"이 엔트로피를 생각만큼 올려 주지 못해요. 대문자를 넣으라면 사람들은 대부분 첫 글자를, 숫자를 넣으라면 끝에 1이나 연도를, 특수문자를 넣으라면 끝에 !를 붙여요. a를 @로, o를 0으로 바꾸는 치환도 공격 도구가 기본으로 시도하는 규칙이에요. 그래서 위 공식은 사람이 고른 비밀번호에는 적용되지 않고, 생성기로 무작위로 뽑았을 때만 성립합니다.
무차별 대입 시간 계산: 비트가 시간으로 바뀌는 과정
엔트로피가 H비트면 경우의 수는 2^H개고, 공격자가 운이 평균적이면 그 절반쯤에서 찾아요. 그래서 평균 시간은 이렇게 계산합니다.
평균 해독 시간 ≈ (경우의 수 ÷ 2) ÷ 초당 추측 횟수
초당 추측 횟수는 상황마다 수천만 배 차이가 나서 하나로 못 박을 수 없어요. 아래 표는 두 가지를 가정해서 계산한 예시입니다.
- 빠른 공격(초당 10^10회): 유출된 DB의 해시가 빠른 해시(솔트 없는 MD5·SHA-1 등)일 때 GPU 여러 장으로 공격하는 상황을 가정한 값
- 느린 공격(초당 10^4회): bcrypt·scrypt·Argon2처럼 일부러 느리게 만든 해시를 쓴 경우를 가정한 값
| 비밀번호 | 엔트로피 | 빠른 공격(10^10/초) | 느린 공격(10^4/초) |
|---|---|---|---|
| 숫자 6자리 | 19.9비트 | 즉시 | 50초 |
| 소문자 8자 | 37.6비트 | 10초 | 121일 |
| 대소문자+숫자 8자 | 47.6비트 | 3시간 | 346년 |
| 94종 8자 | 52.4비트 | 3.5일 | 약 9,660년 |
| 소문자 12자 | 56.4비트 | 55일 | 약 15만 년 |
| 대소문자+숫자 12자 | 71.5비트 | 약 5,110년 | 약 51억 년 |
| 소문자 16자 | 75.2비트 | 약 6.9만 년 | 약 691억 년 |
| 94종 16자 | 104.9비트 | 약 59조 년 | 비교 무의미 |
두 가지가 눈에 띄어요. 첫째, 같은 비밀번호도 서비스가 해시를 어떻게 저장했느냐에 따라 버티는 시간이 100만 배 달라져요. 이건 사용자가 고를 수 없는 부분이라, 사용자 입장에선 빠른 공격 기준으로 버틸 길이를 잡는 게 안전합니다. 둘째, 빠른 공격 기준으로도 12자 이상 무작위면 현실적인 시간 밖으로 나가요.
참고로 로그인 화면에 직접 시도하는 온라인 공격은 보통 횟수 제한·잠금이 걸려서 초당 몇 번 수준이에요. 그래서 온라인 공격에서 진짜 위험한 건 무차별 대입이 아니라 다른 사이트에서 유출된 비밀번호를 그대로 넣어 보는 크리덴셜 스터핑입니다. 비밀번호 재사용이 가장 큰 구멍인 이유예요.
패스프레이즈: 단어 몇 개면 충분한가
패스프레이즈는 무작위로 고른 단어 여러 개를 이어 붙인 비밀번호예요. 대표적인 방식이 주사위 다섯 번으로 단어 하나를 고르는 다이스웨어인데, 목록이 6^5 = 7,776단어라서 단어 하나당 log2(7776) ≈ 12.92비트예요.
- 단어 4개 = 51.7비트 → 94종 8자(52.4비트)와 비슷해요. 빠른 공격 기준 평균 약 2.1일.
- 단어 5개 = 64.6비트 → 빠른 공격 기준 평균 약 45년.
- 단어 6개 = 77.5비트 → 빠른 공격 기준 평균 약 35만 년.
조건은 하나예요. 단어를 사람이 고르면 안 되고 무작위로 뽑아야 해요. "내가 좋아하는 노래 가사"나 "유명한 문장"은 단어 수가 많아도 공격 사전에 들어 있어요. 앞서 예로 든 correct horse battery staple도 너무 유명해져서 지금은 그대로 쓰면 안 되는 비밀번호가 됐습니다.
패스프레이즈는 매일 손으로 쳐야 하는 비밀번호, 예를 들어 비밀번호 관리자의 마스터 비밀번호나 컴퓨터 로그인 비밀번호에 잘 맞아요. 나머지 사이트 비밀번호는 관리자가 기억해 주니 무작위 문자열이 편합니다.
NIST SP 800-63B가 권하는 비밀번호 정책
미국 표준기술연구소(NIST)의 디지털 신원 가이드라인 SP 800-63B는 비밀번호 정책의 사실상 기준이에요. 2025년에 나온 개정판(SP 800-63B-4)을 포함해 최근 흐름을 일반적으로 정리하면 이렇습니다. 세부 수치는 개정마다 바뀔 수 있으니 서비스에 적용할 땐 원문을 확인하세요.
- 길이를 우선: 최소 길이를 정하고(개정판은 비밀번호만으로 로그인하는 경우 15자 이상을 요구), 최대 길이는 넉넉하게(최소 64자 이상) 허용해요.
- 복잡성 규칙 강요 금지: "대문자·숫자·특수문자 각 1개 이상" 같은 조합 규칙을 요구하지 말라고 해요. 앞에서 본 것처럼 예측 가능한 패턴만 늘리기 때문이에요.
- 주기적 강제 변경 금지: 90일마다 바꾸게 하면
봄2026!→여름2026!같은 변형만 늘어요. 유출 정황이 있을 때만 바꾸게 합니다. - 차단 목록 대조: 유출된 비밀번호, 사전 단어, 서비스 이름이 들어간 비밀번호는 거절해요.
- 붙여넣기 허용: 비밀번호 관리자 사용을 막지 않도록 붙여넣기와 자동 입력을 허용해요.
- 모든 문자 허용: 공백과 유니코드 문자를 포함해 입력을 넓게 받아요.
- 저장은 느린 해시로: 솔트를 붙이고 의도적으로 느린 키 유도 함수로 저장하라고 해요.
서비스를 만드는 개발자라면 "특수문자 필수" 규칙을 넣기 전에 이 목록을 한 번 보는 걸 권해요.
비밀번호 관리자와 2단계 인증이 기본인 이유
계산을 해 보면 결론은 단순해요. 사이트마다 서로 다른 16자 이상 무작위 비밀번호를 쓰면 무차별 대입은 걱정할 필요가 없어요. 문제는 사람이 그걸 수십 개 외울 수 없다는 거고, 그래서 비밀번호 관리자가 필요합니다.
- 비밀번호 관리자: 사이트별 무작위 비밀번호를 만들고 기억해요. 마스터 비밀번호 하나만 강하게(무작위 단어 5~6개) 만들면 돼요. 사이트 주소가 다르면 자동 입력을 하지 않아서 피싱 사이트를 걸러 주는 효과도 있어요.
- 2단계 인증(2FA): 비밀번호가 유출돼도 두 번째 요소가 없으면 로그인이 안 돼요. SMS보다 OTP 앱이, OTP 앱보다 패스키나 보안 키(FIDO2)가 피싱에 강합니다.
- 재사용 금지: 크리덴셜 스터핑을 막는 유일한 방법이에요.
생성기 구현: Math.random 대신 crypto.getRandomValues
비밀번호 생성기를 직접 만든다면 여기서 실수가 가장 많이 나와요. 앞의 엔트로피 계산은 각 글자가 균등하고 예측 불가능하게 뽑힌다는 전제 위에 있는데, 난수원이 이걸 깨면 계산이 무의미해져요.
Math.random()은 암호용이 아니에요. V8 등 주요 엔진은 xorshift128+ 계열의 빠른 의사난수 생성기를 쓰는데, 내부 상태가 128비트 정도라 출력 몇 개를 관찰하면 상태를 복원해 다음 값을 예측할 수 있다는 게 알려져 있어요. MDN도 보안 용도에는 Web Crypto API를 쓰라고 안내합니다. 브라우저와 Node.js에서는 crypto.getRandomValues()가 운영체제의 암호학적 난수원(CSPRNG)을 써요.
여기에 두 번째 함정이 모듈로 편향이에요. 0~255 사이 바이트를 뽑아 % 62로 문자를 고르면, 256 = 62 × 4 + 8이라서 앞쪽 8개 문자(인덱스 0~7)는 5번, 나머지 54개는 4번씩 나와요. 앞쪽 문자가 1.25배 자주 나오는 거예요. 94종이면 256 = 94 × 2 + 68이라 앞쪽 68개 문자가 3번, 나머지 26개가 2번으로 1.5배 차이가 납니다. 해결책은 나머지가 생기는 구간을 버리고 다시 뽑는 거절 샘플링이에요.
// 편향 없는 무작위 비밀번호 (브라우저·Node.js 19+)
function generatePassword(length, charset) {
const n = charset.length; // 예: 62
const limit = 256 - (256 % n); // 62이면 248, 94면 188
const out = [];
const buf = new Uint8Array(length * 2);
while (out.length < length) {
crypto.getRandomValues(buf);
for (const b of buf) {
if (b < limit) out.push(charset[b % n]); // 248 이상은 버려서 균등하게
if (out.length === length) break;
}
}
return out.join('');
}
62종이면 256개 중 248개만 쓰니 버리는 비율은 8/256 ≈ 3.1%, 94종이면 68/256 ≈ 26.6%예요. 버리는 만큼 다시 뽑으면 되니 성능 부담은 거의 없어요. 이렇게 만든 비밀번호라야 앞의 표에 나온 엔트로피가 그대로 성립합니다. 직접 구현이 번거롭다면 같은 방식으로 동작하는 비밀번호 생성기를 쓰면 돼요.
관련해서, 문자열을 Base64로 바꾸는 건 암호화가 아니라서 비밀번호를 숨기는 데 쓰면 안 된다는 이야기는 Base64는 암호화가 아니다 글에, 토큰 비밀키를 짧게 잡는 실수는 JWT 보안 실수 모음에 정리했어요. 무작위 ID의 충돌 확률을 같은 방식으로 계산한 UUID v4 v7 구조 글도 함께 보면 비트 계산이 익숙해집니다.
자주 묻는 질문
비밀번호는 몇 자리면 안전한가요?
무작위로 생성한다면 대소문자+숫자 기준 12자 이상(약 71비트), 여유 있게는 16자 이상을 권해요. 초당 100억 번 추측하는 오프라인 공격을 가정해도 12자 무작위는 평균 수천 년이 걸립니다. 사람이 직접 만든 비밀번호라면 길이와 상관없이 이 계산이 성립하지 않아요.
특수문자를 꼭 넣어야 하나요?
무작위 생성이라면 특수문자는 글자당 엔트로피를 5.95비트에서 6.55비트로 0.6비트쯤 올려 줄 뿐이에요. 한두 글자 더 길게 만드는 게 같은 효과를 내고, 입력도 편해요. 사이트가 요구하면 넣되, 강도의 핵심은 길이라고 보면 됩니다.
비밀번호 강도 측정기의 점수는 믿어도 되나요?
단순히 문자 종류를 세는 측정기는 P@ssw0rd! 같은 비밀번호에도 높은 점수를 줘요. 사전 단어와 흔한 치환 패턴, 유출 목록까지 반영하는 측정기가 더 현실적이지만, 가장 확실한 방법은 처음부터 무작위로 생성하는 거예요.
비밀번호를 주기적으로 바꿔야 하나요?
NIST는 유출 정황이 없는데 주기적으로 강제 변경하는 걸 권하지 않아요. 예측 가능한 변형만 늘기 때문이에요. 대신 유출 알림을 받았거나 의심될 때는 즉시 바꾸고, 같은 비밀번호를 쓴 다른 사이트도 함께 바꾸세요.
한글 비밀번호는 더 안전한가요?
한글 완성형 문자는 종류가 많아 이론상 글자당 비트가 크지만, 많은 서비스가 한글 입력을 받지 않거나 인코딩 처리가 제각각이라 로그인 문제가 생길 수 있어요. 또 사람이 고른 한글 단어는 무작위가 아니에요. 한글 자판 그대로 영문으로 친 비밀번호(예: 한글 단어를 영문 키로 입력)도 공격 사전에 흔히 들어 있습니다.
정리
- 무작위 비밀번호의 강도는 엔트로피 = 길이 × log2(문자 종류 수). 길이는 곱셈, 문자 종류는 로그라서 길이가 이겨요.
- 94종 8자는 52.4비트로 빠른 오프라인 공격에 평균 3.5일, 소문자 16자는 75.2비트로 약 6.9만 년이에요.
- 외워야 하는 비밀번호는 무작위 단어 5~6개 패스프레이즈(64.6~77.5비트), 나머지는 비밀번호 관리자로 사이트별 무작위 문자열을 쓰고 2단계 인증을 켜세요.
- 생성기를 만든다면
crypto.getRandomValues()와 거절 샘플링으로 예측 불가능하고 균등하게 뽑아야 계산이 성립해요.
길이와 문자 집합을 골라 엔트로피를 바로 확인하며 만들고 싶다면 비밀번호 생성기를 써 보세요. 이 글의 해독 시간은 2026년 기준 가정한 추측 속도로 계산한 예시라서, 하드웨어가 빨라지면 같은 비밀번호의 시간은 짧아진다는 점도 기억해 두세요.