모든 데이터 침해 공개는 같은 곡선을 따릅니다: “우리는 보안을 진지하게 받아들입니다.” 그런 다음 세부 사항을 읽고 비밀번호가 평문으로 저장되어 있거나, MD5로 해시되어 있거나, 동일한 데이터베이스에 있던 키로 암호화되어 있음을 발견합니다. 비밀번호 저장 문제가 해결되었습니다 — 알고리즘이 존재하고, 라이브러리가 존재하고, 문서가 존재하는데, 개발자들은 여전히 잘못하고 있습니다.
이 게시물은 무엇을 저장해야 하는지, 무엇을 저장하면 안 되는지, 해시와 암호화의 차이, 그리고 SHA-256(password)가 비밀번호 해시가 아닌 이유를 다룹니다.
규칙: 절대 평문을 저장하지 마세요
데이터베이스가 손상되고 평문 비밀번호가 있는 경우, 다른 사이트에서 해당 비밀번호를 재사용하는 모든 사용자가 이제 손상됩니다. 귀하의 사이트뿐만 아니라 — 그들의 이메일, 은행, 소셜 미디어. 평문 비밀번호 저장은 보안 문제가 아닙니다. 도덕적 문제입니다.
해시를 저장하세요. 항상.
해시 vs 암호화
해시는 일방향 변환입니다. 되돌릴 수 없습니다. hashpassword가 주어지면 password를 복구할 수 없습니다. 확인만 할 수 있습니다: 입력을 해시하고 비교합니다.
암호화는 양방향 변환입니다. encrypt(password, key)가 주어지면 키로 password를 복구할 수 있습니다. 키가 손상되면 모든 비밀번호가 손상됩니다.
비밀번호를 해시하세요. 절대 암호화하지 마세요. 평문을 복구해야 하는 경우(필요하지 않습니다), 잘못된 문제를 해결하고 있습니다.
SHA-256이 충분하지 않은 이유
일반적인 실수: hash = SHA-256(password). 빠르고, 잘 알려져 있고, 비밀번호에는 잘못되었습니다.
문제는 속도입니다. 최신 GPU에서 SHA-256은 초당 수십억 개의 해시를 처리합니다. SHA-256 비밀번호 해시 데이터베이스를 가진 공격자는 몇 분 만에 모든 일반 비밀번호, 모든 사전 단어, 모든 순열을 시도할 수 있습니다.
비밀번호 해시 알고리즘은 의도적으로 느립니다. 무차별 대입 공격을 실용하지 않게 만들기 위해 키 스트레칭 — 해시 함수의 수천 번 반복 — 을 사용합니다:
- bcrypt — 1999년 이후 표준. 조정 가능한 비용 계수. 비밀번호 + 소금 → 해시. 모든 주요 언어에 라이브러리가 있습니다.
- scrypt — 메모리 하드. 계산에 대량의 RAM을 필요로 하여 GPU 공격을 더 어렵게 만듭니다.
- Argon2 — 비밀번호 해시 경쟁(2015) 승자. 메모리 하드, 구성 가능한 시간 및 메모리 비용. 현재 모범 사례.
- PBKDF2 — NIST 권장, 널리 사용 가능, HMAC 반복 사용. Argon2만큼 메모리 하드하지는 않지만 잘 테스트됨.
bcrypt, scrypt 또는 Argon2를 사용하세요. 고유한 스키마를 발명하지 마세요. SHA-256으로 한 번 해시하고 끝이라고 하지 마세요.
소금: 왜 모든 비밀번호에 고유한 것이 필요한지
소금은 해시 전에 비밀번호에 추가되는 무작위 데이터입니다. 소금이 없으면 같은 비밀번호를 가진 두 사용자는 같은 해시를 생성합니다. 공격자는 일반 비밀번호의 해시를 사전 계산(레이니어 테이블)하고 데이터베이스와 비교할 수 있습니다.
사용자별 고유 소금이 있으면 비밀번호가 같아도 해시가 다릅니다. 레이니어 테이블은 작동하지 않습니다. 공격자는 각 비밀번호를 개별적으로 무차별 대입해야 합니다.
최신 비밀번호 해시 함수(bcrypt, Argon2)는 자동으로 무작위 소금을 생성합니다. 소금을 개별로 관리할 필요가 없습니다. 비밀번호를 해시 함수에 전달하면 나머지를 처리합니다.
무엇을 저장해야 하는지
각 사용자에 대해 다음을 저장합니다:
- 비밀번호 해시 (bcrypt/Argon2 출력, 소금과 비용 매개변수 포함)
- 해시 알고리즘 (bcrypt, argon2 등)
- 비용 매개변수 (bcrypt 라운드, Argon2 메모리/시간)
저장하지 마세요:
- 평문 비밀번호
- 소금 별도 (해시 함수 출력에 포함됨)
- 비밀번호를 복호화할 수 있는 “암호화 키”
- 비밀번호 힌트 (정보를 유출함)
bcrypt의 해시 출력은 다음과 같습니다: $2b$12$LJ3m4ys3GzF4VQ.YOUR.HASH.HERE. $2b$는 알고리즘을 식별하고, 12는 비용 계수이며, 나머지는 소금 + 해시입니다. 로그인 시도를 확인하려면 동일한 매개변수로 제출된 비밀번호를 해시하고 비교합니다.
비용 계수
bcrypt의 비용 계수는 해시 속도를 결정합니다. 각 증가는 계산 시간을 두 배로 늘립니다:
- 비용 10: 해시당 ~10ms (사용자에게는 빠르고, 공격자에게도 빠름)
- 비용 12: 해시당 ~40ms (합리적)
- 비용 14: 해시당 ~160ms (공격자에게는 느리고, 사용자에게는 눈에 띔)
- 비용 16: 해시당 ~640ms (대부분의 로그인 흐름에는 너무 느림)
2026년 최적 지점: bcrypt 비용 12-14, 64MB+ 메모리와 3+ 반복을 가진 Argon2. 목표는 각 해시 시도를 느리게 하여 수십억 비밀번호 무차별 대입에 수년이 걸리지만, 사용자가 로그인 시 지연을 느끼지 않을 정도로 빠르게 하는 것입니다.
속도 제한
해시만으로는 충분하지 않습니다. 공격자가 로그인 엔드포인트에 초당 수백만 비밀번호를 시도할 수 있다면 bcrypt도 구제하지 못합니다.
로그인 시도를 제한하세요. 동일한 IP 또는 계정에서 5-10회 실패 후 계정을 일시적으로 잠그거나 CAPTCHA를 요구하세요. 이렇게 하면 무차별 대입 공격을 최소한으로 제한합니다.
사용자명 또는 비밀번호가 잘못되었는지 공개하지 마세요. “잘못된 사용자명 또는 비밀번호”는 “해당 이메일로 된 계정이 없습니다”보다 안전합니다. 후자는 공격자에게 어떤 사용자명이 존재하는지 알려줍니다.
지속적인 실패 후 계정 잠금 추가 10-20회 실패 후 15분 동안 계정을 잠그거나 잠금 해제에 이메일 확인을 요구하세요.
비밀번호 생성
비밀번호를 생성할 때(사용자, API 키, 토큰용):
- 최소 12자 최신 하드웨어에 대해 8자는 충분하지 않습니다.
- 대문자, 소문자, 숫자, 기호 포함 하지만 길이가 복잡성보다 중요합니다.
- CSPRNG 사용 (암호학적으로 안전한 의사 난수 생성기).
Math.random()은 안전하지 않습니다. - 예측 가능한 패턴을 절대 생성하지 마세요
Password1!은 복잡해 보이지만 쉽게 추측할 수 있습니다.
최고의 비밀번호는 CSPRNG에서 생성된 무작위 문자열입니다. 비밀번호 관리자를 사용하여 저장하세요. 사용자에게 복잡한 비밀번호를 기억하게 하지 마세요 — 생성된 비밀번호를 제공하고 관리자가 처리하게 하세요.
일반적인 실수
- SHA-256 또는 MD5로 한 번 해시 — 너무 빠르고, 소금 없음, 쉽게 무차별 대입 가능.
- 정적 소금 사용 — 모든 사용자가 같은 소금을 공유하면 레이니어 테이블이 작동함.
- 소금 별도 저장 — 소금이 다른 테이블에 있으면 데이터베이스를 손상시킨 공격자가 둘 다 얻음.
- 해시 대신 암호화 — 비밀번호를 복호화할 수 있으면 키를 가진 공격자도 가능.
- 속도 제한 없음 — bcrypt도 무제한 로그인 시도에 취약.
- 사용자명 존재 공개 — 공격자가 유효한 계정을 열거하는 데 도움.
- 비밀번호 생성에
Math.random()사용 — 예측 가능하고, 암호학적으로 안전하지 않음.
빠른 참조 | 하세요 | 하지 마세요 | |——|–––––| | bcrypt, Argon2 또는 scrypt 사용 | SHA-256 또는 MD5 사용 | | 라이브러리가 소금 처리를 하게 함 | 소금을 직접 관리 | | 로그인 시도를 제한 | 무제한 비밀번호 시도 허용 | | CSPRNG로 비밀번호 생성 | Math.random() 사용 | | 해시만 저장 | 평문 또는 암호화된 비밀번호 저장 | | “잘못된 자격 증명”이라고 말 | “잘못된 비밀번호”라고 말 |
시도해보기
안전한 무작위 비밀번호를 생성하거나 테스트용 문자열을 해시해야 하는 경우 브라우저 기반 도구를 사용하여 작업이 로컬에서 이루어지도록 하세요. 비밀번호는 간단한 생성 작업을 위해 서버를 만져서는 안 됩니다.