이 세상에 하나는 남기고 가자

이 세상에 하나는 남기고 가자

세상에 필요한 소스코드 한줄 남기고 가자

PHP password_hash() salt option has been deprecated

유영재

php에서 비밀번호를 암호화해서 저장하기 위해서 password_hash()를 사용하고 있다. 다른 곳에서도 오래전에는 MD5, SHA1과 같은 hash 함수를 사용하는 경우들도 있었지만 요즘은 대부분 password_hash()을 사용하고 있을 것이라고 생각한다.

이번에 사용중이던 PHP를 7버전으로 버전업하려고 Vagrant에서 먼저 테스트 중이다. 이야기하려는 주제와는 맞지 않지만 PHP 7을 사용하는 것은 필수적인 선택이라고 생각한다. 실제로 테스트 해보니 실행 성능이 향상되며 메모리 사용량도 40% 가량 줄었다. 이 부분은 상황에 따라 다른 결과를 가져올 수 있으나 확실한 것은 성능의 향상이 기대 이상이라는 것이다. 게다가 PHP는 버전업시에 하위 호환에 대해 보수적이라 PHP 7로 버전업하더라도 심각한 호환성 문제를 일으키는 경우는 거의 없을 것으로 생각된다.

다시 본론으로 돌아와서 password_hash()를 보면 PHP 7로 버전업되면서 변경 사항이 있다. 그중 하나가 "salt" 옵션이 Deprecated 되었다는 것이다.

Warning : The salt option has been deprecated as of PHP 7.0.0. It is now preferred to simply use the salt that is generated by default.

이와 관련해서 사용중이던 코드는 아래와 같다.

public function encryptPassword($str)
{
    return password_hash($str, PASSWORD_BCRYPT, ["salt" => "abcdefghijklmnopqrstuvwxyz"]);
}

이 함수를 PHP 7에서 실행하면 아래와 같은 오류를 만나게 된다.

PHP Deprecated:  password_hash(): Use of the 'salt' option to password_hash is deprecated

그럼 이 문제를 해결하려면 어떻게 할까? 아래의 함수를 보자.

function password_hash_compat($str, $salt) {
    $salt = base64_encode($salt);
  if (strlen($salt) < 22) {
      return false;
  }
    $salt = str_replace('+', '.', $salt);
    return crypt($str, '$2y$10$'.$salt.'$');
}

PHP 7에서 password_hash()와 동일한 동작을 하도록 만들어진 함수다. 다만, BCrypt에 한정되며 cost 등의 옵션을 사용할 수 없지만 샘플로 제시한 코드와는 동일한 결과를 반환한다. 주의할 것은 salt의 길이가 22자 이상이어야 한다는 것(실제값이 아니라 base64_encode된 문자열의 길이가 22자 이상).

이 함수가 맘에 들지 않는다면 ircmaxell/password_compat을 사용할 수 있다. 단, 이 라이브러리는 password_hash()가 지원되지 않는 구버전 PHP를 위한 함수로 PHP 7에서 동작하게 하려면 약간의 수정이 필요하다. 하지만 소스를 볼 수 있으니 내부 동작 원리를 파악할 수 있으므로 유용하다.


그런데 Deprecated 된 옵션을 굳이 compat 함수를 만들어가면서까지 유지 해야할 이유가 뭘까? 항상 동일한 hash를 얻어야 하는 것이 아니라면 굳이 필요가 없다. 비밀번호의 경우는 항상 동일한 hash를 만들어 비교하는 것이 목적이 아니라 비밀번호가 같은지만 비교하면 되기 때문에 동일한 hash는 필요치 않다.

다시 이야기하자면 나의 경우는 맨 처음 적었던 코드를 사용했던 이유는 password_hash()를 제대로 이해하지 못했기 때문이다. "salt를 넣지 않으면 생성시마다 다른 hash를 반환하니 추후에 다른 환경(언어)에서 해당 값을 비교할 수 없는 것이 아닌가?" 하는 걱정 때문에 salt를 사용했던 것이다. 이 부분이 착각이었던 것이다. password_hash()와 짝을 이루는 password_verify()에서는 salt 값이 필요하지 않다. 그렇다면 어떻게 검증을 하는 것일까? password_hash()의 결과에 이미 salt 값이 포함되어 있기 때문에 salt를 따로 넣어줄 필요가 없는 것이다. 이와 관련된 자세한 정보는 "PHP password_hash()와 BCrypt"에 따로 남긴다.


결론은 나와 같은 착각을 하고 있는 것이라면 걱정을 뒤로하고 그냥 "salt" 옵션을 사용하지 않도록 변경함으로써 모든 문제는 해결된다. 다만, 다른 이유로 hash가 항상 동일한 값이 필요한 상황이라면 앞서 제시안 대안들을 검토하길 바란다. 비밀번호의 hash가 같은 것은 보안상으로도 좋지않고 다른 값의 hash로 사용하는 것이라면 SHA1 등을 사용하는 것이 맞을테니 대부분의 경우에 나와 같은 착각에서 이 문제를 고민하고 있을 것 같다.

comments powered by Disqus