2단계 인증과 비밀번호 보안 모범 사례
웹 애플리케이션을 만들었고 사용자들이 가입하고 있습니다. 하지만 그들의 계정은 정말 안전할까요? 약한 비밀번호와 2단계 인증(2FA)의 부재는 공격자가 침입할 수 있는 가장 쉬운 방법 중 하나입니다. 이 가이드에서는 오늘 바로 구현할 수 있는 비밀번호 보안과 2FA에 대한 실용적이고 시대를 초월한 모범 사례를 살펴보겠습니다.
비밀번호 보안이 여전히 중요한 이유
수년간의 경고에도 불구하고, 비밀번호는 대부분의 웹 애플리케이션에서 여전히 주요 인증 방법입니다. 공격자들은 이를 알고 피싱, 크리덴셜 스터핑, 무차별 대입 공격을 통해 비밀번호를 노립니다. 단 하나의 유출된 비밀번호로도 계정 탈취, 데이터 도용, 평판 손상으로 이어질 수 있습니다.
강력한 비밀번호 정책과 2FA는 공격자의 진입 장벽을 크게 높입니다. 두 가지를 올바르게 수행하는 방법을 살펴보겠습니다.
비밀번호 저장: 올바른 해싱
비밀번호를 절대 평문으로 저장하지 마세요. 항상 비밀번호용으로 설계된 강력하고 느린 알고리즘으로 해싱하세요. SHA-256과 같은 빠른 해시는 신속한 무차별 대입 공격을 가능하게 하므로 부적합합니다.
권장 알고리즘:
- Argon2id – Password Hashing Competition 우승자로, GPU 및 부채널 공격에 강합니다.
- bcrypt – 널리 지원되고 검증되었으며 설계상 느립니다.
- scrypt – 메모리 하드 방식으로 하드웨어 가속 공격 방지에 좋습니다.
Node.js에서 bcrypt 사용 예시:
const bcrypt = require('bcrypt');
const saltRounds = 12;
async function hashPassword(password) {
return await bcrypt.hash(password, saltRounds);
}
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
항상 비밀번호마다 고유한 솔트를 사용하세요(대부분의 라이브러리가 자동으로 처리합니다). 해싱이 서버에서 약 100-500ms 걸리도록 비용 계수를 조정하세요.
실제로 도움이 되는 비밀번호 정책
"기호와 숫자를 반드시 포함해야 한다"와 같은 복잡한 규칙은 잊으세요. 현대 지침(NIST SP 800-63B)은 다음을 권장합니다:
- 최소 8자 이상, 하지만 패스프레이즈(예: 4개 이상의 무작위 단어)를 권장하세요.
- 유출된 비밀번호 목록(예: Have I Been Pwned API)과 대조하세요.
- 침해가 의심되지 않는 한 주기적 강제 만료는 하지 마세요.
- 모든 인쇄 가능한 ASCII 문자와 공백을 허용하세요.
- 무차별 대입을 방지하기 위해 로그인 시도를 속도 제한하세요.
정당한 비밀번호를 차단하지 않으면서 피드백을 제공하는 간단한 강도 측정기를 구현하세요.
2단계 인증(2FA) 방법
2FA는 두 번째 요소를 추가합니다: 가지고 있는 것(휴대폰, 하드웨어 키) 또는 본인 자체(생체 인식). 보안 순으로 나열한 일반적인 방법은 다음과 같습니다:
| 방법 | 보안 | 사용성 |
|---|---|---|
| 하드웨어 보안 키(WebAuthn/FIDO2) | 높음 | 중간 |
| 인증 앱(TOTP) | 높음 | 높음 |
| 푸시 알림 | 중간 | 높음 |
| SMS 코드 | 낮음 | 높음 |
SMS는 SIM 스와핑과 가로채기에 취약합니다. 가능하면 TOTP 또는 WebAuthn을 선호하세요.
TOTP 기반 2FA 구현
TOTP(시간 기반 일회용 비밀번호)는 보안과 사용성의 훌륭한 균형입니다. 대략적인 워크플로는 다음과 같습니다:
- 사용자를 위한 비밀 키를 생성합니다(예: 32개의 무작위 바이트, base32 인코딩).
- otpauth:// URI를 생성하고 사용자가 인증 앱으로 스캔할 QR 코드를 표시합니다.
- 2FA를 활성화하기 전에 사용자가 유효한 코드를 생성할 수 있는지 확인합니다.
- 비밀 키를 안전하게 저장합니다(저장 시 암호화).
- 계정 복구를 위한 백업 코드를 제공합니다.
로그인 중 비밀번호 확인 후 TOTP 코드를 요청하세요. otplib 또는 speakeasy 같은 라이브러리로 검증하세요.
const { authenticator } = require('otplib');
// Generate secret
const secret = authenticator.generateSecret();
// Verify token
const isValid = authenticator.verify({ token: userToken, secret });
시계 오차를 고려하여 작은 시간 창(예: ±1 간격)을 허용하세요.
WebAuthn: 인증의 미래
WebAuthn(FIDO2의 일부)은 하드웨어 키 또는 플랫폼 인증기(Touch ID, Windows Hello)를 사용한 비밀번호 없는 및 2차 요소 인증을 가능하게 합니다. 자격 증명이 오리진에 바인딩되므로 피싱에 강합니다.
WebAuthn 구현에는 다음이 필요합니다:
- 서버 측 챌린지 생성 및 검증.
- WebAuthn API를 사용하는 클라이언트 측 JavaScript.
- 각 사용자의 공개 키와 자격 증명 ID 저장.
SimpleWebAuthn 같은 라이브러리가 이를 더 쉽게 만듭니다. TOTP보다 복잡하지만 WebAuthn은 가장 강력한 보호를 제공합니다.
속도 제한 및 계정 잠금
2FA가 있더라도 비밀번호에 대한 무차별 대입 공격을 방지해야 합니다. 다음을 구현하세요:
- IP 및 계정별 속도 제한(예: 분당 5회 시도).
- 실패한 시도 후 지수 백오프.
- 반복된 실패 후 계정 잠금, 안전한 잠금 해제 절차(예: 이메일 확인) 포함.
- 몇 번의 실패 후 CAPTCHA.
정당한 사용자를 영구적으로 잠그지 않도록 주의하세요. 임시 잠금을 사용하고 사용자에게 알리세요.
안전한 비밀번호 재설정
비밀번호 재설정 흐름은 일반적인 공격 벡터입니다. 다음 규칙을 따르세요:
- 이메일로 전송되는 일회용, 시간 제한 토큰을 사용하세요.
- 시스템에 이메일이 존재하는지 여부를 드러내지 마세요.
- 이미 로그인한 경우 사용자에게 재인증을 요구하세요.
- 비밀번호 재설정 후 모든 세션을 무효화하세요.
사용자를 비난하지 않고 교육하기
보안은 공동의 책임입니다. 명확한 지침을 제공하세요:
- 비밀번호 관리자를 사용하여 고유한 비밀번호를 생성하고 저장하세요.
- 가능한 모든 곳에서 2FA를 활성화하세요.
- 피싱 시도에 주의하세요.
앱 내 팁과 전용 보안 페이지가 도움이 될 수 있습니다.
FAQ
SMS 기반 2FA가 2FA 없음보다 나은가요?
네, SMS 2FA는 아무것도 없는 것보다 낫지만 SIM 스와핑과 가로채기에 취약합니다. 더 강력한 보안을 위해 TOTP 또는 WebAuthn을 선호하세요.
비밀번호 해싱 알고리즘을 얼마나 자주 교체해야 하나요?
취약점이 발견되지 않는 한 교체할 필요는 없습니다. 그러나 매개변수를 업그레이드할 때(예: bcrypt 비용 증가) 성공적인 로그인 시 비밀번호를 재해싱할 수 있습니다.
OAuth 또는 소셜 로그인과 함께 2FA를 사용할 수 있나요?
네, 하지만 2FA는 일반적으로 ID 제공자가 관리합니다. 소셜 로그인을 사용하는 경우 자체 계정 복구 프로세스가 안전한지 확인하세요.
애플리케이션의 보안을 강화할 준비가 되셨나요? 오늘 강력한 비밀번호 해싱과 TOTP 기반 2FA를 구현하는 것부터 시작하세요. 더 많은 개발자 도구를 원하시면 JSON Formatter를 확인하여 API 응답을 빠르게 디버깅하세요.