제공 :
한빛 네트워크
저자 : Chris Cornutt
역자 : 김동혁
원문 :
Safe(r) Passwords in PHP
웹 애플리케이션을 사용해 왔다면 누구나 잘 알다시피, 패스워드는 여전히 인증 수단으로서 가장 보편적인 것으로 알려져 있습니다. 물론 지난 수년간 전세계적으로 인증 암호 체계에 대한 많은 발전이 있었지만 아직까지 애플리케이션과 웹사이트에서 이를 반영하는 것은 미온적이었습니다. 그리고 인증 시스템은 단일 실패점(single point of failure)의 성격을 갖고 있기 때문에 사실 유저에게 스스로 자신이 누군지 밝히게 했다는 점 자체가 취약점이 될 수 있습니다.
최근에는 두 가지 이상의 인증 방법을 제시하는 솔루션이나, 공인 인증 체계와 같이 다양한 방식으로 애플리케이션 사용자들을 보호하려는 대안 연구들이 관심을 얻고 있는 추세입니다. 사용자들도 이제 간단한 패스워드는 큰 위험을 초래할 수 있음을 잘 알고 있습니다. 뉴스에서 심심치 않게 나오는 취약한 패스워드나, 패스워드 보안 체계 때문에 어떤 회사의 개인정보가 유출되었다는 소식은 이제 지겨운 이야기 거리 중 하나가 될 지경이죠. 실망스럽게도 사실 관리자 입장에서 유저들에게 복잡한 패스워드를 써달라고 요청하는 등 외에는 보안에 관여할 수 있는 여지가 없어 보입니다. 하지만 우리는 분명 더 신경을 쓸 여지가 있습니다. 단지 얼마나 많은 회사와 애플리케이션들이 유저의 패스워드를 보호하는 것에 대해 무관심한 채로, 형편없는 방식을 취했다는 사실만이 놀라울 따름이지요. 심지어 일부 사이트들은 패스워드를 평문으로 저장하는 과감함을 보이기도 합니다(상상만해도 정말 소름이 돋네요…).
과거의 PHP 패스워드 보안 체계는 어떠했을까
PHP 기반의 애플리케이션들은 여타의 환경과 똑같은 문제를 갖고 있습니다. PHP가 줄곧 시달려온 문제들 중 하나는 바로 소중한 유저의 정보를 좀 더 잘 다룰 수 있는 툴과 체계에 대한 개발자들의 인식 부재였습니다. 다행스러운 것은 최신 웹 언어/프레임웍에서는 패스워드 보안에 대해 더 명시적으로 강조하고 있다는 점이지요. 일반적으로 생각하는 비밀 번호 보안 방식은 대략 다음과 같습니다 : 1. 먼저 유저가 시스템에 새 패스워드를 입력하면, 2. 솔트(salt)를 더해주고 암호화 한 뒤 저장하면 끝. 어떤 암호화 방식을 사용하느냐에 따라 복호화에 걸리는 시간이 달라지지만, 암호화/복호화는 서버 단에서 부하가 걸릴 여지가 있는 부분이기 때문에 복잡한 암호화를 사용하기 부담스러운 점도 있습니다. 때문에 PHP는 패스워드를 안전하게 보관하는 효율적인 대안 방식인 해싱에 더 초점을 두어왔습니다.
사용자가 입력한 패스워드 문자열이 해싱이라 불리는 프로세스를 거치면, 해싱 메소드는 어떠한 형식을 갖춘 괴상한 문자열로 이를 변환하여 뱉어줍니다. 이는 일반적인 암호화와 비교하면 훨씬 빠르고, 또한 단방향 변환 특성을 갖고 있습니다. 단방향 변환 특성은 한 번 어떠한 문자열이 특정 해싱 기법을 거쳐서 괴상한 문자열로 변환되었을 때, 우리가 그 특정 해싱 기법이 무엇인지 알아도 쉽게 이를 역으로 해싱하여 이전 문자열을 알 수 없다는 것을 말합니다. 그래서 보통은 해싱 기법을 통해 변형된 비밀번호를 깨기 위해서는 가능한 모든 문자열을 가지고 이들을 해싱한 결과값을 대조해 보는 것이 일반적입니다. 물론 수퍼 컴퓨터를 갖추고 있다면 이것도 매우 쉽게 깨어질 수 있지요. 다행히도 PHP에서는 다른 해커들의 컴퓨터에서 여러분들의 비밀번호를 깨는데 걸리는 시간을 획기적으로 늘리면서도 간단히 사용할 수 있는 bycrpt를 제공하고 있습니다.
Crypt에 대해 알아보자
bcrypt를 이용하여 패스워드를 보호하는 것은 몇 가지 이점을 갖고 있습니다. 첫째로, 일반적인 해싱 호출 함수인 md5( )나 sha1( )이 단일 패스 실행인 것에 반하여 생성된 해시 값을 반복적으로 순회한다는 점이 다릅니다. 또한 오래된 기존 방법들보다 한층 강화된 해싱 기법을 제공하며 결과로 얻어지는 해시 값의 길이가 예측 가능하기 때문에 DB 등에 저장하여 관리할 때도 무리가 없습니다. 다시 PHP로 돌아가보면, 최근 PHP 5.5.0 버전에는 개발자들로 하여금 좀 더 정확하게 해싱을 이용하고, 유저의 비밀번호를 검증하는 것을 쉽게 해주는 랩퍼(wrapper )가 제공됩니다(
password_hash method와 관련 함수들이 그것입니다). 이 랩퍼는 예전부터 사용되어 온 crypt을 결국 이용하고, 이를 이용하면 구현의 부담이 한층 줄어드는 장점도 있고 좀 더 간소화 되었다고 볼 수 있습니다.
다음의 예제를 통해, 어떻게 주어진 패스워드에 솔트와 해싱이 적용되는지 확인해보도록 하죠.
$password = "sup3r53cr3t!";
$options = [
"salt" => mcrypt_create_iv(22, MCRYPT_DEV_URANDOM)
];
$hash = password_hash($password, PASSWORD_DEFAULT, $options);
위의 코드에서 우리는 유저의 $password를 받은 뒤 기본적인 해싱 함수(현재는 내부적으로 bcrypt가 쓰이지만, 향후 PHP 버전에서는 변경될 수 있습니다)를 호출하고 그 결과를 $hash에 저장하는 것을 볼 수 있습니다. 여기서 옵션으로 salt 값을 조정해주었지만, 이 작업을 해주지 않아도 함수에서 내부적으로 기본적인 값은 모두 적용이 되니 걱정하지 않아도 됩니다. 아무쪼록 이러한 옵션들이 있다는 것을 알아두도록 합시다.
위의 password_hash() 함수 호출의 결과의 예는 다음과 같습니다.
$2y$11$q5MkhSBtlsJcNEVsYh64a.aCluzHnGog7TQAKVmQwO9C8xb.t89F.
제일 앞에 $2y$ 식별자는 이 해시 값이 Blowfish 기반임을 알려줍니다. 아까 전에 crypt의 특징으로 1번의 해싱이 아닌 여러 번 반복하여 최종 해시 값을 얻어냈다고도 했었죠?
이 특징은 해싱의 "cost"로 불리며 password_hash 함수에서 이 값을 지정할 수 있도록 되어 있습니다. 이 값을 높일 수록 더 많은 반복 해싱이 일어나 연산량이 늘어날 수 있으니 이 값을 조절하는데 신경을 써야 합니다. 아래의 예제를 통해 이 "cost"를 세팅하는 법을 알아보도록 합시다.
12];
$hash = password_hash($password);
?>
위 예제에서는 cost를 12까지 올렸고, 다시 말하면 최종 값을 얻기 전에 12회의 해싱을 반복 수행함을 의미합니다. password_hash는 기본 값으로 cost를 8로 지정하고 있으며, 대부분의 애플리케이션에서 오버헤드와 안전성을 모두 고려한 적절한 값으로 여겨집니다(눈썰미가 좋은 독자 분들이라면 아셨겠지만, 앞선 예제에서 구한 해시 값 앞 부분의 $2y$ 뒤에 놓인 $11$가 cost임을 짐작하셨을 겁니다).
PHP 5.5.0 상위 버전에서는 이외에 주어진 해시 값을 쉽고 빠르게 검증해주는 password_verify 함수를 제공합니다. 간단히 유저가 인증을 위해 입력한 비밀번호와 해당 유저의 비밀번호 해시 값을 비교하여 true 혹은 false 값을 리턴해줍니다.
완벽한 비밀번호는 없다
다른 분야에서도 그렇듯이, "완벽한" 암호체계라는 것은 존재하지 않습니다. 위에서 줄곧 설명한 해싱도 별반 다를 바가 없지요. 아무리 솔트와 해싱을 잘 세팅하여도 이는 단지 공격자로부터 견디는 시간을 늘려줄 뿐, 무수히 많은 시간이 주어진다면 언젠가 암호는 깨어지고 말겁니다. 때문에 우리는 비밀번호 자체가 아닌 비밀번호와 관련된 시스템에 대해 이야기하는 시점에 다다랐습니다. 이를 테면 비밀번호를 일정 주기로 변경하거나, 오래된 것은 파기하는 규정 같은 것 말이죠. 이러한 부가적인 요소들을 잘 이용한다면 유저들은 주기적으로 자신의 비밀번호를 변경하게 되어 각종 해킹이나 유출에 대해 좀 더 대비할 수 있습니다. 위에서 설명한 bcrypt와 같은 함수의 사용은 크래킹 프로세스를 늦추고, 우리가 원하는 결과대로라면 유저가 비밀번호를 바꾸는 주기보다 역 해싱이 더 오래 걸리도록 막아줄 것 입니다. 오프라인 해시 DB를 갖추고 있는 공격자를 만나더라도 당신의 비밀번호를 여기서 빠져나갈 수 있을 겁니다.
비밀번호는 단지 전체 보안 체계의 절반에 지나지 않음을 기억하세요. 여전히 시스템 관리자는 유저들을 보호해야 할 의무가 있습니다. 다양한 단순 문자열 비밀번호 외의 규정들을 통해 시스템은 더욱 안전해질 수 있고, two-factor 인증 등은 이를 한층 더 안전하게 해줄 겁니다.