bcrypt 해시 생성기: $2b$10$ 완전 해부

데이터베이스에 저장된 $2b$10$... 형태의 문자열을 보고 각 부분이 무엇을 뜻하는지 궁금한 개발자, 또는 비밀번호 해싱 로직을 검증하다가 같은 비밀번호인데 해시 값이 자꾸 달라져 당황한 사람이라면 이 글이 그 궁금증을 구조 단위로 풀어줍니다.
bcrypt 해시 문자열은 무작위 글자 나열이 아니라 버전·비용·솔트·해시 네 부분이 고정된 순서로 이어 붙은 규격 문자열입니다. 이 글은 그 규격을 자리별로 해부하고, 비용 인자와 솔트가 해시 값에 미치는 영향, 검증할 때 흔히 하는 실수를 다룹니다.
요약 ①
$버전$비용$솔트+해시순서로 고정되며, 솔트 22자와 해시 31자를 합쳐 총 60자입니다. ② 비용 인자가 1 늘 때마다 연산 횟수는 2배(2^비용)로 뛰어, 10에서 11로만 올려도 계산량이 두 배가 됩니다. ③ 같은 비밀번호라도 매번 새 솔트가 섞이므로 해시 값이 달라지고, 검증은 문자열 비교가 아니라 저장된 솔트로 재계산 후 비교해야 합니다.
어디서 이 문제가 실제로 발생하는가
회원가입·로그인 기능을 만들 때 비밀번호는 평문 그대로 저장하지 않고 해시로 바꿔 저장합니다. bcrypt는 그 대표적인 방식 중 하나이고, 저장되는 값은 $2b$10$로 시작하는 60자짜리 문자열입니다.
여기서 자주 생기는 오해가 두 가지입니다. 하나는 같은 비밀번호를 두 번 해시했는데 결과 문자열이 완전히 다르게 나오는 것을 보고 "알고리즘이 고장났다"고 착각하는 경우입니다. 다른 하나는 로그인 시 입력한 비밀번호를 다시 해시로 바꾼 뒤, 그 새 해시 문자열을 DB에 저장된 해시 문자열과 그대로 비교해서 항상 로그인이 실패하는 경우입니다.
두 경우 모두 원인은 같습니다. bcrypt는 매번 새로운 무작위 솔트를 만들어 쓰기 때문에, 같은 입력이라도 솔트가 다르면 최종 해시가 달라진다는 사실을 놓친 것입니다. 이 구조를 알면 두 오해 모두 자연스럽게 풀립니다.
도구 없이 문자열만 보고 구조를 해부하는 법
이미 갖고 있는 bcrypt 해시 문자열이 있다면, 계산 없이 글자 위치만 잘라도 각 부분의 의미를 읽어낼 수 있습니다. 전체 문자열은 항상 아래 순서로 고정되어 있습니다.
| 구간 | 길이 | 예시 표기 | 의미 |
|---|---|---|---|
| 버전 식별자 | 4자 | $2b$ | 알고리즘 변형(2, 2a, 2b, 2y 등)을 나타냄 |
| 비용 인자 | 2자 | 10 | 로그 라운드 수. 실제 반복 횟수는 2의 비용제곱 |
| 구분자 | 1자 | $ | 비용 인자와 솔트를 나누는 고정 문자 |
| 솔트 | 22자 | [22자리] | 매 생성마다 무작위로 만들어지는 값 |
| 해시 | 31자 | [31자리] | 비밀번호+솔트를 반복 연산한 최종 결과 |
앞의 4+2+1=7자를 잘라내면 남는 53자 중 앞 22자가 솔트, 나머지 31자가 해시입니다. 이 잘라내기만으로 비용 인자와 솔트를 눈으로 바로 확인할 수 있습니다.
비용 인자가 연산 횟수를 결정하는 방식
비용 인자가 연산량에 미치는 영향은 계산으로 확인됩니다. 비용 인자는 "2의 몇 제곱만큼 반복하는가"를 나타내는 로그값입니다.
비용 10이면 2^10 = 1,024회, 비용 11이면 2^11 = 2,048회, 비용 12면 4,096회입니다. 인자가 1 늘 때마다 연산 횟수와 그만큼의 계산 시간이 정확히 2배씩 늘어납니다.
솔트가 매번 달라지는 이유와 검증 순서
솔트가 매번 달라지는 이유도 구조로 설명됩니다. 22자리 솔트는 128비트(16바이트) 무작위 값을 bcrypt 전용 문자표로 인코딩한 것입니다. 같은 비밀번호를 두 번 해시해도 이 무작위 값이 매번 새로 만들어지므로, 이어지는 31자리 해시 결과도 함께 달라집니다.
검증 로직도 계산 없이 논리로 따라갈 수 있습니다. 저장된 해시 문자열에서 솔트 22자리를 그대로 꺼내고, 로그인 시 입력된 비밀번호를 그 솔트와 함께 같은 방식으로 다시 계산합니다.
그렇게 나온 새 해시 31자리가 저장된 해시 31자리와 같으면 비밀번호가 맞는 것입니다. 새 무작위 솔트를 또 만들어 비교하면 절대 일치하지 않습니다.
직접 비밀번호와 비용 인자를 바꿔가며 눈으로 확인해보려면 저희 도구로 확인하기.
비밀번호를 입력하면 브라우저에서만 계산해 서버 전송 없이 $2b$10$ 형식의 해시를 즉시 생성합니다.

