비밀번호 저장과 KDF: Salt·Argon2id·PBKDF2의 역할

1장 개념과 보호 목표#

비밀번호 저장은 복호화 가능한 암호화보다 비밀번호 전용 해시·키 유도 함수로 검증값을 만드는 것이 일반적입니다. 유출 후 대입 공격의 비용을 높이기 위해 Salt와 메모리·연산 비용을 함께 설정합니다.


2장 원리와 구분#

Salt는 사용자별로 생성하는 공개 가능한 값으로 같은 비밀번호의 해시가 같아지는 것을 방지합니다. Pepper는 별도 비밀 저장소에 보관하는 추가 비밀값입니다. Argon2id, scrypt, bcrypt, PBKDF2는 서로 다른 비용 모델을 가지므로 서버의 메모리·응답시간과 운영 제약에 맞춰 선택합니다. HKDF는 고엔트로피 비밀에서 용도별 키를 유도하는 도구이며 비밀번호 대입 공격을 느리게 만드는 함수와 구분합니다.


3장 비교와 판단 기준#

요소 역할 운영 조건
Salt 동일 비밀번호의 저장값 구분 사용자별 생성·검증값과 보관
비용 인자 공격 대입 비용 증가 실제 장비·동시 요청 측정
Pepper 별도 비밀 추가 분리 저장·유출 시 교체 계획
알고리즘 정보 추후 재해시 지원 버전·파라미터와 함께 보관

4장 데이터베이스 유출 뒤 공격자는 오프라인으로 추측한다#

로그인 API의 요청 제한은 서버에 접속하는 온라인 공격을 줄입니다. 그러나 검증값을 가져간 공격자는 자신의 장비에서 후보 비밀번호를 계산할 수 있어 서비스의 로그인 제한을 거치지 않습니다. 비밀번호 저장 방식은 이런 오프라인 추측의 비용을 높이는 요구를 별도로 갖습니다.

사용자가 만든 비밀번호의 엔트로피는 무작위 암호키와 다릅니다. 흔한 문구·재사용·개인정보 조합이 후보를 줄일 수 있습니다. 강한 해시 이름만 선택해 빠르게 한 번 계산하는 것으로 이 차이를 해결할 수 없습니다.


5장 복호화 가능한 저장과 검증값 저장의 차이#

로그인 검증에는 원래 비밀번호를 다시 읽을 필요가 없습니다. 입력한 후보에 동일한 검증 절차를 적용해 저장된 값과 비교합니다. 따라서 원문 비밀번호를 데이터베이스나 로그에 남기거나 복호화 가능한 암호문으로 보관하는 설계를 피합니다.

flowchart LR
 P["입력 비밀번호"] --> K["전용 KDF"]
 S["계정의 Salt와 비용 설정"] --> K
 K --> C["저장 검증값과 비교"]
 C -->|"일치"| A["다음 인증 단계"]
 C -->|"불일치"| F["실패 처리와 제한"]

6장 Salt는 같은 비밀번호의 결과를 다르게 만든다#

같은 비밀번호를 여러 계정이 사용해도 각 계정의 Salt가 다르면 검증값이 다르게 계산됩니다. 공격자가 한 번의 후보 계산으로 같은 비밀번호의 모든 계정을 확인하기 어렵게 하고 미리 계산한 표를 재사용하는 효과를 줄입니다. Salt는 일반적으로 검증값과 함께 저장할 수 있습니다.

Salt가 있다는 사실이 약한 비밀번호를 강한 무작위 비밀로 바꾸는 것은 아닙니다. 각 계정에 대한 후보 추측은 남습니다. Salt의 안전한 생성과 충분한 크기, 전용 KDF의 비용을 함께 적용해야 합니다.


7장 Pepper는 별도 비밀이며 교체가 쉽지만은 않다#

Pepper를 추가 비밀로 사용하는 경우 데이터베이스와 분리한 보호 위치와 접근 권한을 둡니다. 검증 서버의 침해까지 막는 만능 수단은 아니며 서버가 정상적으로 사용할 수 있는 비밀은 권한 탈취로 악용될 수 있습니다. 백업·장애 복구 경로도 포함합니다.

