비밀번호 저장은 소프트웨어에서 진짜 정답이 존재하는 몇 안 되는 영역이다. 그 정답은 이미 공개되어 있고, 꾸준히 갱신되며, 돈 한 푼 들지 않는다. 그런데도 가장 꾸준히 틀리는 영역이기도 하다. 틀린 답들이 한때는 옳은 답이었고, 그 뒤로 아무도 다시 들여다보지 않았기 때문이다.

실패는 좀처럼 특별하지 않다. 2012년에 통하던 조언을 따라 2016년에 만들어진 시스템이 여전히 돌아가고, 여전히 로그인을 받아들이고 있으며, 그 함수를 작성한 사람이 회사를 떠난 뒤로는 아무도 그 코드를 열어보지 않았을 뿐이다.

한 가지만 가져간다면: Argon2id를 쓰고, 그럴 수 없다면 bcrypt를 써라. 이 글의 나머지는 전부 세부 사항이다. SHA-256 같은 범용 해시는 몇 번을 반복해서 적용하든 비밀번호 해시가 아니다. 그 속도는 설계 목표이고, 속도야말로 당신의 데이터베이스를 손에 넣은 공격자에게 주지 않으려는 바로 그것이기 때문이다.


비밀번호 저장에는 정확히 하나의 올바른 형태가 있다

의도적으로 느리고 메모리를 많이 쓰는 함수로 만든 해시를, 비밀번호마다 고유한 무작위 솔트와 함께, 당신의 하드웨어에 맞춰 조정한 파라미터로 저장하라. 이 세 가지가 하나라도 빠지면 나머지 둘이 의미를 잃는다.

OWASP 비밀번호 저장 지침은 현재 권고안을 정확하게 적어 두었고, 이 권고는 원칙이 아니라 숫자로 인용할 가치가 있다.

알고리즘권장 파라미터사용 시점
Argon2id19 MiB 메모리, 2회 반복, 1 병렬도첫 번째 선택
scryptN=2^17 (128 MiB), r=8, p=1Argon2id를 쓸 수 없을 때
bcryptwork factor 10 이상, 비밀번호 72 byte 제한레거시 시스템
PBKDF2-HMAC-SHA256600,000회 반복FIPS-140 준수가 요구되는 곳

이 표에 관해 두 가지를 덧붙인다. Argon2id 수치는 최소값이고, 메모리를 늘리는 대신 반복을 줄이는 것은 동등한 교환이므로 47 MiB에 1회 반복을 쓰는 구성도 기준선과 비슷한 수준의 안전성을 준다. 그리고 bcrypt의 72 byte 제한은 검증 규칙이 아니라 실제 잘림이다. 72 byte를 넘는 부분은 조용히 무시되며, 긴 패스프레이즈를 허용하거나 입력을 미리 해시해서 넘기는 경우에 문제가 된다.

이 파라미터들은 하드웨어가 좋아질수록 위로 조정된다. 구현할 때는 이 표가 아니라 원문을 읽어라.

솔트는 선택 사항도 아니고 충분하지도 않다

솔트는 각 해시 옆에 함께 저장되는 고유한 무작위 값이다. 목적은 좁지만 중요하다. 같은 비밀번호를 쓰는 두 사용자가 서로 다른 해시를 만들도록 보장하는 것이다. 이렇게 하면 미리 계산해 둔 레인보우 테이블이 무력해지고, 공격자가 비밀번호 하나를 깨서 그 비밀번호를 공유하던 모든 계정을 한꺼번에 가져가는 일도 막을 수 있다.

솔트가 하지 못하는 일은 누군가를 느리게 만드는 것이다. 당신의 데이터베이스를 가진 공격자는 솔트도 함께 가지고 있다. 로그인을 검증하려면 솔트를 읽을 수 있어야 하기 때문이다. 빠른 해시에 솔트만 얹는다는 것은 공격자가 각 비밀번호를 하나씩, 그러나 엄청난 속도로 공격한다는 뜻이며, 그것이 바로 당신이 피하려던 상황이다.

느림은 알고리즘 자체에서 나와야 한다. 메모리를 많이 쓰는 함수의 존재 이유가 정확히 그것이다. 자원을 의도적으로 소모해서, 전용 하드웨어를 갖춘 공격자가 빠른 해시를 상대할 때보다 훨씬 적은 이득만 얻도록 만드는 것이다.

페퍼, 즉 모든 비밀번호에 함께 적용하되 데이터베이스 바깥에 보관하는 비밀 값은 실질적인 한 겹을 더한다. 데이터베이스 덤프 하나만으로는 부족해지기 때문이다. 다만 운영 부담도 함께 늘어난다. 페퍼를 교체하려면 다음 로그인 때 다시 해시해야 하고, 잃어버리면 모든 계정이 잠긴다. 가치가 큰 시스템에는 들일 만하고, 대부분의 경우에는 건너뛰어도 된다.

몇 년씩 살아남는 실수들

솔트를 곁들인 빠른 해시. 솔트를 붙인 SHA-256은 책임감 있어 보이지만 그렇지 않다. 속도 자체가 취약점이다.

해시 대신 암호화. 암호화는 설계상 되돌릴 수 있으므로 키가 존재하고, 그 키를 얻은 사람은 모든 비밀번호를 평문으로 얻는다. 비밀번호는 결코 복구 가능해서는 안 되며, 검증만 가능해야 한다.

