bcrypt, AES, RSA/ECDSA 등 실무에서 쓰는 암호화 방식의 원리와 적용법을 정리한다. 해시·대칭키·비대칭키의 차이, 사용 시나리오, 키 관리와 Node.js 구현 예시를 포함한다.
한 줄 요약: 비밀번호는 bcrypt로 해시, 데이터는 AES로 암호화, 서명과 키 교환은 RSA/ECDSA — 각 도구가 해결하는 문제가 다르다. 상황에 맞는 암호화를 써야 한다.
암호화를 잘 몰라도 개발은 가능하다. 하지만 잘못된 암호화는 아예 안 하는 것보다 더 위험하다. MD5로 비밀번호를 해시하거나 ECB 모드로 AES를 쓰는 것처럼 — 겉으로는 암호화처럼 보이지만 실제로는 무방비 상태다. 이 글은 해시, 대칭키, 비대칭키 암호화를 개발자 관점에서 실용적으로 정리한다.
해시 함수 — 비밀번호는 이렇게 저장한다
해시는 단방향 함수다. 입력에서 고정 길이 출력을 만들고, 출력에서 원래 입력을 복원할 수 없다. 비밀번호 저장에 적합하다. 데이터를 복호화할 필요가 없고, 검증만 하면 되기 때문이다.
해시 알고리즘 선택 기준
알고리즘
용도
비밀번호 저장
비고
MD5
파일 무결성(레거시)
절대 사용 금지
충돌 취약, GPU 초당 수십억 해시
SHA-256
데이터 무결성, JWT 서명
단독 사용 금지
빠름 → 레인보우 테이블 취약
bcrypt
비밀번호 저장
권장
salt 자동 포함, cost factor 조정 가능
Argon2id
비밀번호 저장
최신 권장
메모리 사용 → GPU 공격 어렵게 설계
PBKDF2
비밀번호 저장, 키 유도
허용
반복 횟수 충분히 설정 필요(600,000+)
개발자를 위한 암호화 기초 — 해시, 대칭키, 비대칭키 — 보안 아키텍처 다이어그램 (출처: 공식 문서 및 벤치마크 데이터 기반)
bcrypt와 Argon2 비밀번호 해시 예시 (Node.js)
const bcrypt = require('bcrypt');
const argon2 = require('argon2');
// bcrypt — cost factor 12 권장 (2026년 기준)
async function hashPasswordBcrypt(password) {
const saltRounds = 12; // 값이 클수록 느려짐 (2^12 반복)
return await bcrypt.hash(password, saltRounds);
}
async function verifyPasswordBcrypt(password, hash) {
return await bcrypt.compare(password, hash);
// hash 내부에 salt가 포함되어 있어 별도 저장 불필요
}
// Argon2id — 현재 OWASP 최우선 권장
async function hashPasswordArgon2(password) {
return await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 64 * 1024, // 64MB
timeCost: 3, // 반복 횟수
parallelism: 4
});
}
async function verifyPasswordArgon2(password, hash) {
return await argon2.verify(hash, password);
}
// 사용
const hash = await hashPasswordArgon2(req.body.password);
await db.query('INSERT INTO users (email, password_hash) VALUES ($1, $2)', [email, hash]);
주의: bcrypt는 입력을 72바이트로 자른다. 72자 이상의 비밀번호는 73번째 문자부터 해시에 반영되지 않아 보안 수준이 낮아진다. 72자 이상 비밀번호를 지원해야 한다면 먼저 SHA-256으로 해시한 뒤 bcrypt를 적용하거나(pepper+bcrypt 패턴), Argon2를 사용한다.
대칭키 암호화 — AES로 데이터를 잠근다
대칭키 암호화는 같은 키로 암호화와 복호화를 모두 한다. 속도가 빠르고 대용량 데이터에 적합하다. AES(Advanced Encryption Standard)가 현재 표준이다.
AES 모드 선택
모드
특징
권장 여부
ECB
블록 독립 암호화 — 같은 평문 = 같은 암호문
절대 사용 금지
CBC
IV(초기화 벡터) 필요, 패딩 필요
조건부 허용 (GCM 권장)
GCM
인증 포함 (AEAD), IV 필요
현재 표준 권장
ChaCha20-Poly1305
모바일/임베디드 최적화
권장 (AES 가속 없는 환경)
AES-256-GCM을 쓰면 암호화와 무결성 검증(변조 탐지)을 동시에 처리한다. CBC 모드는 패딩 오라클 공격에 취약하므로 GCM을 우선한다.
개발자를 위한 암호화 기초 — 해시, 대칭키, 비대칭키 — 위협 모델 시각화 (출처: 공식 문서 및 벤치마크 데이터 기반)
AES-256-GCM 암호화/복호화 예시 (Node.js crypto)
const crypto = require('crypto');
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32; // 256 bits
// 키 생성 (환경 변수에서 로드하거나 KMS 사용)
// const key = crypto.randomBytes(KEY_LENGTH); // 생성 예시
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
function encrypt(plaintext) {
const iv = crypto.randomBytes(12); // GCM에는 12바이트 IV 권장
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// iv + authTag + ciphertext를 함께 저장
return {
iv: iv.toString('hex'),
authTag: authTag.toString('hex'),
data: encrypted
};
}
function decrypt(encryptedObj) {
const { iv, authTag, data } = encryptedObj;
const decipher = crypto.createDecipheriv(
ALGORITHM,
key,
Buffer.from(iv, 'hex')
);
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
let decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8'); // authTag 검증 실패 시 여기서 에러
return decrypted;
}
비대칭키 암호화 — RSA와 ECDSA의 역할
비대칭키 암호화는 공개키(Public Key)와 비밀키(Private Key)를 사용한다. 공개키로 암호화하면 비밀키로만 복호화 가능하고, 비밀키로 서명하면 공개키로 검증 가능하다.
RSA vs ECDSA 비교
항목
RSA
ECDSA
키 길이
2048~4096 bits
256~384 bits (동급 보안)
속도
상대적으로 느림
빠름
키 크기
큼
작음 (모바일 유리)
주요 용도
TLS 키 교환, 암호화
디지털 서명, JWT, TLS
호환성
광범위
최신 환경 권장
실무 사용 시나리오
JWT 서명: 단일 서버라면 HS256(대칭, HMAC-SHA256). 마이크로서비스처럼 여러 서비스가 검증해야 하면 RS256 또는 ES256(비대칭)이 적합 — 검증 서비스에 비밀키를 배포하지 않아도 됨.
데이터 암호화 + 전송: 대용량 데이터는 AES-256-GCM으로 암호화하고, AES 키 자체를 RSA로 암호화하는 하이브리드 방식 사용.
코드 서명: 배포 아티팩트의 무결성 검증에 ECDSA 사용.
TLS — 전송 구간 보안의 기반
TLS(Transport Layer Security)는 앞서 설명한 암호화 기법들을 조합해 네트워크 전송을 보호한다. HTTPS의 보안 계층이 TLS다.
TLS 핸드셰이크 간략 흐름
클라이언트가 지원 가능한 암호화 스위트 목록 전송
서버가 인증서(공개키 포함) 전송 + 암호화 스위트 선택
클라이언트가 인증서 유효성 확인 (CA 서명 검증)
키 교환 — ECDH로 세션 키(대칭키) 협상
이후 통신은 협상된 대칭키(AES-GCM)로 암호화
개발자가 확인해야 할 TLS 설정
TLS 1.2 이상만 허용 — TLS 1.0, 1.1, SSL 비활성화
약한 암호화 스위트 비활성화 — RC4, DES, 3DES, MD5 기반 스위트 제거
인증서 체인 완전성 확인 — 중간 CA 인증서가 누락되면 일부 클라이언트에서 실패
OCSP Stapling 활성화 — 인증서 상태 확인 속도 향상
개발자를 위한 암호화 기초 — 해시, 대칭키, 비대칭키 — 취약점 분석 플로우차트 (출처: 공식 문서 및 벤치마크 데이터 기반)
팁: 암호화 키는 코드나 git 저장소에 절대 저장하지 않는다. 프로덕션에서는 AWS KMS, HashiCorp Vault, GCP Secret Manager 같은 전용 키 관리 서비스를 사용한다. 키 로테이션 일정(90~180일)을 자동화하고, 키 접근 로그를 모니터링하는 것이 운영 보안의 기본이다.
실무 적용 가이드 — 상황별 암호화 선택
어떤 암호화를 써야 하는지 상황별로 정리한다.
상황
권장 방법
비고
비밀번호 저장
Argon2id 또는 bcrypt(cost 12+)
SHA-256 단독 절대 금지
개인정보 DB 저장
AES-256-GCM
키는 KMS 관리
JWT 서명 (단일 서버)
HS256 (HMAC-SHA256)
비밀키 32바이트 이상
JWT 서명 (마이크로서비스)
RS256 또는 ES256
공개키만 배포, 비밀키 중앙 관리
파일 무결성 확인
SHA-256 또는 SHA-3
MD5 사용 금지
API 키 생성
crypto.randomBytes(32)
Math.random() 절대 금지
네트워크 전송 보안
TLS 1.3 + AES-256-GCM
인증서 자동 갱신 설정
암호화 라이브러리는 직접 구현하지 않는다. 검증된 라이브러리(Node.js 기본 crypto 모듈, libsodium, OpenSSL 기반 라이브러리)를 사용한다. 직접 구현한 암호화 알고리즘은 미묘한 버그 하나가 전체 보안을 무너뜨린다.
참고: 양자 컴퓨터 시대를 준비한 Post-Quantum Cryptography(PQC) 표준이 2024년 NIST에서 확정됐다. CRYSTALS-Kyber(키 교환)와 CRYSTALS-Dilithium(서명)이 주요 후보다. 대부분의 서비스는 아직 적용하지 않아도 되지만, 금융/의료/국방 관련 장기 데이터는 지금부터 준비를 시작할 시점이다.