해싱 방법
워드프레스는 Portable PHP password hashing framework(phpass)를 사용하여 패스워드를 해싱한다. 이 프레임워크에서는 PHP5.5이후의 내장된 password_hash(), password_verify()를 사용하는 것이 권장하며, 자신들은 그 이전 프로젝트를 위한 호환성을 보장한다고 한다. 워드프레스 최소 환경이 5.2.9인 것을 감안하면 그렇게 해야 할 듯.
예를 들어 설명하는 것이 낫겠다. 워드프레스에서 시험적으로 ‘guest’라는 계정과 ‘guest’라는 매우 취약한 패스워드를 사용하여 계정을 만들었다. 그러면 패스워드 해쉬 값은 ‘$P$ByCNnCoqFGHDSjIkWjlf8heBFfLJLI1’로 나오게 된다.
여기서 ‘$P$’는 이 해시가 phpass, 즉 위에 언급된 프레임워크를 사용해 나온 해싱을 의미한다. 그래서 워드프레스 유저 테이블의 모든 패스워드의 처음은 이것으로 시작한다.
그리고, 우선 코드 테이블을 이야기 하자. phpass의 코드 테이블은 아래와 같다.
./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
점(.)부터 0, 슬래시(/)는 1, … 순으로 증가한다.
그다음 대문자 ‘B’의 의미는 솔트를 위한 파라미터이다. B는 위 코드 테이블의 인덱스 13에 해당한다. 더 자세하게는 2^12=8192를 md5돌리겠다는 말이다. 그리고 그 다음 8글자 ‘yCNnCoqF’는 솔트이다.
해시 얻기
이 솔트를 이용해 해시를 제작한다. wp-includes/class-phpass.php, PasswordHash::crypt_private() 메소드에 다음 부분이 있다.
# We're kind of forced to use MD5 here since it's the only # cryptographic primitive available in all versions of PHP # currently in use. To implement our own low-level crypto # in PHP would result in much worse performance and # consequently in lower iteration counts and hashes that are # quicker to crack (by non-PHP code). if (PHP_VERSION >= '5') { $hash = md5($salt . $password, TRUE); do { $hash = md5($hash . $password, TRUE); } while (--$count); } else { $hash = pack('H*', md5($salt . $password)); do { $hash = pack('H*', md5($hash . $password)); } while (--$count); }
먼저 회원이 입력한 비밀번호와 솔트를 합쳐 md5 초벌을 만들고, 2^13 = 8192회 md5 해싱을 반복하여 해시를 생성한다. 주어진 예를 들어 설명하면 ‘yCNnCoqFguest’ (솔트 + 비밀번호)로 초벌 해싱을 한 후 8192회 해싱을 반복(이 때는 이전의 해시 문자+패스워드)하여 임의의 문자열을 생성한다. 사실 문자열이라기 보다는 그냥 0과 1의 이진 패턴이다. 어쨌든 예의 결과를 16비트씩 10진수 (괄호는 16진수) 패턴으로 바꿔 출력하면 다음과 같이 나온다.
210 (D2) 244 (F4) 120 (78) 47 (2F) 05 (05) 139 (8B) 111 (6F) 188 (BC) 42 (2A) 173 (AD) 218 (DA) 68 (44) 235 (EB) 85 (55) 93 (5D) 212 (D4)
해시 인코딩
여기서 끝나는 것이 아니다. 다음 방법에 따라 인코딩을 한다.
- 위 숫자, 즉 D2 F4 78 … 55 5D D4를 왼쪽부터 3바이트 (24비트)씩 읽어 바이트 단위로 역순 배치한다. 아래와 같이 그룹이 나눠진다.
- 78 F4 D2
- 8B 05 2F
- 2A BC 6F
- …
- 이 24비트를 6비트씩 쪼개 0 ~ 3F(십진수 63) 사이의 숫자로 변환한다.
- 78 F4 D2를 2진 변환하면 0111 1000 1111 0100 1101 0010이다.
- 6비트씩 쪼개어 4부분으로 변환하면 011110, 001111, 010011, 010010
- 각 6비트를 하위부터 읽어 코드 테이블에 대응한다.
- 010010 = 18 -> G
- 010011 = 19 -> H
- 001111 = 15 -> D
- 011110 = 30 -> S
- 2번을 반복하여 총 16글자가 나올때까지 반복한다. 완성된 16글자는 ‘GHDSjIkWjlf8heBFfLJLI1’이다.
- 데이터베이스에 기록할 때는 처음 매직 코드와 솔트 생성을 위한 파라미터, 솔트, 인코딩된 해싱을 모두 하나로 붙여 기록한다. 즉, $P$ByCNnCoqFGHDSjIkWjlf8heBFfLJLI1가 데이터베이스 패스워드 필드에 기록되는 것이다.
이전 호환성 유지, 그리고 트릭
워드프레스도 아주 예전에는 md5로 패스워드를 해싱했었다. 그러나 md5는 취약하여 더이상 패스워드 같은 중요 정보를 해싱할 때 쓰지 않는다. 그러나 호환성을 위해 로그인 시 패스워드 매칭을 위한 입력으로는 md5를 사용할 수 있다. 그러나, 이 문자열은 길이가 다르고, $P$로 시작하지 않으므로 워드프레스 코어는 정확한 패스워드가 입력된 경우 단박에 이 패스워드를 위 알고리즘대로 변경하여 해시를 교체한다.
이를 이용해서 패스워드가 도저히 기억나지 않지만, 데이터베이스에 접근할 수 있는 권한이 있는 경우에 간단하게 임시로 패스워드를 재설정하는 용도로는 사용할 수 있다.
결론
워드프레스는 phpass (피에이치패스라고 읽는다)라이브러리를 이용하여 패스워드 해싱을 관리한다. 이 방법은 아마도 PHP5.5이후의 표준 함수와 유사한 방법을 사용하는 것으로 보인다. 암호와 보안 쪽은 잘 모르는 관계로 설명이 부족했을 수 있지만, 최대한 소스코드를 분석하여 알아낸 만큼 기록해 둔다.
전임자가 어카운트 정보를 인계하지 않고 퇴직해버려서 곤란한 참이었는데 이 글 보고 비밀번호를 초기화 할 수 있었습니다. 감사합니다~
도움이 되셨다니 좋네요. 저도 글 적은 보람이 있습니다.