'솔트'는 비밀번호 해싱을 이야기하는 순간 등장합니다. 해싱 전에 무작위 값 하나를 더하는 것만으로 안전성이 크게 달라집니다. 동작 원리와 핵심을 설명합니다(공격 절차 없음).
솔트가 비밀번호를 더 안전하게 만드는 이유
솔트가 없으면 같은 비밀번호를 가진 사용자들은 같은 저장 해시를 갖습니다. 그러면 공격자에게 두 가지 방법이 생깁니다. 미리 계산된 테이블(레인보우 테이블)에서 비밀번호를 곧장 찾는 것, 그리고 한 계정을 깬 결과를 같은 비밀번호를 가진 모두에게 재사용하는 것입니다.
솔트를 더하면 같은 "password123"도 사용자마다 완전히 다른 값으로 저장됩니다.
같은 이유로 솔트는 사용자마다(이상적으로는 비밀번호마다) 새로 만들어야 합니다. 모두가 하나의 솔트를 공유하면 같은 비밀번호를 가진 사용자의 저장 값이 다시 같아져 효과의 대부분이 사라집니다. 예측하기 어려운 암호학적 난수 생성기를 쓰고, 솔트는 해시와 함께 저장합니다(비밀일 필요는 없습니다).
솔트가 막는 것은 미리 계산된 테이블과 재사용된 비밀번호의 대량 크래킹뿐입니다. 비밀번호 하나에 대한 무차별 대입을 느리게 하는 것은 솔트의 역할이 아니며, bcrypt/Argon2 같은 일부러 느린 해시가 맡습니다. 막는 위협이 다르므로 항상 둘 다 씁니다.
솔트의 길이와 만드는 법
기준이 되는 문서는 두 가지입니다.
- NIST SP 800-63B-4(미국의 인증 가이드라인): 솔트는 최소 32비트여야 하며, 저장된 솔트끼리 거의 겹치지 않도록 선택해야 합니다. 비밀번호마다 솔트와 해시를 함께 저장합니다
- RFC 9106(Argon2 사양): 비밀번호 해싱에는 16바이트(128비트)를 권장하며, 공간이 부족하면 64비트까지 줄일 수 있습니다
만드는 법의 규칙은 세 가지입니다.
- 암호학적 난수 생성기를 쓴다.
Math.random()이나 PHP의mt_rand()처럼 예측 가능한 함수가 아니라, 언어가 제공하는 안전한 난수 함수를 씁니다 - 추측할 수 있는 값을 쓰지 않는다: 사용자 이름, 이메일 주소, 사용자 ID, 가입 시각은 솔트를 대신할 수 없습니다
- 비밀번호를 바꾸면 솔트도 새로 만든다. 예전 솔트를 그대로 두면 이전 해시와 새 해시 사이에 연결 고리가 남습니다
실무에서는 표준 bcrypt, Argon2, scrypt 구현이 이 모든 것을 자동으로 충족합니다. 솔트를 직접 만드는 코드를 쓰고 있다면 검토가 필요하다는 신호로 보세요.
솔트, 페퍼, 키 스트레칭의 차이
비밀번호 저장에는 이름이 비슷한 세 가지 대책이 있습니다. 막는 대상이 서로 달라 어느 하나가 다른 것을 대신하지 않습니다.
| 솔트 | 페퍼 | 키 스트레칭(느린 해시) | |
|---|---|---|---|
| 무엇인가 | 사용자마다 다른 무작위 값 | 모든 사용자에게 공통인 비밀 값 | 해시 계산을 일부러 무겁게 하는 것 |
| 비밀인가 | 아님(해시와 함께 저장) | 비밀(데이터베이스 밖에 보관) | 설정 값은 비밀일 필요 없음 |
| 막는 것 | 미리 계산된 테이블, 같은 비밀번호의 대량 크래킹 | 데이터베이스만 유출되었을 때의 크래킹 | 해시 하나하나에 대한 무차별 대입 |
| 구현 | bcrypt/Argon2가 자동으로 추가 | 필요하면 직접 추가 | bcrypt의 cost, Argon2의 메모리와 반복 횟수 |
NIST SP 800-63B-4는 '검증자만 아는 비밀 키'(흔히 페퍼라고 부르는 것)를 비밀번호 해시와 분리해 보관하고, 가능하면 HSM 같은 전용 하드웨어 안에 두라고 권장합니다.
실제 사례: LinkedIn의 솔트 없는 저장(2012년)
2012년 6월, LinkedIn 이용자의 것으로 보이는 비밀번호 해시 약 650만 건이 인터넷에 게시되었다고 보도되었습니다. LinkedIn은 그중 일부가 LinkedIn 계정에 해당한다고 확인하고, 해당 회원의 비밀번호를 무효화했습니다.
회사는 공식 블로그에서 비밀번호를 해싱만 하던 방식에서 해싱과 솔트를 함께 쓰는 방식으로 옮겨 가던 중이었다고 밝혔습니다. 당시 보도에 인용된 전문가들은 SHA-1 해싱만으로 비밀번호를 보호했다며 회사를 비판했습니다.
교훈은 두 가지입니다.
- 솔트 없는 해시는 흔한 비밀번호가 미리 계산된 테이블로 대량 복원될 수 있습니다
- 전환 중이라도 예전 형식의 해시가 남아 있으면, 유출의 영향은 그 예전 형식이 결정합니다
자신의 환경에서 확인하는 방법
저장된 문자열의 형태를 본다
직접 관리하는 개발용 데이터베이스에서 비밀번호 값 몇 개를 봅니다. bcrypt라면 $2b$12$ 뒤의 22자가 솔트입니다. Argon2라면 $argon2id$v=19$m=…,t=…,p=…$ 뒤에 솔트와 해시가 $로 구분되어 이어집니다. 어느 형태에도 맞지 않고 별도의 솔트 열도 없다면, 솔트 없이 저장되어 있을 수 있습니다.
같은 비밀번호로 테스트 계정 두 개를 만든다
개발 환경에서 같은 비밀번호로 테스트 계정 두 개를 등록하고 저장된 값을 비교합니다. 일치한다면 솔트가 작동하지 않는 것입니다. 비밀번호를 바꾸면 솔트 부분도 바뀌는지 확인합니다.
솔트를 직접 만드는 코드를 찾는다
코드에서 salt라는 단어와, 난수 함수나 해시 함수를 직접 조합하는 곳을 검색합니다. 예측 가능한 난수 함수, 사용자 이름이나 ID의 재사용, 고정 문자열을 찾았다면 언어나 프레임워크의 표준 비밀번호 함수로 바꿉니다.
전환은 실무 가이드를 참고한다
솔트 없는 저장에서 벗어나는 방법(로그인할 때 다시 해싱하는 방법 등)과 알고리즘별 권장 설정은 비밀번호를 안전하게 저장하는 법에서 다룹니다.
흔한 오해 vs 사실
오해
- 솔트는 비밀로 유지해야 한다
- 모든 사용자에게 하나의 공유 솔트를 써도 된다
- 솔트가 약한 비밀번호도 안전하게 만든다
사실
- 솔트는 공개되어도 된다(해시와 함께 저장)
- 사용자마다(이상적으로는 비밀번호마다) 다른 무작위 값
- 솔트는 '사전 계산과 재사용 크래킹'을 막고, 무차별 대입은 느린 해시가 막는다
이 사이트의 관점: 먼저 솔트와 느린 해시를 제대로
페퍼는 데이터베이스만 도난당한 경우를 위한 추가 대책입니다. 다른 곳에서 비밀을 관리하는 수고가 늘고, 그 비밀을 잃어버리면 누구의 비밀번호도 검증할 수 없게 됩니다.
그래서 실무의 첫 번째 우선순위는 bcrypt/Argon2로 솔트 + 느린 해시를 올바르게 쓰는 것입니다. 페퍼는 키 관리 체계가 이미 갖춰진 뒤에 검토하세요.
출처(1차 자료)
- NIST, "SP 800-63B-4 Digital Identity Guidelines: Authentication and Authenticator Management"(2025년 8월) — pages.nist.gov(최소 32비트 솔트, 솔트와 해시의 저장, 분리 보관하는 비밀 키)
- RFC 9106, "Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications" — rfc-editor.org(비밀번호 해싱에 16바이트 솔트 권장)
- OWASP, "Password Storage Cheat Sheet" — cheatsheetseries.owasp.org(솔트, 페퍼, 대부분의 라이브러리가 솔트를 자동 처리)
- PHC string format(Argon2 등이 쓰는 해시 문자열 형식) — github.com/P-H-C
- TechCrunch, "LinkedIn Confirms Hack And Leak Of 'Some' User Passwords"(2012년 6월 6일) — techcrunch.com(약 650만 건, 해싱과 솔트로의 전환에 관한 회사 발표)
- Computerworld, "LinkedIn provides breach update -- sort of"(2012년 6월 8일) — computerworld.com(SHA-1 단독 해싱에 대한 비판, 비밀번호 무효화)
다음으로 읽기
- 용어집: 비밀번호 해싱이란(솔트와 함께 이해하기)
- 학습: 비밀번호를 안전하게 저장하는 법(실전 해싱 + 솔트)
- 기초: 비밀번호를 올바르게 저장하기