SHA-256 vs bcrypt vs Argon2 - 개발자를 위한 해싱 알고리즘 완벽 비교
비밀번호 저장에 SHA-256을 쓰면 안 되는 이유, bcrypt와 Argon2의 차이, 2026년 권장 해싱 전략을 상세히 알아봅니다.
Toolypet Team
Development Team
SHA-256 vs bcrypt vs Argon2: 언제 무엇을 사용해야 할까?
"비밀번호를 SHA-256으로 해싱해서 저장하면 안전하겠지?"
이 질문을 하는 개발자가 많습니다. 결론부터 말하면, 비밀번호에 SHA-256을 사용하면 안 됩니다.
이 가이드에서는 해싱 알고리즘의 차이와 각 상황에 맞는 올바른 선택을 알아봅니다.
해싱 vs 암호화: 기본 개념
해싱 (Hashing)
입력 → 해시 함수 → 고정 길이 출력 (복원 불가)
"password" → SHA-256 → "5e884898da28047d..."
- 단방향: 원본 복구 불가능
- 결정적: 같은 입력 = 같은 출력
- 고정 길이: 입력 크기와 무관하게 일정한 출력
암호화 (Encryption)
입력 + 키 → 암호화 → 암호문 → 복호화 + 키 → 원본
"password" + key → AES → "Xyz..." → AES + key → "password"
- 양방향: 키로 원본 복구 가능
- 키 의존: 키가 없으면 복호화 불가
비밀번호 저장에는 해싱을 사용합니다. 원본을 알 필요 없이 입력값과 해시를 비교하면 됩니다.
해싱 알고리즘 분류
빠른 해시 (Fast Hash)
| 알고리즘 | 출력 길이 | 속도 | 용도 |
|---|---|---|---|
| MD5 | 128비트 | 매우 빠름 | ❌ 보안용 사용 금지 |
| SHA-1 | 160비트 | 빠름 | ❌ 보안용 사용 금지 |
| SHA-256 | 256비트 | 빠름 | 파일 무결성, 디지털 서명 |
| SHA-512 | 512비트 | 빠름 | 파일 무결성, 블록체인 |
느린 해시 (Slow Hash / Password Hash)
| 알고리즘 | 특징 | 2026 권장 |
|---|---|---|
| bcrypt | 시간 조절 가능 (cost) | ✅ |
| scrypt | 메모리 집약적 | ✅ |
| Argon2 | 최신, OWASP 1순위 | ✅✅ |
| PBKDF2 | 호환성 높음 | ⚠️ 레거시 |
왜 SHA-256으로 비밀번호를 저장하면 안 될까?
이유 1: 너무 빠르다
SHA-256은 속도를 위해 설계되었습니다. 이것이 파일 무결성 검사에는 장점이지만, 비밀번호 저장에는 치명적 단점입니다.
현대 GPU 성능:
- MD5: 1,800억 해시/초
- SHA-256: 100억 해시/초
- bcrypt (cost 12): 1,000 해시/초
8자리 복잡한 비밀번호도 SHA-256으로 저장하면 몇 분 만에 크래킹됩니다.
이유 2: Salt 직접 관리 필요
# ❌ 잘못된 방법
hash = sha256(password)
# 문제: Rainbow Table 공격에 취약
# ⚠️ 개선했지만 여전히 부족
salt = generate_random_salt()
hash = sha256(salt + password)
# 문제: 여전히 너무 빠름
이유 3: GPU 가속 취약
SHA-256은 GPU에서 병렬 처리가 쉽습니다. 공격자는 게이밍 GPU 몇 개로 엄청난 속도로 해시를 계산할 수 있습니다.
bcrypt: 27년 검증된 표준
작동 원리
bcrypt(cost, salt, password) → hash
cost: 계산 반복 횟수 (2^cost)
salt: 22자 랜덤 문자열 (자동 생성)
왜 bcrypt가 안전한가?
- 의도적으로 느림: cost factor로 속도 조절
- 자동 Salt: 매번 다른 해시 생성
- 메모리 집약: GPU 가속 어려움
Cost Factor 가이드 (2026)
| Cost | 해시 시간 | 권장 용도 |
|---|---|---|
| 10 | ~100ms | 개발/테스트 |
| 12 | ~250ms | 일반 웹앱 (권장) |
| 13-14 | ~500ms | 높은 보안 요구 |
| 15+ | 1초+ | 특수 목적 |
코드 예시
// Node.js with bcrypt
const bcrypt = require('bcrypt');
// 해싱 (회원가입)
const hash = await bcrypt.hash(password, 12); // cost 12
// 검증 (로그인)
const isValid = await bcrypt.compare(inputPassword, storedHash);
# Python with bcrypt
import bcrypt
# 해싱
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# 검증
is_valid = bcrypt.checkpw(input_password.encode(), stored_hash)
Argon2: 차세대 표준
Argon2란?
2015년 Password Hashing Competition 우승자입니다. OWASP에서 2026년 1순위 권장 알고리즘입니다.
Argon2 변형
| 변형 | 특징 | 권장 |
|---|---|---|
| Argon2d | GPU 공격 저항 | 사이드채널 취약 |
| Argon2i | 사이드채널 저항 | GPU 공격 취약 |
| Argon2id | d + i 혼합 | ✅ 권장 |
Argon2 파라미터
Argon2id(memory, iterations, parallelism, password, salt)
- memory: 메모리 사용량 (KB)
- iterations: 반복 횟수
- parallelism: 병렬 스레드 수
OWASP 권장 설정 (2026)
최소 설정:
- memory: 64MB (65536 KB)
- iterations: 3
- parallelism: 4
높은 보안:
- memory: 256MB
- iterations: 4
- parallelism: 8
코드 예시
// Node.js with argon2
const argon2 = require('argon2');
// 해싱
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64MB
timeCost: 3,
parallelism: 4
});
// 검증
const isValid = await argon2.verify(storedHash, inputPassword);
# Python with argon2-cffi
from argon2 import PasswordHasher
ph = PasswordHasher(
memory_cost=65536,
time_cost=3,
parallelism=4
)
# 해싱
hash = ph.hash(password)
# 검증
try:
ph.verify(stored_hash, input_password)
except VerifyMismatchError:
# 비밀번호 불일치
bcrypt vs Argon2: 무엇을 선택할까?
비교표
| 항목 | bcrypt | Argon2id |
|---|---|---|
| 나이 | 1999년 (27년) | 2015년 (11년) |
| 검증 | 27년 실전 검증 | 학술 검증 완료 |
| 메모리 조절 | ❌ | ✅ |
| GPU 저항 | ⚠️ 보통 | ✅ 강함 |
| 라이브러리 | 모든 언어 지원 | 대부분 지원 |
| OWASP 권장 | 2순위 | 1순위 |
선택 가이드
bcrypt 선택:
- 레거시 시스템 호환 필요
- 검증된 안정성 우선
- 간단한 설정 선호
Argon2id 선택:
- 새 프로젝트 시작
- 최신 보안 표준 준수
- 높은 GPU 저항 필요
2026 권장 순위 (OWASP)
1순위: Argon2id
2순위: bcrypt
3순위: scrypt
4순위: PBKDF2 (호환성 필요 시)
SHA-256을 사용해야 할 때
SHA-256은 비밀번호가 아닌 다른 용도에 완벽합니다.
적합한 용도
| 용도 | 예시 |
|---|---|
| 파일 무결성 | 다운로드 검증, 백업 확인 |
| 디지털 서명 | JWT, 인증서 |
| 블록체인 | 비트코인 채굴 |
| 체크섬 | 데이터 전송 검증 |
| 해시 테이블 | (HMAC과 함께) API 키 저장 |
코드 예시
// 파일 해시 (Node.js)
const crypto = require('crypto');
const fs = require('fs');
const fileBuffer = fs.readFileSync('file.zip');
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
console.log(hash); // "a1b2c3d4..."
실전 구현 체크리스트
비밀번호 저장
- Argon2id 또는 bcrypt 사용
- 적절한 work factor 설정 (250-500ms)
- 라이브러리 자동 salt 생성 사용
- 해시 결과 전체 저장 (salt 포함)
비밀번호 검증
- 타이밍 공격 방지 비교 함수 사용
- 검증 실패 시 일관된 응답 시간
- 로그인 실패 횟수 제한
마이그레이션
- 새 해시로 점진적 업그레이드
- 로그인 성공 시 새 알고리즘으로 재해싱
- 구 해시 식별 가능하도록 prefix 사용
FAQ
Q1: MD5로 저장된 기존 비밀번호는 어떻게 하나요?
A: 사용자가 로그인할 때 새 알고리즘으로 재해싱하세요.
// 로그인 성공 시
if (hash.startsWith('$md5$')) {
// bcrypt로 재해싱
const newHash = await bcrypt.hash(inputPassword, 12);
await updateUserHash(userId, newHash);
}
Q2: bcrypt의 72바이트 제한은 문제 되지 않나요?
A: 대부분의 비밀번호는 72바이트 이내입니다. 더 긴 입력이 필요하면 먼저 SHA-256으로 해싱 후 bcrypt 적용하세요.
// 긴 비밀번호 처리
const prehash = crypto.createHash('sha256').update(password).digest('base64');
const finalHash = await bcrypt.hash(prehash, 12);
Q3: cost factor를 너무 높이면 DoS 공격에 취약하지 않나요?
A: 맞습니다. 로그인 요청 속도 제한(rate limiting)을 함께 구현하세요. 250-500ms가 적절한 균형점입니다.
Q4: pepper는 필요한가요?
A: pepper(애플리케이션 레벨 비밀 키)는 추가 보안을 제공하지만 필수는 아닙니다. salt만으로도 충분합니다.
Q5: 해시 온라인 도구를 사용해도 되나요?
A: 학습/테스트 목적으로만 사용하세요. 프로덕션에서는 서버 사이드에서 해싱해야 합니다. 해시 생성기는 100% 클라이언트에서 처리되어 안전합니다.
마무리
| 용도 | 권장 알고리즘 |
|---|---|
| 비밀번호 저장 | Argon2id > bcrypt |
| 파일 무결성 | SHA-256 |
| 디지털 서명 | SHA-256 / SHA-512 |
| 레거시 호환 | bcrypt / PBKDF2 |
핵심 원칙:
- 비밀번호에 빠른 해시(SHA-256, MD5) 절대 금지
- bcrypt cost 12 이상, Argon2 memory 64MB 이상
- 라이브러리 제공 salt 자동 생성 사용
관련 도구
| 도구 | 용도 |
|---|---|
| Hash Generator | SHA-256, bcrypt 해시 생성 |
| Password Generator | 강력한 비밀번호 생성 |
저자 소개
Toolypet Team
Development Team
The Toolypet Team creates free, privacy-focused web tools for developers and designers. All tools run entirely in your browser with no data sent to servers.