헷갈리기 쉬운 부분
버전 식별자. $2b$처럼 붙는 버전 문자는 알고리즘 구현의 개정 이력을 나타냅니다. 초기 버전에서 발견된 구현상의 결함을 고치는 과정에서 여러 버전 문자가 등장했고, 지금은 2b가 가장 널리 쓰입니다. 같은 계산 규칙을 쓰는 한 검증에는 지장이 없습니다.
비용 인자의 실질 의미. 비용 인자를 높이면 연산 횟수가 2배씩 늘어나므로 계산 시간도 함께 늘어납니다. 이는 무차별 대입 시도 한 번에 걸리는 시간도 그만큼 늘어난다는 뜻입니다. 다만 값이 너무 크면 정상적인 로그인 요청도 느려지므로, 어떤 값이 적절한지는 서버 성능과 트래픽에 따라 달라지는 설계 문제입니다.
검증 실수. 아래 두 접근은 결과가 다릅니다.
| 접근 방식 | 결과 |
|---|---|
| 입력값을 새 무작위 솔트로 다시 해시해 문자열째 비교 | 비밀번호가 맞아도 항상 불일치 |
| 저장된 해시에서 솔트만 꺼내 같은 솔트로 재계산 후 비교 | 비밀번호가 맞으면 항상 일치 |
이 도구가 실제 bcrypt 라이브러리와 완전히 같은 값을 내나요. 이 도구는 브라우저에서 안전하게 돌아가도록 SHA-256 해시를 비용 인자만큼(2^비용) 반복하는 방식으로 같은 형식($버전$비용$솔트+해시)의 문자열을 만듭니다.
실제 프로덕션 환경의 bcrypt 라이브러리는 SHA-256이 아니라 Blowfish 기반 연산을 씁니다. 문자열의 구조와 비용·솔트가 결과에 미치는 원리는 동일하게 체험할 수 있지만, 나온 해시 값 자체가 표준 bcrypt 라이브러리의 계산 결과와 바이트 단위로 같지는 않습니다.
정리
- bcrypt 해시 문자열은
$버전$비용$솔트+해시순서의 60자 고정 구조입니다. 앞 7자를 자르면 솔트 22자와 해시 31자가 남습니다. - 비용 인자가 1 늘 때마다 연산 횟수는 2배(2^비용)로 늘어나며, 계산 시간과 브루트포스 저항성이 함께 늘어난다는 뜻입니다.
- 같은 비밀번호라도 22자리 솔트가 매번 무작위로 새로 만들어지므로 최종 해시 값은 매번 달라집니다.
- 검증할 때는 새 솔트로 다시 해시해 문자열째 비교하면 안 되고, 저장된 해시에 담긴 솔트를 그대로 써서 재계산한 뒤 비교해야 합니다.
- 이 원리를 실제 문자열로 확인해보려면 아래 도구에서 비밀번호와 비용 인자를 바꿔가며 결과를 직접 생성해볼 수 있습니다.
비밀번호를 입력하면 브라우저에서만 계산해 서버 전송 없이 $2b$10$ 형식의 해시를 즉시 생성합니다.
자주 묻는 질문
bcrypt 해시는 왜 매번 60자로 고정되나요?
버전 4자, 비용 인자 2자, 구분자 1자, 솔트 22자, 해시 31자를 모두 더하면 60자가 됩니다. 솔트가 128비트, 해시 결과가 184비트 값을 bcrypt 전용 문자표로 인코딩하기 때문에 이 자릿수가 고정되어 있습니다.
비용 인자를 최대로 설정하면 무조건 안전한가요?
연산 횟수가 늘어나는 만큼 무차별 대입에 걸리는 시간은 늘어나지만, 동시에 정상적인 로그인 요청 한 번을 처리하는 시간도 똑같이 늘어납니다. 어떤 값이 서비스에 적합한지는 서버 성능과 로그인 빈도에 따라 달라지는 설계 문제라, 이 글에서 특정 값을 정답으로 제시하지는 않습니다.
해시 결과를 그대로 비교했는데 왜 항상 로그인이 실패하나요?
검증용 해시를 만들 때 새로운 무작위 솔트를 사용했기 때문입니다. 같은 비밀번호라도 솔트가 다르면 해시 결과가 달라집니다. 저장된 해시 문자열에 포함된 솔트를 그대로 추출해 그 솔트로 재계산한 값을 비교해야 정상적으로 일치합니다.
이 도구로 생성한 해시를 실제 서비스 데이터베이스에 저장해도 되나요?
이 도구는 SHA-256 반복 연산으로 형식만 동일하게 만드는 클라이언트 학습·확인용 도구이며, 프로덕션 환경의 표준 bcrypt 라이브러리와 바이트 단위로 같은 값을 내지 않습니다. 구조와 원리를 확인하는 용도로 활용하시면 됩니다.
