Bcrypt
브라우저를 벗어나지 않고 bcrypt 해시를 만들거나 기존 해시와 비밀번호를 대조합니다. 비용과 $2a$, $2b$, $2y$ 변형을 고르고, 어떤 해시든 안을 뜯어 읽습니다.
여기에 입력한 것은 이 브라우저를 떠나지 않습니다. 계산은 이 탭의 Web Worker에서 돌아가니, 직접 네트워크 패널을 열어 확인해 보세요. 계산하는 동안 나가는 요청은 하나도 없습니다.
해시 만들기
비밀번호와 비용과 변형을 정합니다. 솔트는 실행할 때마다 이 브라우저에서 새로 뽑히므로 같은 비밀번호라도 매번 다른 해시가 나옵니다. 이는 bcrypt가 제대로 작동한다는 뜻이지 결함이 아닙니다.
해시와 비밀번호 대조
붙여 넣은 해시는 비밀번호 없이도 즉시 뜯어 읽힙니다. 비밀번호를 더하면 일치 여부를 알려 줍니다. 해시는 여기서도 다른 어디서도 되돌릴 수 없습니다.
bcrypt 해시 안에 실제로 들어 있는 것
bcrypt 해시는 60자짜리 문자열 하나이고, 그 안의 어느 부분도 감춰져 있지 않습니다. 비밀인 것은 그것을 만든 비밀번호뿐인데, 그 비밀번호는 거기에 아예 들어 있지 않습니다. 해시를 뜯어 읽는 데에는 키도 비밀번호도 계산도 필요하지 않습니다 — 이 페이지는 하나를 붙여 넣는 순간 그 일을 합니다.
- 변형, 문자열이 시작하는 자리입니다: $2b$, $2a$, $2y$ 가운데 하나. 그 해시를 누구의 규칙이 만들었는지를 이름 짓습니다.
- 비용, 정확히 두 자리로 적힙니다: 04부터 31까지. 이것은 지수여서 비용 12는 2^12 — 4,096 — 번의 키 늘이기 라운드를 뜻하고, 비용 13은 비용 12보다 13분의 1만큼 더 드는 것이 아니라 두 배로 듭니다.
- 솔트, 22자: 16바이트의 무작위 값이, 그 솔트를 더해 얻은 답 옆에 드러난 채로 적혀 있습니다.
- 다이제스트, 마지막 31자: 23바이트의 출력입니다. 비밀번호에 기대는 부분은 이것뿐이고, 그리고 이것은 해시가 아닙니다 — 해시는 문자열 전체입니다.
이 글자들을 뽑아 오는 알파벳은 bcrypt 자신의 것이고 표준 Base64가 아닙니다: 차례가 ./A-Za-z0-9여서, Base64에서는 더하기와 빗금이 끝에 오는 자리에 마침표와 빗금이 앞에 옵니다. 보통의 Base64 해독기에 넣으면 오류가 아니라 틀린 바이트가 돌아옵니다. 오후 한나절을 통째로 잃는 고전적인 방법입니다.
한도는 72바이트이고, 한국어에서는 24자에서 옵니다
bcrypt는 비밀번호를 최대 72바이트까지만 읽고 그 뒤는 모두 무시합니다. 72자가 아니라 UTF-8의 72바이트이고, 그것이 몇 글자가 되는지는 그 언어의 글자가 몇 바이트인지에 달려 있습니다. 부호가 붙지 않은 라틴 문자 26자는 한 글자에 1바이트이고, 그 밖의 글자는 모두 그보다 더 듭니다. 한글은 한 글자에 3바이트가 들므로 한국어 비밀번호는 24자에서 잘립니다. 영어를 읽는 사람이 만나는 수의 삼분의 일이고, 사람들이 실제로 치는 길이입니다. 한 가지가 더 있습니다: 붙여 넣은 한글이 자모로 풀려 있으면 — 어떤 macOS 자료에서 복사한 글이 그렇습니다 — 낱자 하나하나가 3바이트씩이어서 한 음절이 받침 없이는 6바이트, 받침이 있으면 9바이트가 되고, 눈에 보이는 여덟에서 열두 글자만으로 한도에 닿습니다. 그때에도 칸 아래 계수기는 24자를 셉니다. 세는 것이 음절이 아니라 코드 포인트이기 때문입니다. 이 독자에게 한도는 이론이 아니라 실제로 닿는 것입니다.
- ASCII는 한 글자에 1바이트이므로 영어를 쓰는 사람은 72자에서 한도를 만나고, 거의 만나지 않습니다.
- 히브리어, 아랍어, 러시아어, 그리스어는 한 글자에 2바이트이므로 한도가 36자에서 옵니다.
- 일본어, 한국어, 중국어는 3바이트이므로 24자에서 옵니다.
- 이모지는 대개 4바이트이고, 사람들이 실제로 치는 것 가운데 여럿은 코드 포인트 여러 개를 이어 붙인 것이어서, 몇 개만 놓아도 이미 예산 전부입니다.
비밀번호 칸 아래의 바이트 계수기는 한도를 나중에 알아차리는 대신 다가오는 것을 볼 수 있게 하려고 있습니다. 그리고 자름은 글자가 아니라 바이트 자리에서 일어납니다. 어느 참조 구현이든 그렇게 하기 때문이며, 경계에 걸친 여러 바이트 글자는 제 일부를 잃고 나머지를 남깁니다. 이는 결함이 아니라 호환되는 동작이고, 이 페이지는 그런 일이 일어나면 그렇게 말합니다.
그리고 여기서부터가 나머지 전부를 설명하는 대목입니다: 자름은 답 안에 자취를 남기지 않습니다. 키 일정은 정확히 72바이트의 키를 제 상태에 섞어 넣고 그보다 긴 키를 앞으로 되감는 일이 결코 없으므로, 100바이트짜리 비밀번호를 해시해도 그 앞 72바이트를 해시해도 같은 다이제스트가 나옵니다. 구현이 알아차릴 것도 없고 알릴 것도 없습니다. 어느 라이브러리도 오류를 내지 않는 까닭이 그것이고, 앞 72바이트가 같은 서로 다른 긴 비밀번호 둘이 저장된 같은 해시에 둘 다 일치하는 까닭도 그것입니다. 이 페이지도 긴 비밀번호를 거절할 수 없습니다 — 실제 시스템이 잘라서 만든 해시와 맞춰 보는 중이라면 답이 필요하니까요 — 그래서 답을 내주고, 무엇이 잘렸는지를 알려 줍니다.
변형은 접두사를 바꿀 뿐 다이제스트를 바꾸지 않습니다
변형은 판 번호처럼 보이지만 그것이 아닙니다. 이 도구가 쓰는 셋에는 차례가 없고, 어느 하나가 다른 하나를 대신하지도 않으며, 그 사이에서 고르는 일은 보안이 아니라 호환의 판단입니다.
- $2b$는 OpenBSD가 자리 잡은 것이고 오늘날 Python, Node, Go가 내놓는 것입니다. 여기서의 기본값이며, 다른 것을 강요하는 것이 없을 때의 옳은 답입니다.
- $2a$는 더 오래된 것이고, 오래 돌아온 Java와 Spring Security 환경 가운데는 아직 이것을 기대하는 것이 있습니다.
- $2y$는 PHP의 password_hash가, 따라서 Laravel이 쓰는 것이어서, 그 세계로 붙여 넣는 해시는 대개 이것을 원합니다.
셋을 가르는 것은 255바이트보다 긴 키를 어떻게 다루느냐인데, bcrypt는 이미 72바이트에서 읽기를 멈춥니다 — 그래서 여기서 칠 수 있는 어떤 비밀번호도 그 차이에 닿지 못합니다. 이 도구가 앞으로 받게 될 무엇에 대해서든 셋은 같은 23바이트를 내놓고, 다른 것은 앞의 네 글자뿐입니다. 변형을 바꿔도 다이제스트는 움직이지 않습니다.
둘이 더 있고, 이 도구는 그것을 계산하는 대신 이름을 밝힙니다. 하나는 $2x$입니다. 이것은 고침이 아닙니다: Openwall이 부호 확장 버그를 일부러 되살리려고 만든 것으로, 망가진 코드가 만든 해시를 그래도 맞춰 볼 수 있게 하려던 것이며, 여기서 그것을 구현한다면 알려진 결함을 도구 안에 들여놓는 셈이 됩니다. 다른 하나는 $2$로, 키 끝에 0 바이트가 더해지기 전의 최초의 것입니다. 둘 다 지금 쓰이는 무엇에서도 나오지 않습니다. 손에 하나가 있다면, 그것이 나온 시스템은 변형이 찾아낸 것 가운데 가장 작은 문제일 만큼 오래된 것입니다.
비용과, 그것이 여러분의 기기에서 얼마나 드는가
비용은 작업 계수로 4부터 31까지이며 지수입니다: 한 단계마다 시간이 두 배가 됩니다. 그것이 bcrypt의 온전한 요점입니다. 비밀번호 해시는 느리라고 있는 것입니다. 훔친 표를 쥔 공격자는 추측마다 같은 값을 치러야 하고, 비용을 한 단계 올리면 같은 돈으로 얻는 초당 추측 횟수가 반으로 줄기 때문입니다.
어떤 비용이 실제로 얼마나 걸리는지는 그것을 돌리는 기계에 관한 사실이지 미리 적어 둘 수 있는 수가 아닙니다 — 전화기와 서버는 두 자릿수만큼 떨어져 있습니다. 그래서 이 페이지는 인용하는 대신 잽니다: 페이지가 열릴 때 여러분의 기기에서 짧은 해시를 한 번 돌리고 거기서 미루어 셈합니다. 비용 조작부 옆의 어림값이 잠깐 비었다가 나타나는 까닭이 그것입니다. 몇 초를 넘으면 시작하기 전에 묻기 때문에, 잘못 친 숫자가 멈춰 버린 페이지로 읽히지 않습니다.
수를 고르자면: 12는 Laravel과 Python의 bcrypt가 기본으로 내놓는 값이고 오늘날 운영의 바닥으로 알맞습니다. 그보다 낮은 비용은 시험용 자료를 위한 것으로, 사용자 쉰 명을 심는 시험 묶음이 한 사람마다 1초를 쓸 일은 없어야 합니다. 15쯤보다 높은 비용은 내보내기 전에 실제 로그인 트래픽과 견주어 재 볼 값어치가 있습니다. 로그인 한 번마다 같은 값을 치르는 것은 이쪽도 마찬가지이기 때문입니다.
# Apache: 비용 12로 htpasswd 줄 쓰기
htpasswd -nbBC 12 alice "correct horse battery staple"
# PHP와 Laravel: password_hash가 내놓는 것은 $2y$
php -r 'echo password_hash("correct horse battery staple", PASSWORD_BCRYPT);'
# Python: bcrypt가 내놓는 것은 $2b$, 비용을 받는 것은 gensalt
python -c "import bcrypt; print(bcrypt.hashpw(b'correct horse battery staple', bcrypt.gensalt(12)))"솔트와, 여기서 만든 해시가 저장용이 아닌 때
솔트는 해시마다 새로 뽑는 16바이트의 무작위 값이고, 문자열 안에 드러난 채로 저장됩니다. 비밀이 아니고 비밀이도록 뜻한 적도 없습니다 — 그 일은 같은 비밀번호 둘이 서로 관계없는 해시 둘을 내놓게 하는 것이고, 그래야 훔친 표를 미리 셈해 둔 사전 하나로 한꺼번에 칠 수 없습니다.
여기서 같은 비밀번호가 단추를 누를 때마다 다른 해시를 주는 까닭도 그것입니다. 결함이 아니고 앞의 답이 낡은 것도 아닙니다: 새 솔트는 bcrypt가 일하고 있다는 뜻입니다. 그 해시들은 모두 그 비밀번호와 일치합니다. 솔트가 대조되는 문자열 안에 함께 실려 다니기 때문입니다.
고급 솔트 칸은 한 가지 일을 위해 있습니다: 남의 해시를 그대로 재현해서 두 구현을 맞대어 볼 수 있게 하는 것입니다. 해시 전체를 붙여 넣으면 비용과 변형이 저절로 따라오므로, 올바른 비밀번호라면 입력을 글자 하나까지 그대로 재현합니다. 그렇게 만든 것은 견주기 위한 것이지 저장하기 위한 것이 아닙니다 — 두 번 쓰인 솔트는 제 일을 그만둔 솔트입니다.
bcrypt 해시는 되돌릴 수 없습니다
사람들이 이런 페이지에 오는 것은 bcrypt 해시를 복호할 길을 찾아서입니다. 그런 길은 없고, 까닭은 그것이 어려워서가 아닙니다: 비밀번호가 문자열 안에 없습니다. 60자가 나르는 것은 변형과 비용과 무작위한 16바이트와 23바이트의 출력이고, 그것들을 어떻게 늘어놓아도 어떤 길이의 비밀번호도 담기지 않습니다. 암호화된 것이 없으니 복호할 것도 없습니다.
어떤 사이트가 할 수 있는 것은 — 그리고 복호를 내걸고 있는 곳들이 하고 있는 것은 — 추측입니다. 흔한 비밀번호 목록을 가져다가 여러분의 솔트와 여러분의 비용으로 하나씩 해시해 보고, 그 가운데 일치하는 것이 있는지 봅니다. bcrypt는 바로 그 일이 비싸지도록 설계되었고 비용이 그것을 위해 있으며, 이 도구는 그것을 어떤 꼴로도 내놓지 않습니다.
그 물음의 정직한 판은 대개 다른 물음이고, 그쪽에는 답이 있습니다: 이 문자열 안에 무엇이 있는가, 그리고 이 비밀번호가 그것을 만드는가. 둘 다 이 페이지에 있습니다. 해시를 붙여 넣으면 그것이 나르는 것이 모두 보이고, 비밀번호를 더하면 예 또는 아니요를 받습니다.
자주 묻는 질문
- 제 비밀번호가 서버로 보내지나요?
- 아니요. 모든 일이 이 브라우저 탭 안에서 일어납니다 — 해싱은 여러분의 기계에 있는 Web Worker에서 돌고, 비밀번호가 URL에 들어가는 일이 없으며, 브라우저 저장소에 적히는 것도 없습니다. 네트워크 패널을 열고 비밀번호를 친 다음 단추를 눌러 보세요: 밖으로 나가는 요청은 하나도 없습니다. 같은 물음에서 이 페이지 위에 놓이는 두 사이트와의 차이가 바로 그것입니다.
- bcrypt 해시를 비밀번호로 복호할 수 있나요?
- 아니요, 그리고 그것이 어려워서가 아닙니다. 비밀번호가 문자열 안에 없습니다: 거기 있는 것은 변형과 비용과 솔트와 23바이트의 출력이고, 그 가운데 입력을 담은 것은 없습니다. 복호해 주겠다는 것은 여러분의 솔트에 대고 흔한 비밀번호를 추측하고 있을 뿐이며, bcrypt는 바로 그것을 느리게 만들려고 설계된 것입니다.
- 같은 비밀번호인데 매번 다른 해시가 나오는 까닭은 무엇인가요?
- 실행할 때마다 새 무작위 솔트를 뽑고, 그 솔트가 문자열의 일부이기 때문입니다. 그 해시들은 모두 같은 비밀번호와 일치합니다 — 대조기가 여러분이 붙여 넣은 해시에서 솔트를 읽어 내므로, 어느 실행이 그것을 만들었는지 알 필요가 없습니다.
- 제 비밀번호가 72바이트에서 잘렸습니다. 왜 오류가 없었나요?
- 알아차릴 것이 없기 때문입니다. bcrypt는 정확히 72바이트의 키를 제 상태에 섞고 그 뒤를 결코 읽지 않으므로, 긴 비밀번호와 그 앞 72바이트는 같은 다이제스트를 냅니다 — 자름은 라이브러리가 알아볼 자취를 남기지 않습니다. 이 페이지는 그 일이 일어났다고 알려 드립니다. 그것이 어떤 구현이든 정직하게 할 수 있는 최대입니다.
- 제 비밀번호가 24자에서 한도에 닿은 까닭은 무엇인가요?
- 한도가 72자가 아니라 72바이트이고, 한글은 한 글자에 3바이트가 들기 때문입니다. 히브리 문자, 아랍 문자, 키릴 문자, 그리스 문자는 2바이트여서 그쪽에서는 한도가 36자에서 옵니다. 이모지는 대개 4바이트입니다. 칸 아래의 계수기가 치는 동안 바이트 수를 보여 줍니다.
- 어떤 변형을 골라야 하나요?
- 붙여 넣을 시스템이 기대하는 것으로 고르세요: PHP와 Laravel에는 $2y$, 오래된 Spring Security에는 $2a$, 그 밖의 모든 곳에는 $2b$. 이는 보안이 아니라 호환의 판단입니다 — 이 도구에 줄 수 있는 어떤 길이에서도 셋은 똑같은 다이제스트를 내놓고, 다른 것은 앞의 네 글자뿐입니다.
- 어떤 비용을 써야 하나요?
- 12가 흔한 기본값이고 운영의 바닥으로 알맞습니다. 시험용 자료에는 낮은 비용을 써서 시험 묶음이 키 늘이기 값을 쉰 번씩 치르지 않게 하고, 15보다 높은 것은 실제 로그인 트래픽과 먼저 견주어 재 보세요. 성공한 로그인마다 공격자와 같은 값을 치르기 때문입니다.
- 여기서 만든 해시를 진짜 사용자 표에 넣어도 안전한가요?
- 데이터베이스를 심거나 시험 자료를 쓰거나 htpasswd 줄을 더하는 일이라면 예 — 솔트는 여러분 브라우저의 암호학적 난수원에서 옵니다. 다만 이곳은 비밀을 맡아 주는 서비스가 아니라 갈겨쓰는 페이지입니다: 솔트를 직접 쳐 넣으면 그 결과는 저장하기에 안전하지 않게 되고, 페이지가 그때 그렇게 말합니다. 지킬 값어치가 있는 비밀번호는 그것이 살아갈 곳에서 만들어지는 편이 낫습니다.
- 그것을 만든 코드는 bcrypt라고 하는데 제 해시가 $2y$로 시작하는 까닭은 무엇인가요?
- PHP의 password_hash가 $2y$를 쓰고 Laravel이 그 위에 세워졌기 때문입니다. $2b$와 같은 알고리즘이며, 변형은 어느 구현이 그 문자열을 썼는지를 기록합니다. 그것을 거절하는 대조기는 비밀번호가 일치하지 않는 것이 아니라 변형을 거절하고 있는 것입니다.
- 해시를 붙여 넣었더니 솔트 표기가 고쳐졌다고 나옵니다. 망가진 것인가요?
- 아니요. 22자는 16바이트가 필요로 하는 것보다 많은 비트를 담을 수 있어서 끝의 네 비트가 떨어지고, 같은 16바이트로 풀리는 표기가 둘 이상인 솔트가 생깁니다. 실제 시스템이 그런 것을 내놓아 왔으므로, 이 페이지는 그런 해시를 거절하는 대신 읽고 표준 표기를 알려 줍니다.
관련 도구
- JWT 디코더 / 검증기
JSON Web Token 디코딩과 검증 — 서명과 클레임.
- 해시 생성기
MD5, SHA-1, SHA-256, SHA-384, SHA-512를 한 번에.
- 인증서 / PEM 디코더
OpenSSL 없이 X.509 인증서와 CSR을 읽기.
- HMAC 생성
키로 메시지에 서명, 또는 Webhook 서명을 검증.