본문으로 건너뛰기
yutils

비밀번호 해싱 — bcrypt·Argon2 와 절대 쓰지 말아야 할 것

왜 SHA-256 으로 비밀번호를 저장하면 안 되는지, bcrypt 와 Argon2 의 차이, cost 파라미터 선택 기준, pepper, 알고리즘 마이그레이션 전략.

약 10분 읽기

비밀번호를 잘못 저장한 사고는 매년 나온다. LinkedIn (2012, SHA-1 평문), Yahoo (2013, MD5), Adobe (2013, 3DES). 공통점은 속도가 빠른 해시 또는 일반 해시 + salt 없음. 이 가이드는 2026 년 기준으로 옳은 선택과 흔히 빠지는 오답을 정리한다.

왜 SHA-256 이 아닌가

SHA-256 은 훌륭한 일반 해시 함수 다. 문서 무결성, JWT 서명, 파일 체크섬 등엔 적합하다. SHA 해시 로 직접 계산할 수 있다. 하지만 비밀번호엔 부적합하다 — 이유는 단 하나,너무 빠르다.

2026 년 기준 GPU 한 장으로 SHA-256 을 초당 100 억 회 (10 GH/s) 계산할 수 있다. 8 자 영문+숫자 비밀번호의 전체 공간은 약 2.18 × 1014 — GPU 8 장이면 6 시간 안에 전수 탐색이 끝난다. salt 가 있어도 한 사용자에 대해서는 같은 시간이 걸린다. MD5 는 더 빠르고 (50 GH/s+), 충돌까지 증명됐다 — MD5 해시 는 체크섬용으로만 남겨둬야 한다.

비밀번호 해시 함수는 의도적으로 느려야 한다. 정상 로그인 (1 회) 은 100 ms 라도 사용자가 못 느끼지만, brute force (수십억 회) 는 견딜 수 없게 된다.

옳은 선택지 — 2026 기준

Argon2id (1순위)

2015 년 Password Hashing Competition 우승. RFC 9106 (2021) 표준. 메모리 하드 — GPU 의 메모리 부족을 강제하기 때문에 GPU 가속이 의미를 잃는다.

  • 파라미터 3 개: m (메모리 KiB), t (반복), p (병렬).
  • OWASP 권장 (2026): m=46 MiB, t=1, p=1. 서버가 여유 있으면 m=64 MiB.
  • id 변종이 side-channel + GPU 둘 다 방어. i 또는 d 단독은 비추.

bcrypt (2순위, 호환성 우선시)

1999 년 등장. 30 년 가까이 검증됐고, 거의 모든 언어에 검증된 라이브러리 가 있다. Argon2 가 없는 환경에서 안전한 차선.

  • 파라미터 1 개: cost (= log2 반복 횟수).
  • OWASP 권장 (2026): cost=12. M2 MacBook 기준 약 250 ms.
  • 제한: 입력 72 바이트로 잘림. 긴 비밀번호 또는 passphrase 는 SHA-256 으로 먼저 해시 후 bcrypt 적용 (단, NULL 바이트 취약점 주의 — base64 인코딩 후 넣는 것이 안전).
  • 직접 시험해 보려면 Bcrypt 해시 에서 cost 별 해시 시간과 결과 형식을 볼 수 있다.

scrypt — 3순위

Argon2 보다 오래됐지만 메모리 하드. Argon2 가 가능하다면 굳이 새로 쓸 이유는 없다. 이미 scrypt 를 쓰는 시스템이면 그대로 유지해도 무방.

PBKDF2 — 권장하지 않음 (하위 호환만)

NIST FIPS 호환이 필요한 정부 시스템 외에는 추천 안 함. GPU 가속에 약함.

cost 파라미터 — 어떻게 정할까

원칙: 정상 로그인 1 회가 100~250 ms 안에 끝나는 가장 큰 값. 사용자 체감 X, 공격자 비용 ↑.

직접 측정 — 운영 환경과 같은 사양 머신에서 시간 측정.

import bcrypt from "bcryptjs";
console.time("bcrypt-12");
await bcrypt.hash("test", 12);
console.timeEnd("bcrypt-12");

Argon2id 의 경우 메모리도 함께 봐야 한다. m=46 MiB p=1 이면 동시 로그인 10 명이면 460 MiB 가 필요. 서버 RAM 과 동접 부하 함께 산정.

salt — 왜 필요한지

salt 는 사용자별 랜덤 바이트로, 같은 비밀번호가 다른 해시를 만들도록 강제한다. 두 가지 공격 방어:

  1. rainbow table — 미리 계산된 (비밀번호 → 해시) 표. salt 가 있으면 사용자마다 다른 표가 필요해 무용지물.
  2. 일괄 공격 — DB 가 털리면 같은 비밀번호 쓰는 사람들이 같은 해시로 묶여 보이는 문제. salt 가 있으면 일괄 비교 불가.

