개발 이야기3분 읽기

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

dXNlcjpjb3JyZWN0IGhvcnNluser:correct horse의 base64입니다. 암호화가 아니라 인코딩이라 디코딩하면 그대로 나옵니다. HTTPS가 아닌 연결에서는 비밀번호를 평문으로 흘리는 것과 같습니다.

그리고 요청마다 서버가 해시를 한 번씩 계산합니다. bcrypt cost를 높게 잡으면 그 비용이 매 요청에 붙습니다 — 로그인 폼과 달리 세션으로 넘어가지 않기 때문입니다.

확인 순서

  1. 기존 파일에 {SHA}$가 없는 줄이 있으면 다시 만듭니다. 솔트가 없거나 DES 기반입니다
  2. 새로 만들 때는 bcrypt(-B)를 고르고, cost는 10에서 시작합니다
  3. 기본 인증은 HTTPS 위에서만 씁니다
  4. 비밀번호 파일을 문서 루트 밖에 둡니다. 웹으로 읽히면 해시가 통째로 나갑니다
  5. cost를 올릴 때는 요청당 지연을 재 봅니다. 매 요청에 붙는 비용입니다

tools.onuel.devhtpasswd 생성기는 APR1과 {SHA} 항목을 만들고 기존 항목을 검증합니다. 비밀번호는 서버로 보내지 않고 브라우저에서 계산합니다. 위 표의 방식별 차이는 bcrypt, MD5, SHA-1에 같은 문자열을 넣어 대조해 볼 수 있고, 계정을 새로 만든다면 비밀번호 생성기에서 값을 뽑습니다.

  • #인증
  • #해시
  • #Apache