비밀번호 관련 조언 중 상당수는 틀렸거나 비현실적입니다. “대문자, 소문자, 숫자, 특수문자를 섞어 쓰세요”는 엄격해 보이지만 P@ssw0rd1! 같은 비밀번호를 만들어냅니다. 컴퓨터는 쉽게 크랙하는데 사람은 기억하기 어려운 비밀번호입니다. 이 글에서는 진짜 중요한 것을 정리합니다.

비밀번호를 실제로 강하게 만드는 것

비밀번호 강도의 본질은 엔트로피입니다 — 공격자가 비밀번호를 찾는 데 필요한 추측 횟수입니다.

Tr0ub4dor&3 같은 12자 비밀번호는 복잡해 보입니다. 하지만 일반적인 패턴(단어 + 숫자 치환 + 기호)을 따르기 때문에, 규칙 기반 변형을 사용하는 사전 공격으로 무작위 8자 비밀번호보다 빠르게 크랙될 수 있습니다.

진정한 무작위성은 복잡해 보이는 패턴보다 항상 강합니다.

비밀번호 엔트로피

엔트로피는 비트로 예측 불가능성을 측정합니다. 비트가 하나 늘어날 때마다 탐색 공간이 두 배가 됩니다.

N개 문자 풀에서 길이 L의 비밀번호를 선택할 때:

엔트로피 = L × log₂(N)
문자 풀풀 크기12자 엔트로피
소문자만2656비트
소문자 + 대문자5268비트
소+대+숫자6272비트
소+대+숫자+특수문자(32개)9479비트
패스프레이즈 (7776 단어 목록)7776~51비트/단어

참고 기준:

  • 50비트 미만: 현대 하드웨어로 초~시간 내 크랙 가능
  • 50~70비트: 속도 제한이 있는 저위험 계정에는 적절
  • 70비트 이상: 대부분의 목적에 강력
  • 100비트 이상: 미래 대비

강력한 비밀번호 생성하기

ZeroTool 비밀번호 생성기 →

브라우저의 crypto.getRandomValues() API를 사용해 암호학적으로 무작위한 비밀번호를 생성합니다. 길이, 문자셋, 개수 옵션을 제공합니다. 서버로 아무것도 전송되지 않습니다.

비밀번호 생성기의 작동 원리

안전한 비밀번호 생성기는 반드시 **암호학적으로 안전한 난수 생성기(CSPRNG)**를 사용해야 합니다. 표준 RNG와는 다릅니다.

// 잘못된 방법: Math.random()은 암호학적으로 안전하지 않음
// 공격자가 시드를 알면 모든 출력을 예측할 수 있음
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let password = '';
for (let i = 0; i < 16; i++) {
    password += chars[Math.floor(Math.random() * chars.length)];
}

// 올바른 방법: crypto.getRandomValues()는 암호학적으로 안전함
function generatePassword(length = 16, charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*') {
    const array = new Uint32Array(length);
    crypto.getRandomValues(array);
    return Array.from(array, n => charset[n % charset.length]).join('');
}

Python에서:

import secrets
import string

def generate_password(length=16, use_symbols=True):
    alphabet = string.ascii_letters + string.digits
    if use_symbols:
        alphabet += string.punctuation

    return ''.join(secrets.choice(alphabet) for _ in range(length))

# secrets 모듈은 os.urandom()을 사용 — 암호학적으로 안전
password = generate_password(20)

주의: Python의 random.choice()는 암호학적으로 안전하지 않습니다. 보안 관련 무작위성에는 항상 secrets.choice()를 사용하세요.

패스프레이즈: 무작위 문자열의 대안

패스프레이즈는 무작위 사전 단어들의 조합입니다:

correct-horse-battery-staple
violet-mountain-cloud-seven-river

7776단어 목록(Diceware)에서 각 단어는 약 12.9비트의 엔트로피를 추가합니다. 4단어 ~52비트, 5단어 ~64비트, 6단어 ~77비트입니다.

장점:

  • 모바일에서 입력하기 더 쉬움
  • 가끔 기억해야 할 때 더 기억하기 쉬움
  • 화면에서 읽을 때 오독 가능성이 낮음

