인증은 애플리케이션의 문을 지키는 보안 요원과 같습니다. 누군가 회원가입을 하거나 로그인할 때마다, 시스템은 그 사용자가 주장하는 본인이 맞는지 판단해야 합니다. 만약 이를 잘못 처리하면, 단순히 로그인 실패를 디버깅하는 문제로 끝나지 않습니다. 실제 사용자 데이터가 그대로 유출될 위험을 초래하게 됩니다. 이를 제대로 수행하려면 두 가지 신뢰할 수 있는 도구가 필요합니다. 저장된 비밀번호를 보호하기 위한 Bcrypt와, 사용자가 앱 내에서 이동하는 동안 신원을 확인하기 위한 JSON Web Token입니다.
Plaintext 저장 방식이 실패하는 이유
이 글에서 단 하나의 규칙만 기억해야 한다면, 바로 이것입니다: 데이터베이스에 비밀번호를 평문(plaintext)으로 저장하지 마십시오. 데이터베이스가 방화벽 뒤에 있든, 팀의 모든 엔지니어를 신뢰하든 상관없습니다. 비밀번호를 가공되지 않은 형태로 테이블에 기록하는 것은 현관 매트 아래에 집 열쇠를 두는 것과 같습니다. 잘못 설정된 API, 유출된 백업, 또는 인젝션 공격을 통해 누군가 해당 데이터베이스에 접근하는 순간, 모든 인증 정보가 즉시 노출됩니다.
사람들은 비밀번호를 재사용하기 때문에 피해는 배가 됩니다. 단 한 번의 침해로 귀하의 애플리케이션뿐만 아니라 사용자의 이메일, 은행, 소셜 미디어 계정까지 위험에 처할 수 있습니다. 이것이 우리가 비밀번호를 해싱(hashing)하는 이유입니다. 해싱은 비밀번호를 원래의 형태와 전혀 닮지 않은 뒤섞인 문자열로 변환합니다. 이 과정은 단방향이며 되돌릴 수 없습니다. 해시에 수학적 산(acid)을 부어 원래의 비밀번호로 다시 녹여낼 수는 없습니다.
Bcrypt를 이용한 비밀번호 해싱
Bcrypt는 비밀번호를 위해 특별히 설계된 해싱 함수입니다. 평문 문자열을 입력받아 Blowfish 암호화 알고리즘을 거쳐 $2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy와 같은 결과를 반환합니다. 접두사(prefix)는 알고리즘과 비용 인자(cost factor)를 나타냅니다. 뒤에 이어지는 긴 문자열은 솔트(salt)와 해시 값의 조합입니다.
솔트는 해싱을 시작하기 전에 비밀번호에 섞이는 무작위 데이터입니다. 솔트는 사용자마다 고유하기 때문에, 두 사람이 모두 "password123"을 선택하더라도 데이터베이스에는 완전히 다른 해시 값이 저장됩니다. 이러한 사소한 차이는 레인보우 테이블(rainbow tables, 흔히 쓰이는 비밀번호에 대해 미리 계산해 둔 해시 사전)을 무력화합니다. 공격자가 모든 고유한 솔트에 대해 테이블 전체를 다시 생성해야 하기 때문입니다.
Bcrypt는 의도적으로 느리게 설계되었습니다. 최신 하드웨어는 SHA-256과 같은 빠른 알고리즘을 사용하면 초당 수십억 개의 해시를 추측할 수 있습니다. 하지만 Bcrypt는 일부러 속도를 늦춥니다. 컴퓨터가 빨라짐에 따라 높일 수 있는 비용 인자에 의해 제어되며, 해싱 과정을 여러 번 반복합니다. 이러한 느린 속도는 탈취된 데이터베이스를 무차별 대입 공격(brute-force)으로 뚫으려는 시도에 큰 제약을 줍니다. 로그인 시 200밀리초를 더 기다리는 정당한 사용자는 이를 눈치채지 못하겠지만, 수백만 번의 추측을 시도하는 공격자는 분명히 체감하게 될 것입니다.
JSON Web Token의 작동 원리
Bcrypt가 정문을 담당한다면, JWT는 복도 통행증을 담당합니다. JSON Web Token은 사용자가 이미 인증되었음을 증명하는 간결하고 URL 안전한(URL-safe) 문자열입니다. 서버가 발급하고 클라이언트가 소지하고 다니는 디지털 신분증이라고 생각하면 됩니다.
JWT는 점(.)으로 구분된 세 가지 세그먼트, 즉 헤더(header), 페이로드(payload), 서명(signature)으로 구성됩니다. 헤더는 토큰 유형과 서명 알고리즘을 지정합니다. 페이로드는 사용자 ID, 사용자 이름, 만료 타임스탬프와 같이 사용자 및 토큰 자체에 대한 정보인 클레임(claims)을 담고 있습니다. 서명은 암호화된 인장입니다. 서버는 헤더와 페이로드를 인코딩한 후 비밀 키(secret key)를 통해 처리하여 이를 생성합니다. 누군가 페이로드를 조작하면 서명이 더 이상 일치하지 않게 되며, 서버는 해당 토큰을 즉시 거부합니다.