Pepper가 노출되었을 때 기존 계정의 검증값을 어떻게 전환할지 미리 정합니다. 평문 비밀번호를 갖고 있지 않기 때문에 구성에 따라 로그인 시 전환 또는 재설정이 필요할 수 있습니다. Salt의 공개성과 Pepper의 비밀성을 혼동하지 않습니다.


8장 Argon2id의 비용을 숫자 하나로 읽지 않는다#

메모리 사용량·반복 횟수·병렬성은 서로 다른 설정입니다. 특정 시간만 맞춰도 메모리 비용이 적절한지와 동시 요청 때 전체 자원이 감당되는지를 봐야 합니다. 개발 노트북의 단일 로그인 시험을 운영 서버의 동시 로그인 결과로 대신할 수 없습니다.

비용 설정은 현재 권고와 하드웨어, 피크 부하를 기준으로 선택하고 주기적으로 재검토합니다. 사용 라이브러리의 단위와 인코딩 형식을 확인해야 메모리 값을 바이트·킬로바이트·메비바이트로 혼동하는 실수를 피할 수 있습니다.


9장 PBKDF2와 일반 KDF를 구분한다#

PBKDF2는 비밀번호를 이용하는 반복 기반 키 유도에 사용됩니다. HKDF처럼 고엔트로피 비밀의 용도별 키를 유도하는 방식과 비밀번호 추측 비용의 요구를 동일하게 취급하지 않습니다. 입력 비밀의 특성과 목적을 먼저 확인해야 합니다.

규정·검증 요구·환경 때문에 선택이 달라질 수 있습니다. 알고리즘 이름과 반복 설정, 사용하는 해시·구현·파라미터를 함께 기록하고 어떤 요구를 충족하기 위해 선택했는지 남깁니다.


10장 검증값에 버전과 비용을 함께 저장한다#

알고리즘 이름 / 형식 버전
메모리·반복·병렬성 등의 비용
계정별 Salt
계산된 검증값

형식을 명확히 하면 기존 계정이 어느 정책으로 저장되었는지와 전환 대상인지 알 수 있습니다. 입력이 임의로 매우 큰 비용을 요청하거나 미지원 알고리즘을 선택하도록 두지 말고 서버가 허용한 상한과 형식을 검증합니다.


11장 로그인 성공 시 재해시하는 흐름#

먼저 기존 형식으로 후보를 검증합니다. 성공하면 현재 정책보다 오래된 비용·알고리즘인지 확인하고 입력 비밀번호로 새 검증값을 만들어 갱신합니다. 로그인 검증에 실패한 후보로 저장값을 바꾸면 안 됩니다. 갱신의 경쟁 조건과 DB 실패도 처리해야 합니다.

flowchart TB
 A["기존 검증값 읽기"] --> B["후보 비밀번호 검증"]
 B --> C{"성공했는가?"}
 C -->|"아니오"| F["실패 처리"]
 C -->|"예"| D{"현행 정책보다 오래된가?"}
 D -->|"예"| E["새 Salt와 비용으로 재해시"]
 D -->|"아니오"| L["로그인 진행"]
 E --> L

12장 비용 강화가 서비스 거부를 만들지 않게 한다#

비밀번호 검증은 의도적으로 자원을 사용합니다. 공격자가 많은 요청을 보내면 CPU·메모리·작업 큐가 병목이 될 수 있습니다. 계정·출발지·요청 맥락의 제한, 전체 동시성 상한과 과부하 때의 동작을 설계해야 합니다.

정상 로그인 지연·피크 메모리·실패 요청의 비용·복구 작업을 측정합니다. 사용자 존재 여부나 실패 원인을 과도하게 노출하지 않으면서 정상 사용자가 잠금·분실 상황에서 복구할 수 있게 합니다.


13장 긴 비밀번호와 문자 처리를 시험한다#

