"사용자 비밀번호를 데이터베이스에 정확히 어떻게 저장해야 하지?" — 만드는 사람이라면 한 번은 부딪히는 질문입니다. 답은 명확합니다. 여기 안전하게 저장하는 법을 순서대로, 공격 절차 없이 정리합니다.
평문·암호화·생 해시가 모두 실패하는 이유
언젠가 데이터베이스가 샌다고 가정하세요. 그때 피해는 저장 방식에 따라 천차만별입니다.
- 평문: 새는 순간 모든 비밀번호가 노출됩니다. 더 나쁜 것은, 비밀번호 재사용이 침해를 사용자의 다른 서비스로 연쇄시킵니다. 최악입니다.
- 암호화(가역): 키가 그것을 되돌립니다 — 그래서 키가 함께 새면 평문으로 돌아옵니다. 비밀번호는 되읽을 필요가 없으니, 가역성은 아무 이득도 주지 않습니다.
- 생 해시(MD5/SHA-256): 빠른 해시는 공격자가 추측을 빠르게 시험하게 합니다. 흔한 비밀번호는 레인보우 테이블과 무차별 대입에 무너집니다.
안전으로 가는 "네 단계"
약한 방식에 한 번에 하나씩 보강을 더해 가는 것으로 이해하면 가장 빠릅니다.
핵심 통찰: 솔트와 느린 해시는 서로 다른 일을 합니다. 솔트는 사전 계산(레인보우 테이블)과 대량 재사용 크래킹을 무력화합니다. 느린 해시는 무차별 대입을 비현실적인 속도로 떨어뜨립니다. 안전해지려면 둘 다 필요합니다(→ 해싱이란 무엇인가).
실전: 지금 무엇을 할까
Argon2id(또는 bcrypt)를 쓴다
신규 시스템이라면 Argon2id를 1순위로 — 메모리까지 요구할 수 있어 GPU 무차별 대입에 저항합니다. 검증된 구현을 선호한다면 bcrypt도 실전에서 충분합니다. 어느 쪽이든 표준 라이브러리 구현을 그대로 쓰세요.
솔트는 자동으로 맡긴다
bcrypt/Argon2는 사용자별 솔트를 생성해 해시 문자열 안에 삽입합니다. 별도의 솔트 컬럼을 둘 필요가 없습니다. MD5(salt + password)를 손수 짜는 것은 전형적인 하지 말아야 할 일입니다.
환경에 맞게 비용을 튜닝한다
bcrypt의 비용 인자, 또는 Argon2의 메모리/반복/병렬도를 정상 로그인이 여전히 즉각적으로 느껴지는 한도에서 최대한 높게 설정하세요. 서버가 빨라지면 공격자도 빨라지므로 — 매년 다시 점검해 올리세요.
표준 비교 함수로 검증한다
로그인 시 입력을 저장된 해시와 라이브러리의 검증 함수로 대조하세요(대부분 타이밍 안전한 상수 시간 비교를 씁니다). 직접 문자열 동등 비교를 짜지 마세요.
약한 해시는 로그인 시 재해싱으로 이전한다
이미 MD5/SHA-256을 저장 중이라면, 사용자가 로그인에 성공하는 순간 새 방식으로 재해싱해 저장하세요. 로그인하지 않은 사용자는 기존 해시를 bcrypt로 감싸(레이어드 해시) 잠정 업그레이드할 수 있습니다.
흔한 실수 vs 올바른 구현
흔한 실수
- 저장용으로 비밀번호를 암호화(키가 새면 → 끝)
MD5(password)나SHA-256(password)를 그대로 저장- 모든 사용자에게 단일 공유 솔트 사용
hash(salt + password)를 손수 짜기
올바른 구현
- Argon2id / bcrypt로 일방향 해시
- 사용자별 솔트(라이브러리가 자동으로 추가)
- 괜찮게 느껴지는 한도에서 높은 비용, 주기적으로 상향
- 표준 해시/검증 함수를 그대로 사용
본 사이트의 견해: 비밀번호 저장은 지루하고 검증된 답에 올라타는 자리다
비밀번호 저장은 창의력을 발휘할 자리가 아닙니다. Argon2/bcrypt의 세계적으로 검증된 표준 구현에 올라타는 것이 가장 안전하고 운영도 가장 쉽습니다. 우리의 입장: 여기서는 혁신하지 마세요. 그 노력은 대신 비밀번호 의존 자체를 줄이는 데 쓰세요 — 철저한 다단계 인증(MFA), 그리고 결국에는 패스키(패스워드리스)로의 이동입니다. 아무리 강한 해시도 약하고 재사용된 비밀번호를 구할 수는 없습니다.
다음으로 읽기
- 용어집: 비밀번호 해싱이란 무엇인가 / 솔트란 무엇인가
- 기초: 비밀번호를 올바르게 보관하기(사용자 측) / 비밀번호 관리자 고르기
- 방어: MFA를 제대로 고르기(비밀번호 의존 줄이기)
- 용어집: 패스키란 무엇인가(패스워드리스로 가기)
FAQ
Q저장용으로 비밀번호를 그냥 암호화하면 안 되나요?
안 됩니다 — 암호화는 잘못된 도구입니다. 암호화는 키로 되돌릴 수 있어서, 키가 새면 모든 비밀번호가 평문으로 돌아옵니다. 비밀번호는 본인을 포함해 누구도 되읽을 필요가 없으므로, 되돌릴 수 없는 '해시'가 정답입니다. 다만 생 해시만으로는 부족합니다; 사용자별 솔트와 느린 해시(bcrypt/Argon2)를 결합하세요.
Qbcrypt와 Argon2 중 무엇을 써야 하나요?
신규 개발이라면 Argon2(특히 Argon2id)가 1순위입니다: 메모리와 연산을 모두 튜닝할 수 있어 GPU 무차별 대입에 잘 저항합니다. 오랜 검증을 거친 구현을 중시한다면 bcrypt도 여전히 충분히 실용적입니다. 어느 쪽이든 핵심은 표준 라이브러리 구현을 그대로 쓰고 직접 만들지 않는 것입니다.
Q이미 MD5/SHA-256으로 비밀번호를 저장했습니다. 어떻게 이전하나요?
평문으로 일괄 되돌릴 수 없으므로(그게 핵심입니다) 로그인 시 이전하세요. 사용자가 로그인에 성공하는 순간, 방금 입력한 올바른 비밀번호를 새 방식(예: Argon2id)으로 재해싱해 저장하세요. 로그인하지 않은 사용자는 기존 해시를 bcrypt로 한 번 감싸(레이어드 해시) 잠정 업그레이드할 수 있습니다.