한 번 정하고 다시는 올리지 않은 work factor. 2015년에 고른 bcrypt 비용 값은 2026년 하드웨어 앞에서 상당히 약하다. 다음 로그인이 성공했을 때 올려라. 그 순간에는 평문이 잠시 손에 있으므로 다시 해시할 수 있다.

잘라내기나 문자 제한. 최대 길이가 대략 64자보다 짧거나 특정 문자를 금지한다면, 대개 비밀번호가 그 내용에 신경 쓰는 방식으로 저장되고 있다는 신호다. 그럴 필요가 없다. 어차피 해시할 값이기 때문이다.

시간을 흘리는 비교. 검증은 상수 시간 비교를 써야 한다. 표준 라이브러리 함수는 그렇게 하지만, 직접 작성한 동등 비교는 그렇지 않다.

비밀번호를 로그에 남기기. 오류 로그나 요청 추적, 모니터링 시스템으로 흘러들어가고, 그 순간 데이터베이스보다 접근 통제가 약한 세 곳에 비밀번호가 존재하게 된다.

기존 시스템 이전하기

가지고 있지 않은 것은 다시 해시할 수 없다. 당신이 보관하는 것은 해시뿐이기 때문이다. 현실적인 방법은 로그인 시점에 업그레이드하는 것이다.

사용자가 인증에 성공하면 그 순간 평문 비밀번호가 잠시 메모리에 있다. 옛 해시로 검증한 다음, 곧바로 현재 알고리즘으로 새 해시를 계산해 저장된 값을 교체하라. 몇 달이면 활성 계정 대부분이 사용자 눈에 보이는 변화 없이 옮겨간다.

레코드마다 어떤 알고리즘으로 저장했는지 함께 기록해 두 방식이 공존하게 하고, 그 시점 이후로는 남아 있는 옛 해시를 무효화하고 해당 사용자에게 재설정을 요구할 날짜를 정하라. 약한 해시를 가진 채 잠들어 있는 계정이야말로 공격자가 노리는 대상이다.

지름길로 옛 해시를 새 알고리즘으로 한 번 더 감싸지 마라. 동작은 한다. 그러나 겉모습만 현대적일 뿐 실제 보안 경계는 여전히 약한 해시로 남는다.

알고리즘보다 중요한 것

올바른 해시는 데이터베이스가 유출되었을 때 당신을 지켜준다. 더 흔한 공격, 즉 다른 곳에서 얻은 진짜 비밀번호로 누군가 그냥 로그인하는 상황에는 아무 도움이 되지 않는다.

인증 경로의 속도 제한, 다중 인증 제공, 새 비밀번호를 이미 유출된 목록과 대조하는 검사, 크리덴셜 스터핑 패턴에 대한 경보가 바로 그 문제를 다루며, 하루하루의 관점에서는 Argon2id와 bcrypt 중 무엇을 고르느냐보다 전부 더 중요하다. API 보안 통제도 같은 계열의 문제다.

둘 다 할 가치가 있고, 순서에 대해서는 솔직할 필요가 있다. 해시는 값싸고 영구적이니 한 번에 제대로 잡아 두고, 그 다음의 지속적인 노력은 로그인 경로에 쏟아라. 공격이 실제로 도착하는 곳이 거기다. Mecanik은 애플리케이션 보안 분석 과정에서 두 가지를 함께 점검한다.



자주 묻는 질문

2026년 최고의 비밀번호 해시 알고리즘은 무엇인가? Argon2id이며, 최소 19 MiB 메모리, 2회 반복, 1 병렬도를 쓴다. 사용할 수 없다면 scrypt를 N=2^17, r=8, p=1로 쓰는 것이 대안이고, work factor 10 이상의 bcrypt는 레거시 시스템에 맞으며, PBKDF2-HMAC-SHA256을 600,000회 반복으로 쓰면 FIPS-140 요구 사항을 충족한다.

비밀번호에 솔트만 붙이면 충분한가? 아니다. 솔트는 같은 비밀번호가 서로 다른 해시를 만들도록 보장해 레인보우 테이블을 무력화하고, 깨진 비밀번호 하나가 그것을 공유하던 모든 계정을 열지 못하게 막는다. 그러나 공격자를 느리게 만들지는 못한다. 로그인을 검증하려면 솔트를 읽을 수 있어야 하기 때문이다. 느림은 의도적으로 메모리를 많이 쓰는 알고리즘에서 나와야 한다.

비밀번호에 SHA-256을 쓰면 안 되는 이유는? 속도가 설계 목표이고, 속도야말로 당신의 데이터베이스를 쥔 공격자에게 주지 않으려는 바로 그것이기 때문이다. 범용 해시는 몇 번을 적용하든, 솔트를 붙였든 아니든 비밀번호 해시가 아니다.

bcrypt에는 비밀번호 길이 제한이 있는가? 있다. 72 byte이며, 그것을 넘는 부분은 거부되는 대신 조용히 잘린다. 긴 패스프레이즈를 허용하거나 입력을 bcrypt에 넘기기 전에 미리 해시하는 경우에 중요하다. 잘림이 어떤 오류 표시도 없이 일어나기 때문이다.

사용자 비밀번호 없이 비밀번호 해시를 어떻게 갱신하는가? 로그인 시점에 한다. 사용자가 인증할 때 평문을 잠시 손에 쥐게 되므로, 옛 해시로 검증한 뒤 곧바로 현재 알고리즘으로 다시 해시하라. 레코드마다 알고리즘을 기록해 두 방식이 공존하게 하고, 그 이후 남은 옛 해시를 무효화하고 해당 사용자에게 재설정을 요구할 날짜를 정하라.