한 기관 서비스의 이용자가 고객센터에 전화를 걸었습니다. 비밀번호가 기억나지 않으니 기존에 쓰던 값을 알려달라는 요청이었습니다. 이름과 사번을 확인하면 고객센터가 화면에서 비밀번호를 찾아 읽어줄 수 있을 거라고 생각했습니다.
담당자는 기존 비밀번호를 알려줄 수 없다고 답했습니다. 대신 등록된 이메일로 재설정 안내를 보내겠다고 했습니다. 이용자는 답답해했습니다. “서비스가 제 비밀번호를 가지고 있으니까 맞는지 확인하는 것 아닌가요? 가지고 있는데 왜 알려줄 수 없죠?”
이 질문에는 비밀번호 저장의 핵심이 들어 있습니다. 안전하게 설계된 서비스는 이용자가 입력했던 비밀번호 원문을 꺼내 읽는 방식으로 로그인 여부를 판단하지 않습니다. 원문을 되찾지 않고도 같은 비밀번호가 제출됐는지 비교할 수 있는 값을 저장합니다.
비밀번호를 그대로 보관하면
가장 단순한 방법은 회원가입 때 입력한 비밀번호를 데이터베이스에 그대로 저장하는 것입니다. 로그인할 때 새로 입력된 값과 저장된 문자를 비교하면 됩니다. 운영자가 비밀번호를 알려달라는 요청을 받았을 때도 기록을 열어 읽어주면 그만입니다.
단순한 만큼 위험은 큽니다. 데이터베이스가 유출되거나, 비밀번호를 볼 수 있는 내부 계정이 악용되거나, 관리 화면과 기록에 값이 잘못 노출되면 이용자의 비밀이 그대로 드러납니다. 더 큰 문제는 사람들이 같은 비밀번호를 여러 서비스에서 반복한다는 데 있습니다. 한 서비스에서 유출된 값이 이메일, 쇼핑몰, 업무 시스템 같은 다른 계정의 문까지 여는 열쇠가 될 수 있습니다.
비밀번호를 가역적으로 암호화해 보관하는 방법도 생각할 수 있습니다. 암호화는 정해진 키를 이용해 원문을 알아보기 어려운 형태로 바꾸고, 필요한 경우 다시 복원하는 기술입니다. 주소나 전화번호처럼 서비스가 나중에 원문을 다시 보여주거나 처리해야 하는 정보에는 암호화가 필요할 수 있습니다.
비밀번호는 목적이 다릅니다. 서비스는 로그인할 때 기존 비밀번호를 다시 읽어 사용자에게 보여줄 필요가 없습니다. 새로 제출된 값이 등록할 때의 값과 같은지만 판단하면 됩니다. 복원할 필요가 없는 비밀을 복원 가능한 형태로 저장하면, 데이터와 함께 복호화 키가 노출될 때 원문까지 한꺼번에 드러날 위험이 생깁니다. 그래서 일반적인 로그인용 비밀번호는 가역 암호화보다 비밀번호 검증에 맞는 해시 방식으로 보호하는 것이 권장됩니다.
원문 대신 남기는 흔적
해시는 입력값을 일정한 규칙으로 처리해 결과값을 만드는 방식입니다. 같은 입력을 같은 조건으로 처리하면 같은 결과가 나옵니다. 반면 결과만 보고 원래 입력을 되돌리는 일을 전제로 설계하지 않습니다.
회원가입 때 이용자가 비밀번호를 만들면 서버는 원문을 그대로 보관하는 대신 적절한 비밀번호 해시 알고리즘으로 처리한 값을 저장합니다. 이후 로그인 요청이 오면 새로 제출된 비밀번호를 같은 방식으로 처리합니다. 두 결과가 같으면 같은 값이 제출됐다고 판단합니다.
이 흐름에서 서버가 하는 일은 보관해둔 비밀번호를 꺼내 읽는 것이 아닙니다. 지금 받은 비밀번호가 이전에 받은 값과 같은 흔적을 만드는지 확인하는 것입니다. 고객센터가 기존 비밀번호를 볼 수 없고 재설정만 안내하는 이유도 여기에 있습니다. 운영자 화면에 비밀번호 원문이 표시된다면 편리한 기능이라기보다 저장 구조를 다시 의심해야 할 신호에 가깝습니다.
해시를 종이 분쇄기에 비유하기도 합니다. 종이를 잘게 자른 뒤 원래 문서를 그대로 되살리기 어렵다는 점에서는 도움이 되는 비유입니다. 하지만 해시는 종이를 무작위로 파괴하는 일과 다릅니다. 같은 입력은 같은 조건에서 같은 결과를 만듭니다. 공격자는 후보 비밀번호를 하나씩 처리해 유출된 결과와 같은지 대조할 수 있습니다.
따라서 “해시는 되돌릴 수 없으니 유출돼도 안전하다”는 결론은 맞지 않습니다. 원문을 직접 복원하는 대신, 사람들이 선택했을 법한 값을 계속 시험해 정답을 찾을 수 있기 때문입니다. 짧고 흔한 비밀번호라면 후보가 많지 않습니다. 공격자는 자주 쓰이는 단어, 이전 침해에서 발견된 비밀번호, 숫자와 기호를 붙인 익숙한 변형부터 확인할 수 있습니다.
같은 비밀번호도 다르게 보이게
여기서 솔트가 등장합니다. 솔트는 비밀번호를 해시할 때 계정마다 고유하게 더하는 값입니다. 솔트 자체를 비밀로 숨기는 것이 목적은 아닙니다. 같은 비밀번호를 사용한 두 계정의 저장 결과가 똑같아지는 일을 막는 데 의미가 있습니다.
가령 두 이용자가 우연히 같은 비밀번호를 선택했다고 해봅시다. 비밀번호만 그대로 해시하면 저장 결과도 같아질 수 있습니다. 데이터베이스를 얻은 공격자는 결과가 같은 계정끼리 같은 비밀번호를 사용한다고 짐작할 수 있습니다. 자주 쓰이는 비밀번호의 결과를 미리 대량으로 계산해둔 자료도 여러 계정에 반복해서 적용하기 쉬워집니다.
계정마다 다른 솔트를 함께 처리하면 같은 비밀번호라도 결과가 달라집니다. 공격자는 한 번 계산한 결과를 모든 계정에 그대로 대입하기 어려워집니다. 그렇다고 솔트가 비밀번호를 강하게 바꾸거나 모든 추측을 막는 것은 아닙니다. 솔트와 저장된 결과를 함께 얻은 공격자는 각 계정에 대해 후보 비밀번호를 다시 시험할 수 있습니다. 솔트는 그 작업을 계정별로 반복하게 만들 뿐, 약한 비밀번호를 강한 비밀번호로 바꾸지는 않습니다.
‘소금 한 꼬집을 더한다’는 이름 때문에 조리법처럼 받아들이기 쉽지만, 실제 의미는 계정마다 다른 조건을 부여하는 데 있습니다. 솔트는 비밀키도 아니고, 이용자가 기억해야 하는 두 번째 비밀번호도 아닙니다. 서비스가 안전하게 생성하고 비밀번호의 검증값과 함께 관리하는 값입니다.
빠른 해시가 항상 좋은 것은 아니다
컴퓨터 작업은 대개 빠를수록 좋다고 생각합니다. 비밀번호 저장에서는 조금 다릅니다. 해시 계산이 지나치게 빠르면 정상 사용자는 로그인할 때 편할 수 있지만, 유출된 데이터를 가진 공격자도 매우 많은 후보를 짧은 시간에 시험할 수 있습니다.
그래서 비밀번호에는 일반적인 빠른 해시를 한 번 적용하는 것보다, 비밀번호 저장을 위해 설계된 느리고 비용이 드는 방식을 사용합니다. 정상적인 로그인에서는 한 번의 검증에 약간의 계산이 추가되는 정도입니다. 공격자가 수백만, 수억 개의 후보를 시험할 때는 그 작은 비용이 계속 누적됩니다. 현대적인 비밀번호 해시 방식은 계산 횟수뿐 아니라 메모리 사용량까지 늘려 대량 추측의 비용을 높이기도 합니다.
어떤 알고리즘과 설정이 적절한지는 기술 환경과 시점에 따라 달라집니다. 이 책에서 특정 이름과 수치를 외울 필요는 없습니다. 핵심은 ‘해시했다’는 한마디만으로 저장이 안전해지는 것이 아니라는 점입니다. 계정별 솔트가 있어야 하고, 대량 추측을 어렵게 만드는 적절한 비밀번호 해시 방식과 설정이 필요합니다. 오래된 시스템은 당시의 기준에 맞춰 만든 저장값을 계속 가지고 있을 수 있으므로, 로그인에 성공하는 순간 더 나은 방식으로 갱신하는 전략도 사용합니다.
처음 비밀번호를 어떻게 전달할까
기관용 서비스에서는 이용자가 직접 가입하지 않고 기관이 계정을 미리 만드는 경우가 있습니다. 이때 최초 비밀번호를 명단 파일에 함께 적어 전달하거나, 모든 이용자에게 같은 임시 비밀번호를 부여하면 운영은 간단해집니다. 하지만 명단이 여러 담당자와 업체를 거치는 동안 비밀번호도 함께 퍼집니다. 이용자가 값을 바꾸지 않으면 임시 비밀번호가 오랫동안 실제 비밀번호로 남을 수 있습니다.
더 나은 방향은 계정 생성과 비밀번호 설정을 분리하는 것입니다. 기관은 어느 이용자의 계정을 만들지 전달하고, 이용자는 자신에게 도착한 일회성 안내를 통해 직접 비밀번호를 정합니다. 이때 메일이나 문자가 비밀번호 원문을 담는 것이 아니라, 일정 시간 동안 설정 화면에 들어갈 수 있는 재설정용 증표를 전달하도록 설계할 수 있습니다.
운영 현장에서는 여기서도 선택이 생깁니다. 이메일 주소가 잘못 등록됐거나 이용자가 더 이상 그 주소에 접근할 수 없다면 안내를 받을 수 없습니다. 재설정 메일이 전달되는 경로가 안전한지, 증표의 유효기간이 적절한지, 한 번 사용한 뒤 폐기되는지도 중요합니다. 다만 이는 기존 비밀번호를 저장하는 문제와는 다른 보안 경계입니다. 서버가 비밀번호 원문을 가지고 있지 않아도, 복구 절차가 약하면 다른 사람이 새 비밀번호를 설정할 수 있습니다. 계정 복구의 구조는 7장에서 다시 살펴보겠습니다.
서버가 모른다고 끝나는 것은 아니다
비밀번호 원문을 저장하지 않는 구조는 피해를 줄이기 위한 기본 조건입니다. 그렇다고 서비스가 비밀번호와 관련해 아무 책임도 지지 않는다는 뜻은 아닙니다. 로그인할 때 비밀번호는 서버에 제출되어 검증 과정을 거칩니다. 전송 구간이 보호되어야 하고, 프로그램 기록이나 오류 메시지에 원문이 남지 않도록 해야 합니다. 개발과 운영 과정에서 비밀번호를 함부로 수집하거나 복사하지 않는 절차도 필요합니다.
이용자도 해시의 존재만 믿을 수는 없습니다. 저장값이 유출되면 공격자는 후보를 대조할 수 있습니다. 비밀번호가 짧고 흔할수록 찾아낼 가능성이 커집니다. 다른 서비스에 같은 값을 사용했다면 한 곳의 저장 방식이 무너졌을 때 피해가 번집니다.
좋은 저장 방식은 비밀번호를 절대로 잃지 않는 마법이 아닙니다. 서비스가 침해되더라도 공격자가 원문을 바로 손에 넣지 못하게 하고, 후보를 시험하는 비용을 높여 대응할 시간을 벌어주는 장치입니다. 비밀번호 자체의 선택과 사용 습관이 중요한 이유도 여기에 있습니다.
안전한 서비스는 비밀번호 원문 대신, 같은 값이 제출됐는지 확인할 수 있는 느리고 고유한 흔적을 저장한다.