인코딩·최대 입력 길이·정규화·드라이버의 제한을 확인합니다. 사용자의 입력을 조용히 잘라 버리면 서로 다른 긴 비밀번호가 같은 값으로 처리될 수 있습니다. 필요한 제한은 명시하고 설정·재설정·로그인의 규칙을 일치시킵니다.

비밀번호와 Pepper가 오류 로그·추적 데이터·분석 도구에 남지 않는지도 시험합니다. KDF가 안전해도 평문을 다른 경로에서 수집하면 저장 보호의 효과를 잃을 수 있습니다.


14장 적용 사례와 운영 방법#

로그인 시 저장된 알고리즘·파라미터로 먼저 검증한 뒤 현재 정책에 미달하면 새 Salt와 비용 인자로 재해시합니다. 원문 비밀번호를 임시 저장해 일괄 전환하지 않습니다. bcrypt를 사용하는 시스템은 입력 길이 제한과 라이브러리 동작을 확인해야 합니다.


15장 비밀번호 KDF는 느리게, 일반 키 유도는 목적에 맞게#

사람이 만든 비밀번호는 가능한 후보가 적을 수 있어 빠른 해시만 쓰면 대량 추측에 취약합니다. 비밀번호 전용 KDF는 시간과 메모리 비용을 부여해 공격자의 후보 검사를 비싸게 만듭니다. Argon2id의 메모리·반복·병렬성, PBKDF2의 반복 횟수는 서버의 동시 로그인과 서비스 거부 가능성까지 측정해 정합니다.

Salt는 비밀번호별로 다른 결과를 만들어 사전 계산과 동일 비밀번호의 비교를 어렵게 합니다. Pepper는 추가 비밀로 사용할 수 있으며 데이터베이스와 분리해 보관합니다. Pepper의 유출·교체 때 기존 검증값을 어떻게 바꿀지도 계획해야 하고 Salt를 비밀로 숨기는 것으로 비용 설정을 대신해서는 안 됩니다.


16장 기존 회원의 검증값을 단계적으로 전환하기#

검증값에는 알고리즘·버전·비용·Salt 정보를 남깁니다. 기존 방식으로 로그인 검증에 성공했을 때 현재 정책으로 다시 계산해 갱신하면 평문 비밀번호를 저장하지 않고 전환할 수 있습니다. 장기간 접속하지 않는 계정은 별도의 비밀번호 재설정 정책을 검토합니다.

로그인 속도 제한은 온라인 추측을, 전용 KDF는 검증값 유출 후 오프라인 추측을 줄이는 통제입니다. 두 상황을 구분해야 합니다. 테스트에서는 최대 동시 요청의 지연·메모리와 비용 상한을 확인하고 평문 비밀번호나 Pepper가 로그에 남지 않는지도 점검합니다.


17장 적용 전 확인할 사항#

  • 사용자별 Salt와 알고리즘·비용 정보를 저장한다
  • 비용 인자를 실제 장비에서 부하 측정한다
  • 로그인 시 재해시와 실패 요청 제한을 구현한다

18장 자주 묻는 질문#

반복 횟수를 최대한 높이면 좋은가요?#

공격 비용과 서비스 가용성을 함께 고려해야 합니다. 정상 로그인과 동시 요청을 측정하고 대입 공격 제한을 병행해야 서버 자원 고갈을 줄일 수 있습니다.

Salt가 같으면 무슨 문제가 생기나요?#

동일 비밀번호가 동일 검증값으로 보일 수 있고 공격자가 한 번의 계산을 여러 계정에 적용하기 쉬워집니다. 계정별 안전한 Salt 생성과 저장 형식을 사용해야 합니다.

KDF를 느리게 하면 로그인 제한은 필요 없나요?#

온라인 공격의 요청 수와 서비스 자원 소모는 별도 문제입니다. 계정·요청 맥락별 제한과 탐지를 함께 적용하고 정상 이용자의 복구 절차도 준비해야 합니다.


함께 읽기#