bcrypt/Argon2 모두 salt 를 알아서 생성하고 해시 문자열 안에 포함시킨다. 직접 만질 일이 없다.

pepper — 선택 사항

pepper 는 모든 사용자에게 같은 시크릿 (env 변수 등). DB 만 털린 상황 에서 추가 방어선. 단,

  • 서버가 시크릿을 알아야 검증 가능. 분산 시스템에서는 모든 서버에 배포 필요.
  • pepper 가 노출되면 salt 만 있는 상태와 동일 — 일반화된 이점은 없다.
  • rotation 이 어렵다. 변경 시 모든 해시를 다시 계산해야 함.

대부분의 서비스는 Argon2id 만으로 충분하다. pepper 는 보안 요구가 매우 높은 시스템에서만 추가.

비밀번호 생성 정책 — 사용자 측

NIST 800-63B (2020 개정) 의 핵심:

  • 최소 8 자, 권장 12 자 이상.
  • 구성 강제 금지 — "대문자·숫자·특수문자 포함 필수" 는 실제로 보안을 떨어뜨린다. 사용자가 Password1! 같은 예측 패턴으로 회피.
  • 주기적 만료 금지 — 사고 의심 시에만 강제 변경. 정기 변경은 더 약한 비밀번호로 이동시킬 뿐.
  • 유출 DB 대조 — Have I Been Pwned API 또는 로컬 사본 으로 흔히 털린 비밀번호 차단.

사용자에게 권장할 비밀번호 자체는 비밀번호 생성기 같은 도구로 16+ 자 랜덤 또는 4~5 단어 passphrase 가 가장 안전.

알고리즘 마이그레이션

오래된 알고리즘 (MD5, SHA-1, 약한 bcrypt cost) 을 쓰고 있다면 어떻게 옮길까? 두 가지 패턴:

A. wrap-and-rotate (즉시 보호)

모든 기존 해시를 new_algo(old_hash) 로 한 번에 감싼다. 예: bcrypt(sha256(password)). 검증 시 같은 순서로 적용. 로그인 사용자가 평문을 보낼 때 점진적으로 순수 새 해시로 교체.

  • 장점: DB 안의 모든 해시가 즉시 강화됨. 사용자 액션 불필요.
  • 단점: 검증 비용 약간 증가. 사용자별 마이그레이션 상태 추적 필요.

B. on-login rotate (단순)

로그인 시 평문을 받으면 새 알고리즘으로 다시 해시해 저장. 아직 로그인 안 한 사용자는 옛 해시 그대로. 대부분의 사용자는 한 달 안에 마이그레 이션 완료.

  • 장점: 단순. 한 컬럼에 알고리즘 prefix 만 표시.
  • 단점: 비활성 사용자는 영영 남음. 사고 시 노출.

대규모 사용자라면 A + B 조합 — A 로 즉시 wrap, B 로 점진 교체.

흔히 빠지는 함정

1. 자체 구현

"SHA-256 을 1000 번 반복하면 안전" 같은 자체 설계 금지. 검증된 라이브러리만 사용. Argon2 는 직접 구현하면 side-channel 누수가 거의 확실.

2. salt 재사용

모든 사용자에 같은 salt = salt 없음과 동일. 라이브러리가 자동 생성하면 문제 없음.

3. 평문 로그

에러 로그에 request body 가 그대로 들어가면 평문 비밀번호가 로그에 남는다. 로그 마스킹 필수.

4. 클라이언트 해시

브라우저에서 해시한 결과를 서버로 보내고 "서버는 평문을 모른다" 고 주장하는 패턴. 잘못. 그 해시가 그 사용자의 사실상 비밀번호가 된다 — DB 가 털리면 그대로 로그인 가능. 클라이언트 해싱은 추가 layer 이지 대체가 아니다.

5. 길이 제한

bcrypt 72 바이트 제한 외에 임의 제한 (16/32 자) 금지. passphrase 사용자 를 막을 뿐.

요약

  • 비밀번호 해시는 의도적으로 느린 함수만. SHA-256/MD5 는 금지.
  • 2026 권장: Argon2id (1순위) → bcrypt cost 12 (호환성).
  • 정상 로그인 1 회 100~250 ms 가 되도록 cost 조정. 실측 권장.
  • salt 는 자동, pepper 는 선택. 자체 구현 절대 금지.
  • NIST 800-63B: 최소 길이만, 구성 강제 X, 정기 만료 X, 유출 DB 차단 O.
  • 알고리즘 교체는 wrap-and-rotate 또는 on-login rotate. 비활성 사용자 남는 점 주의.
가이드 목록으로