단점:

  • 더 길어 타이핑이 많음
  • 일부 시스템의 문자 제한에 걸릴 수 있음

길이 vs 복잡도

둘 중 하나를 선택해야 한다면 더 길게를 선택하세요. 문자 하나를 추가하면 탐색 공간이 풀 크기(26~94배)만큼 곱해집니다. 이미 소+대+숫자가 있을 때 특수문자를 추가하는 것은 생각보다 효과가 적습니다(16자 비밀번호에서 3비트 미만 추가).

실용적 권장 사항: 혼합 문자 클래스로 최소 16자 이상 사용하세요.

비밀번호는 외우면 안 됩니다

모든 계정에 고유한 무작위 비밀번호를 생성한다면(해야 합니다), 기억할 수 없습니다. 비밀번호 관리자가 존재하는 이유입니다.

비밀번호 관리자는 모든 비밀번호를 하나의 마스터 비밀번호로 암호화해 저장합니다. 강력한 패스프레이즈 하나만 기억하고 나머지는 관리자에게 맡기세요.

좋은 선택지:

  • Bitwarden — 오픈소스, 무료 티어 우수, 자체 호스팅 가능
  • 1Password — 세련된 UX, 강력한 팀 공유 기능
  • KeePassXC — 로컬 전용, 클라우드 의존성 없음

비밀번호를 절대 재사용하지 마세요. 데이터 유출 하나가 재사용한 모든 계정을 노출시킵니다.

개발자용: 애플리케이션 시크릿 생성

API 키, JWT 시크릿, 세션 키, 암호화 키 같은 애플리케이션 시크릿에는 플랫폼의 보안 무작위 바이트 함수를 사용하세요:

# Shell — 32바이트 무작위를 16진수로 (대부분의 시크릿에 적합)
openssl rand -hex 32

# Shell — base64 인코딩
openssl rand -base64 32
import secrets

# 32바이트 16진수 시크릿 (64자)
api_key = secrets.token_hex(32)

# URL 안전 base64 시크릿
session_secret = secrets.token_urlsafe(32)
// Node.js
import { randomBytes } from 'crypto';
const apiKey = randomBytes(32).toString('hex');

JWT 시크릿에는 최소 256비트(32바이트)를 사용하세요. AES-256에는 정확히 32바이트가 필요합니다. 사람이 기억할 수 있는 문자열을 암호화 키로 절대 사용하지 마세요.

Have I Been Pwned 확인

Have I Been Pwned(HIBP) API를 사용하면 실제 비밀번호를 전송하지 않고도 비밀번호가 알려진 침해 데이터베이스에 있는지 확인할 수 있습니다:

  1. 비밀번호의 SHA-1 해시를 계산
  2. 해시의 앞 5자만 API에 전송
  3. API는 해당 5자로 시작하는 모든 해시를 반환
  4. 로컬에서 전체 해시가 목록에 있는지 확인
import hashlib
import requests

def is_pwned(password):
    sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
    prefix, suffix = sha1[:5], sha1[5:]

    response = requests.get(f'https://api.pwnedpasswords.com/range/{prefix}')
    hashes = (line.split(':') for line in response.text.splitlines())
    return any(h == suffix for h, _ in hashes)

if is_pwned("P@ssw0rd1"):
    print("이 비밀번호는 알려진 침해 데이터에 포함되어 있습니다!")

HIBP 데이터베이스에 있는 비밀번호는 겉보기 복잡도와 무관하게 절대 사용하면 안 됩니다.

요약

목표권장 방법
무작위 비밀번호16자 이상, 혼합 문자셋
기억 가능한 패스프레이즈Diceware에서 6개 이상의 무작위 단어
애플리케이션 시크릿secrets.token_hex(32) 또는 openssl rand -hex 32
비밀번호 저장Argon2id (최소 3회 반복, 64MB 메모리)
침해 확인Have I Been Pwned API
일상 비밀번호 관리Bitwarden 또는 1Password

엔트로피, 무작위성, 고유성 — 임의적인 복잡도 규칙이 아닌 이것들이 비밀번호를 안전하게 만듭니다.

ZeroTool로 암호학적으로 강력한 비밀번호 생성하기 →