비밀번호 저장의 목표는 원문을 복원하는 것이 아니라, 유출된 검증값만 가진 공격자가 후보를 대량으로 시험하는 비용을 높이는 것입니다. salt는 계정별 결과를 분리하고, 시간·메모리 비용은 후보 계산을 늦추며, pepper는 별도 비밀 경계를 추가합니다. 이 요소를 인증 서버의 정상 지연·메모리 예산·정책 전환과 함께 설계해야 합니다.
DB 유출 이후 후보 검증 비용
로그인 서버는 사용자가 입력한 비밀번호가 맞는지만 알면 됩니다. 원래 비밀번호를 다시 보여 줄 필요가 없으므로 복호화 가능한 암호화로 저장할 이유가 대개 없습니다. 하지만 “해시는 역산하기 어렵다”는 설명도 충분하지 않습니다. 공격자는 역산 대신 흔한 비밀번호 후보를 해시해 저장값과 비교할 수 있기 때문입니다.
비밀번호 전용 해시는 후보 하나를 계산하는 데 시간·메모리를 쓰게 만들어 유출 DB에 대한 대량 대입 비용을 높입니다. 온라인 API의 rate limit은 오프라인 대입에 적용되지 않습니다. 둘은 서로 다른 경계를 보호합니다.
Salt와 후보 단위 계산 비용의 역할 차이
상태를 예로 들면 두 계정이 같은 password123을 선택해도 서로 다른 무작위 salt를 사용하면 저장 문자열이 달라지고, 각 계정에 대해 공격자는 여전히 후보 계산을 해야 합니다. 예상 결과는 두 값 모두 올바르게 검증되지만 어느 한 저장값을 사전 계산한 결과로 다른 계정에 재사용할 수 없는 것입니다. 이것은 약한 비밀번호 자체를 강하게 바꾸는 기능은 아닙니다.
| 구성 | 역할 | 해결하지 못하는 것 |
|---|---|---|
| 사용자별 무작위 salt | 같은 비밀번호의 결과 분리, 사전 계산 재사용 감소 | 특정 계정에 대한 빠른 후보 대입 |
| 반복·시간 비용 | 후보 하나의 계산량 증가 | 약한 비밀번호의 모든 추측 방지 |
| 메모리 비용 | 대량 병렬 계산의 메모리 부담 증가 | 온라인 인증 폭주 자체 |
| pepper | DB와 분리된 추가 비밀 | 키 유출·분실·교체 운영 |
salt는 해시와 함께 저장해도 됩니다. 검증할 때 같은 salt와 파라미터로 입력 후보를 계산해야 하기 때문입니다. pepper는 비밀이므로 일반 salt처럼 같은 DB에 무심코 저장하지 않습니다. pepper를 도입하면 검증 서버의 비밀 저장소 의존성과 키 교체·분실 때의 재설정 정책이 생깁니다.
비밀번호 해시 알고리즘과 자원 계약
일반 SHA-256(password)나 SHA-256(salt + password)는 빠른 계산을 목표로 한 해시라서, 비밀번호 후보 하나를 일부러 비싸게 만드는 용도에 맞지 않습니다. PBKDF2-HMAC-SHA-256은 HMAC-SHA-256을 내부에 사용하는 구성으로, 반복 횟수로 계산 비용을 조정합니다. Argon2id와 scrypt는 시간·메모리 비용을 조정하는 방식이고, bcrypt는 work factor로 비용을 조정하지만 메모리 크기를 같은 방식으로 독립 조정하는 알고리즘은 아닙니다.
직접 조합을 만들기보다 유지보수되는 비밀번호 검증 라이브러리와 현재 보안 지침을 따릅니다. 규정 준수는 알고리즘 이름만으로 판정하지 않고 검증된 모듈·설정·운영 환경의 요구를 확인합니다. bcrypt를 포함해 구현의 입력 바이트 길이·잘림·인코딩 규칙도 확인해야 합니다. 긴 비밀번호 두 개가 잘림 때문에 같은 입력으로 처리되면 사용자가 생각하는 비밀번호 계약과 달라집니다.
정상 로그인 예산과 해시 비용
예를 들어 인증 동시 실행이 40개이고 작업 하나가 64 MiB를 사용하면, 해시 작업에 필요한 메모리는 40 × 64 MiB = 2,560 MiB, 즉 약 2.5 GiB입니다. 이는 권장 파라미터가 아니라 동시 실행 수와 메모리 비용을 곱해 보는 산술 예시입니다. 실제 서버에서는 목표 지연과 피크 동시성을 측정한 뒤 인증 worker·대기 큐·요청률을 함께 제한하고, 비용을 최대로 올리는 것만으로 보안이 좋아지는 것은 아니므로 정상 로그인 예산도 지킵니다.
존재하지 않는 사용자와 잘못된 비밀번호에 같은 외부 오류를 반환하고 처리 경로의 큰 시간 차이를 줄입니다. 없는 계정에도 적절한 더미 검증을 적용할 수 있지만 이것이 무제한 비싼 해시 요청을 허용하는 이유는 아닙니다. 입력 크기 제한과 남용 방어를 함께 둡니다. 비교 함수 하나가 constant-time이어도 전체 네트워크 응답이 일정하다고 주장하지 않습니다.
그림을 글로 읽기
- 저장된 형식 읽기: 알고리즘·비용·salt
- 기존 정책으로 후보 검증
- 성공하고 정책이 낡았음: 새 salt로 재해시
- 기대 버전 조건으로 저장
- 저장된 형식 읽기 → 기존 정책으로 후보 검증: 검증 방식 선택
- 기존 정책으로 후보 검증 → 성공하고 정책이 낡았음: 원문 후보가 맞음
- 성공하고 정책이 낡았음 → 기대 버전 조건으로 저장: 변경 경쟁 확인
재해시와 동시 비밀번호 변경 충돌
알고리즘과 비용은 정상 로그인 지연·피크 동시성·메모리 상한을 기준으로 선택하고, 비용을 높인 뒤 인증 worker가 밀려 장애가 나지 않는지 확인합니다. 전환 장애에서는 저장 문자열의 알고리즘·파라미터·정책 version, credential_version 조건 결과, 재설정 필요 상태를 함께 조회합니다. 새 정책으로 바뀐 계정 수만 늘고 동시 변경에서 옛 비밀번호가 복귀했다면 재해시 조건부 저장이 빠진 것입니다.
정상 로그인이 구 해시 검증을 끝낸 사이 사용자가 비밀번호를 변경할 수 있습니다. 로그인 worker가 구 입력을 새 정책으로 계산해 무조건 저장하면 변경된 비밀번호를 옛 비밀번호로 되돌립니다. UPDATE ... WHERE credential_version = expected 같은 조건으로 아직 같은 자격인지 확인하고, 실패하면 재해시 저장을 건너뛰거나 현재 상태를 다시 평가합니다.
해시 저장 형식에는 알고리즘·파라미터·salt·정책 버전을 식별할 정보가 필요합니다. 비밀번호 원문을 로그·예외·분석 이벤트로 내보내지 않고 요청 수명이 끝나면 불필요한 참조를 남기지 않습니다. 메모리에서 완전한 소거를 보장할 수 있는지는 언어 런타임의 한계도 구분합니다.
장기 미접속 계정의 별도 전환
원문 없이 모든 구 해시를 일반적인 새 비밀번호 해시로 일괄 바꿀 수는 없습니다. 옛 해시 문자열을 다시 해시하는 임시 계층화는 원래 입력으로 새 정책을 적용한 것과 같은 보안 계약이 아닙니다. 장기 미접속 계정의 위험·보관 목적·노출 여부에 따라 재설정 필요 상태, 추가 인증, 오래된 자격 폐기 일정을 정합니다.
약한 이메일 복구만 붙여 정책을 강화했다고 주장하지 않습니다. 재설정 이후 기존 세션 처리와 MFA 유지·복구는 별도 보안 변경입니다. 전환 지표는 전체 계정 중 구 정책 수, 정상 로그인 재해시 성공, 조건 충돌, 재설정 필요 계정 수로 봅니다.
기능 검사와 인증 부하 측정
동일 비밀번호를 다른 salt로 저장했을 때 결과가 다르고 각각 검증되는지, 틀린 입력·길이 경계·인코딩·구 정책을 올바르게 처리하는지 검사합니다. 재해시와 비밀번호 변경을 동시 실행해 최신 자격이 보존되는지도 확인합니다.
실제 장비에서 피크 인증 부하와 메모리 상한을 측정하기 전에는 특정 비용이 운영에 적합하다고 확정하지 않습니다. 이 노트는 알고리즘 선택과 전환 설계이며 인증 서버의 벤치마크 결과를 제시하지 않습니다.