htpasswd 파일의 $apr1$은 무엇인가
기본 인증 비밀번호 파일이 쓰는 네 가지 형식과, 그중 둘을 지금 쓰면 안 되는 이유를 정리했습니다.
nginx나 Apache로 디렉터리 하나를 잠글 때 쓰는 파일입니다.
user:$apr1$45y19kAp$f97yq0VLVTIsbfoJ0hYn6.
사용자명:해시 한 줄이 계정 하나입니다. 이 해시 부분이 네 가지 형식 중 하나로 옵니다.
형식 구분
$로 시작하는 값은 첫 필드가 방식을 가리킵니다.
| 앞부분 | 방식 | 솔트 | 반복 |
|---|---|---|---|
$apr1$ |
Apache 변형 MD5 | 8자 | 1,000회 고정 |
$2y$ |
bcrypt | 22자 | 2^cost 회 |
{SHA} |
SHA-1 + base64 | 없음 | 1회 |
| (없음) | 전통 crypt DES | 2자 | 25회 |
같은 비밀번호 correct horse를 각 방식으로 만들면 이렇게 나옵니다.
$apr1$45y19kAp$f97yq0VLVTIsbfoJ0hYn6.
$2y$10$4ynIhrlKtywu3FMagWFMgO9K0Haiof4JPJtLnqvxCXT5bKtq/inwK
{SHA}L55TUjtiq8FBorTWAZ0jy6g129A=
{SHA}에는 솔트가 없다
{SHA}는 비밀번호를 SHA-1로 한 번 돌려 base64로 적은 값입니다. 그게 전부입니다.
{SHA}L55TUjtiq8FBorTWAZ0jy6g129A=
{SHA}L55TUjtiq8FBorTWAZ0jy6g129A=
두 번 만들어도 같은 값입니다. 여기서 두 가지가 따라옵니다.
같은 비밀번호를 쓰는 계정이 파일 안에서 한눈에 보입니다. 해시가 글자까지 같기 때문입니다. 그리고 미리 계산된 표에 그대로 걸립니다 — 솔트가 없으니 이 비밀번호의 SHA-1은 세상에서 하나뿐이고, 흔한 비밀번호라면 이미 표에 있습니다.
$apr1$은 다릅니다. 솔트가 8자 들어가므로 같은 비밀번호도 매번 다른 값이 됩니다.
$apr1$aaaaaaaa$PIfPPBy/tCSByvue467kV.
$apr1$bbbbbbbb$0tJ.z0NIabyFv/p0pGeut1
$apr1$이 하는 일
$apr1$솔트$해시 구조입니다. 솔트 8자, 해시 22자입니다.
안쪽은 MD5를 1,000번 도는 반복문입니다. 매 회차마다 비밀번호·솔트·직전 결과를 어떤 순서로 넣을지 회차 번호로 정합니다 — 짝수면 직전 결과 먼저, 3의 배수가 아니면 솔트를 끼우고, 7의 배수가 아니면 비밀번호를 한 번 더 넣습니다. 이 뒤섞기가 계산을 병렬화하기 어렵게 만드는 장치입니다.
문제는 1,000이라는 숫자가 고정이라는 점입니다. 1990년대에 정해진 값이고 바꿀 수 없습니다.
이 환경에서 재 보면 초당 약 1,200회를 계산합니다. 스크립트 언어로 짠 것이 그 정도고, 최적화된 구현과 GPU를 쓰면 여기서 몇 자릿수가 올라갑니다.
bcrypt는 비용을 정할 수 있다
$2y$10$4ynIhrlKtywu3FMagWFMgO9K0Haiof4JPJtLnqvxCXT5bKtq/inwK
^^ cost
10은 2^10 = 1,024회를 뜻합니다. 12면 4,096회, 14면 16,384회입니다. 1 올릴 때마다 두 배가 됩니다.
하드웨어가 빨라지면 이 숫자를 올려서 따라갑니다. $apr1$에는 그런 손잡이가 없습니다. 새로 만드는 파일이라면 bcrypt를 고를 이유가 여기 있습니다. Apache는 2.4부터, nginx는 시스템 crypt가 지원하면 $2y$를 읽습니다.
기본 인증 자체의 성질
비밀번호 파일과 별개로, HTTP 기본 인증은 자격 증명을 요청마다 보냅니다.
Authorization: Basic dXNlcjpjb3JyZWN0IGhvcnNl
dXNlcjpjb3JyZWN0IGhvcnNl는 user:correct horse의 base64입니다. 암호화가 아니라 인코딩이라 디코딩하면 그대로 나옵니다. HTTPS가 아닌 연결에서는 비밀번호를 평문으로 흘리는 것과 같습니다.
그리고 요청마다 서버가 해시를 한 번씩 계산합니다. bcrypt cost를 높게 잡으면 그 비용이 매 요청에 붙습니다 — 로그인 폼과 달리 세션으로 넘어가지 않기 때문입니다.
확인 순서
- 기존 파일에
{SHA}나$가 없는 줄이 있으면 다시 만듭니다. 솔트가 없거나 DES 기반입니다 - 새로 만들 때는 bcrypt(
-B)를 고르고, cost는 10에서 시작합니다 - 기본 인증은 HTTPS 위에서만 씁니다
- 비밀번호 파일을 문서 루트 밖에 둡니다. 웹으로 읽히면 해시가 통째로 나갑니다
- cost를 올릴 때는 요청당 지연을 재 봅니다. 매 요청에 붙는 비용입니다
tools.onuel.dev의 htpasswd 생성기는 APR1과 {SHA} 항목을 만들고 기존 항목을 검증합니다. 비밀번호는 서버로 보내지 않고 브라우저에서 계산합니다. 위 표의 방식별 차이는 bcrypt, MD5, SHA-1에 같은 문자열을 넣어 대조해 볼 수 있고, 계정을 새로 만든다면 비밀번호 생성기에서 값을 뽑습니다.
- #인증
- #해시
- #Apache