비밀번호 생성 및 검증 도구
강력한 비밀번호와 암호 문구를 생성하고, 일괄 생성, 고유성, 강도 및 정책 검증을 지원하며, 다국어 코드 예제와 실용적인 지식을 제공합니다.
비밀번호 해시 빠른 참조 가이드
Argon2id
- Argon2id를 사용하고 적절한 메모리 비용 파라미터를 설정하세요
- 반복 횟수 ≥ 2회, 메모리 ≥ 64MB (환경에 따라 조정)
- 사용자별로 독립적인 솔트를 저장; 애플리케이션 레이어에서 페퍼 사용을 고려하세요
PBKDF2
- SHA-256 또는 SHA-512를 선택하고 반복 횟수 ≥ 210,000회 (필요에 따라 조정)
- 각 해시에 고유한 솔트를 사용; 파라미터 업그레이드를 지원하세요
- 다음 로그인 시 더 높은 비용 파라미터로 전환하세요
BCrypt
- 비용 값 10–14, 서버 성능에 맞게 조정
- 비밀번호 자르기 문제를 피하고 전체 비밀번호를 해싱하세요
- 인증 엔드포인트에 요청 제한 및 모니터링을 적용하세요
참조: NIST SP 800-63B, OWASP ASVS. 파라미터는 하드웨어 성능과 SLO와 일치해야 합니다.
비밀번호 강도 평가
강도는 엔트로피를 기반으로 추정됩니다: entropy = log2(문자 집합 크기) × 길이. 더 큰 문자 집합과 더 긴 길이는 추측 저항성을 높입니다.
- 약함: < 50 비트 —— 일회성 또는 저가치 시나리오에만 적합
- 보통: 50–80 비트 —— 낮은 위험 환경에서 허용 가능
- 강함: 80–110 비트 —— 권장 기본 목표
- 매우 강함: > 110 비트 —— 관리자 또는 핵심 계정에 적합
참고: 실제 공격 모델은 차이가 있을 수 있습니다. 비밀번호 재사용을 피하고 다중 요소 인증(MFA)을 활성화하세요.
사용 방법
- 길이와 문자 집합(소문자/대문자/숫자/기호)을 선택하세요. 필요 시 '유사 문자 제외' 및 '각 항목 필수 포함'을 활성화하세요.
- 더 세밀한 제어가 필요할 경우: 특정 문자 또는 문자 그룹을 제외하거나 기호 그룹을 선택하세요.
- 생성 버튼을 클릭하세요. 여러 결과가 필요하면 일괄 생성을 활성화하고 한 번에 모두 복사할 수 있습니다.
- 기존 비밀번호를 검증하려면 '정책 테스터'를, 기억하기 쉬운 암호 문구를 원하면 '암호 문구 생성기'를 사용하세요.
기능 특징
- 보안 무작위 소스(Web Crypto)
- 설정 가능한 문자 집합 및 기호 그룹
- 유사 문자 필터링 및 사용자 정의 제외
- 일괄 생성, 고유성 보장 및 중복 제거 통계
- 강도 및 엔트로피 지표
- 정책 테스터 및 암호 문구 생성기
- 다국어 코드 예제 (JS、Python、PHP、Go、Java、C#、Rust)
- 한 번에 복사 (단일/모두)
비밀번호 지식 기반
1) 비밀번호 강도와 엔트로피
- 엔트로피 ≈ log2(문자 집합 크기) × 길이; 길이가 더 큰 영향을 미칩니다
- 목표 권장값: 일반 계정 ≥ 80비트; 고권한/금융 계정 ≥ 110비트
- 더 큰 문자 집합 + 더 긴 길이 → 추측 저항성 향상
2) 길이 대 복잡도
- 기호를 무분별하게 추가하는 것보다 길이를 늘리는 것이 더 효과적입니다
- 예측 가능한 패턴을 피하세요 (예: 항상 ‘첫 글자 대문자 + 끝에 숫자!’)
- 충분한 길이를 확보한 후, 문자 다양성을 적절히 높이는 것이 좋습니다
3) 흔한 오류 및 역패턴
- 키보드 시퀀스(qwerty), 반복 구간, 생일/연도 등은 규칙 기반 공격에 쉽게 노출됩니다
- ‘기본 비밀번호 + 웹사이트 접미사’ 방식은 변형 재사용으로, 위험이 집중되고 예측하기 쉽습니다
- 여러 사이트에서 동일한 비밀번호를 사용하지 마세요
4) 비밀번호 관리 권장사항
- 비밀번호 관리자를 사용하고, 각 사이트마다 고유한 비밀번호를 설정하며, 중요한 계정에는 MFA를 활성화하세요
- 공개 채널을 통해 비밀번호를 평문으로 전송하지 마세요. 필요한 경우 발음하기 쉬운 암구를 사용하세요
- 누설되거나 재사용된 경우 즉시 변경하고, 고유하게 관리하세요
5) 암구(Passphrase) 가이드
- 4~6개 단어로 구성된 조합은 보통 강력하면서도 기억하기 쉽습니다
- 구분 기호 혼용, 첫 글자 대문자, 숫자 삽입 등을 통해 강도와 가독성을 동시에 향상시키세요
- 흔한 문구, 가사, 명언을 그대로 연결하지 마세요
비밀번호 보안 실천 가이드
최선의 실천 방법
- 충분한 길이를 사용하세요: 일반 계정은 16자 이상, 핵심 계정은 24자 이상을 권장합니다.
- 기억이 필요한 경우 암호 문구를 우선적으로 사용하고, 무작위 고강도 암호는 암호 관리자를 통해 저장하세요.
- 가능하면 다중 요소 인증(MFA)을 활성화하세요.
- 다른 웹사이트에서 비밀번호를 재사용하지 말고, 모든 계정에 고유한 비밀번호를 사용하세요.
엔트로피와 강도
엔트로피는 길이와 문자 집합 크기에 기반한 예측 불가능성을 나타내며, 엔트로피 비트 수가 높을수록 일반적으로 더 강력합니다.
- 최대 효과를 얻으려면 길이를 늘리는 것을 우선시하세요.
- 가능한 경우 다양한 문자 집합을 사용하세요.
- 과도하게 문자를 제외하면 문자 집합이 줄어들어 강도가 낮아집니다.
정책 및 주기적 변경
- 복잡한 구성 규칙보다는 길이와 일반적/유출된 암호를 차단하는 블랙리스트를 선호하세요.
- 빈번한 강제 주기적 변경은 피하고, 유출이나 위험이 발생할 때만 변경하세요.
- 유출된 암호 목록을 사용하여 일반적/유출된 암호를 차단하세요.
암호 문구
- 4~6개의 무작위 단어를 구분자로 연결하여 사용하세요. 예: lake-CARROT-planet_7
- 유명한 문장이나 가사 같은 일반적인 문구를 피하세요. '지혜로움'보다 무작위성이 더 중요합니다.
- 핵심 계정에는 암호 관리자를 사용하여 진정으로 고엔트로피의 무작위 암호를 저장하세요.
생성 설정 팁
- "선택한 각 세트를 필수로 포함" 옵션은 각 유형이 최소 한 번 이상 포함되도록 보장합니다.
- "유사한 문자 피하기"는 가독성을 향상시키지만 문자 집합 크기를 약간 줄입니다.
- 기호는 대상 시스템에서 허용되는 하위 집합으로 제한할 수 있습니다.
서버 측 저장
- 절대 평문으로 저장하지 마세요. 강력한 해시(Argon2id/PBKDF2/BCrypt)를 사용하고 솔트를 적용하세요.
- 적절한 매개변수(메모리/시간/비용)를 설정하고 필요시 피퍼를 사용하세요.
- 속도를 제한하고 실패 시도를 모니터링하세요. 공격이 감지되면 CAPTCHA 또는 장치 검증을 추가하세요.
다중 요소 및 복구
- TOTP/하드웨어 키를 우선 사용하고, SMS는 가능한 한 피하세요.
- 복구 프로세스를 보호하세요: 다중 요소 또는 이메일 인증을 사용하고 쿨다운 기간을 설정하세요.
- 백업 복구 코드를 제공하고 사용자가 안전하게 보관하도록 권장하세요.
브루트 포스 공격 방어
- 점진적인 지연/잠금 및 IP/장치 리스크 점수를 사용하세요.
- API 및 로그인 폼에 WAF/요청 제한을 구성하세요.
- 사용자 계정 정보 유출 시도를 감지하고 고유한 비밀번호 사용을 장려하세요.
로컬 저장 및 처리
- 신뢰할 수 있는 암호 관리자를 사용하여 저장하고 자동 채우기를 수행하세요.
- 비밀번호를 채팅이나 이메일로 평문으로 공유하지 마세요. 팀은 비밀 관리 도구를 사용하세요.
- 오프라인으로 기록해야 하는 경우 물리적 보안을 확보하세요.
공지사항: 이 도구는 브라우저 내부에서 Web Crypto를 사용하여 로컬로 비밀번호를 생성하며, 데이터를 서버로 전송하지 않습니다.
프로그래밍 언어로 비밀번호 생성하는 방법
JavaScript(Web Crypto)
function randomPassword(length = 16, sets = {lower:true, upper:true, digits:true, symbols:true}) {
const pools = {
lower: 'abcdefghijklmnopqrstuvwxyz',
upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
digits: '0123456789',
symbols: '!@#$%^&*+-=_~`|/?()[]{}<>,.;:\'\"'
};
let pool = '';
for (const k of Object.keys(sets)) if (sets[k]) pool += pools[k];
if (!pool) throw new Error('No charset');
const bytes = new Uint32Array(length);
crypto.getRandomValues(bytes);
let out = '';
for (let i = 0; i < length; i++) out += pool[bytes[i] % pool.length];
return out;
}
Python(secrets)
import secrets
def random_password(length=16, lower=True, upper=True, digits=True, symbols=True):
pools = {
'lower': 'abcdefghijklmnopqrstuvwxyz',
'upper': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'digits': '0123456789',
'symbols': '!@#$%^&*+-=_~`|/?()[]{}<>,.;:\'\"'
}
pool = ''.join(v for k, v in pools.items() if locals()[k])
if not pool:
raise ValueError('No charset')
return ''.join(secrets.choice(pool) for _ in range(length))
PHP(random_int)
function random_password($length = 16, $sets = ['lower'=>true,'upper'=>true,'digits'=>true,'symbols'=>true]) {
$pools = [
'lower' => 'abcdefghijklmnopqrstuvwxyz',
'upper' => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'digits' => '0123456789',
'symbols' => '!@#$%^&*+-=_~`|/?()[]{}<>,.;:\'\"'
];
$pool = '';
foreach ($sets as $k => $on) if ($on) $pool .= $pools[$k];
if ($pool === '') throw new Exception('No charset');
$out = '';
for ($i = 0; $i < $length; $i++) {
$out .= $pool[random_int(0, strlen($pool)-1)];
}
return $out;
}
Go(crypto/rand)
package main
import (
"crypto/rand"
"math/big"
)
func RandomPassword(length int, pool string) (string, error) {
out := make([]byte, length)
for i := 0; i < length; i++ {
nBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(pool))))
if err != nil { return "", err }
out[i] = pool[nBig.Int64()]
}
return string(out), nil
}
Java(SecureRandom)
import java.security.SecureRandom;
public class Pw {
static final String POOL = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*+-=_~`|/?()[]{}<>,.;:'\"";
static final SecureRandom SR = new SecureRandom();
static String randomPassword(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int idx = SR.nextInt(POOL.length());
sb.append(POOL.charAt(idx));
}
return sb.toString();
}
}
C#(.NET RandomNumberGenerator)
using System;
using System.Security.Cryptography;
public static class Pw {
public static string RandomPassword(int length, string pool) {
using var rng = RandomNumberGenerator.Create();
var bytes = new byte[length];
rng.GetBytes(bytes);
var chars = new char[length];
for (int i = 0; i < length; i++) {
chars[i] = pool[bytes[i] % pool.Length];
}
return new string(chars);
}
}
Rust(rand + getrandom)
use rand::rngs::OsRng;
use rand::RngCore;
fn random_password(length: usize, pool: &str) -> String {
let mut bytes = vec![0u8; length];
OsRng.fill_bytes(&mut bytes);
let chars: Vec = pool.chars().collect();
bytes
.iter()
.map(|b| chars[(*b as usize) % chars.len()])
.collect()
}
fn main() {
let pool = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*+-=_~`|/?()[]{}<>,.;:'\"";
let pw = random_password(16, pool);
println!("{}", pw);
}