본문으로 건너뛰기
HexaTransfer
블로그로 돌아가기
암호화 및 보안

키 파생 함수: PBKDF2, Argon2, scrypt 비교

비밀번호 기반 암호화를 위한 키 파생 함수를 비교하세요. PBKDF2, Argon2, scrypt의 강점과 트레이드오프.

2026년 비밀번호 파생 암호화 키에는 Argon2id가 권장됩니다. Password Hashing Competition 우승자이자 OWASP 최우선 선택으로, GPU와 ASIC 공격자에 적극적으로 방어합니다. scrypt는 메모리 경도가 있고 암호화폐에 널리 배포된 탄탄한 두 번째 선택입니다. PBKDF2-SHA-256는 600,000회 이상의 반복으로 호환성 측면에서는 여전히 허용되지만 GPU 저항이 최소 수준입니다. 사용자가 비밀번호를 입력해 공유 파일을 보호하는 파일 전송 앱에서는 반복 3회, 메모리 64 MiB, 병렬성 4의 Argon2id가 현대적 기준선입니다. PBKDF2는 Web Crypto API에 내장되어 있고 WASM 의존성이 필요 없어 살아남습니다. 세 가지의 차이와 각각의 적합한 상황을 설명합니다.

비교표

| 속성 | PBKDF2 | scrypt | Argon2id | |---|---|---|---| | 도입 연도 | 2000 (RFC 2898) | 2009 (RFC 7914) | 2015 (PHC 우승) | | 메모리 경도 | 없음 | 있음 | 있음 | | 매개변수 유연성 | 반복 횟수만 | N, r, p | 시간, 메모리, 병렬성 | | GPU 저항 | 약함 | 보통 | 강함 | | ASIC 저항 | 매우 약함 | 보통 | 강함 | | 브라우저 네이티브 (Web Crypto) | 예 | 아니오 | 아니오 | | OWASP 2024 권장 | 허용 가능한 폴백 | 허용 가능 | 선호 | | 일반적인 브라우저 비용(현대 하드웨어) | 600,000회 ≈ 500ms | N=2^17 ≈ 800ms | 3회, 64 MiB ≈ 1초 |

메모리 경도가 중요한 이유

비밀번호 기반 암호화의 위협 모델은 오프라인 브루트 포스입니다. 공격자가 암호문과 솔트를 확보하고, 비밀번호 사전을 순회하며, 복호화에 성공하는 키를 파생하려고 시도합니다. 방어는 각 시도를 비싸게 만드는 것입니다.

PBKDF2는 CPU 시간(SHA-256 반복)에서만 각 시도를 비싸게 만듭니다. 현대 GPU는 초당 수십억 번의 SHA-256을 실행합니다. 게이밍 GPU는 하루에 1,000만~1억 번의 PBKDF2-SHA-256-600000 추측을 테스트할 수 있습니다. ASIC 공격자는 수 배 더 잘합니다.

메모리 경도 함수(scrypt, Argon2)는 시도당 고정된 양의 메모리를 요구합니다. GPU와 ASIC은 메모리 대역폭이 제한되어 있으므로 장치당 병렬성이 제한됩니다. 64 MiB 메모리 요구사항은 16GB VRAM을 가진 GPU가 수백만이 아닌 최대 256개의 병렬 추측만 실행할 수 있음을 의미합니다. 브루트 포스의 경제적 비용이 2~3배 자릿수로 증가합니다.

PBKDF2: 레거시 기본값

PBKDF2(RFC 2898)는 비밀번호와 솔트에 대해 일반적으로 HMAC-SHA-256이나 HMAC-SHA-512인 의사 무작위 함수를 반복합니다. 반복 횟수가 유일한 조정 가능 매개변수입니다.

const passwordKey = await crypto.subtle.importKey(
  "raw", new TextEncoder().encode(password),
  "PBKDF2", false, ["deriveKey"]
);
const aesKey = await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt,  // 16 무작위 바이트
    iterations: 600000,
    hash: "SHA-256",
  },
  passwordKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);

OWASP 2023은 PBKDF2-SHA-256 최소 600,000회 반복을 권장합니다. 일부 사양(예: LastPass의 2018년 기본값인 100,100)은 2026년 기준으로 너무 낮습니다.

장점: Web Crypto 내장, WASM 불필요, FIPS 검증됨, TLS 1.3 세션 재개에서 지원, Node와 브라우저에서 동일하게 작동합니다.

한계: 메모리 경도 없음, GPU와 ASIC 가속에 취약합니다. 반복 횟수를 두 배로 늘리면 공격자 비용이 두 배가 되지만 합법적인 사용자 비용도 두 배가 됩니다.

scrypt: 최초의 메모리 경도 배포

scrypt(RFC 7914)는 Colin Percival이 2009년 Tarsnap을 위해 발명했습니다. 비밀번호 재료를 큰 메모리 버퍼를 통해 혼합해, 공격자가 각 추측 중 해당 버퍼를 보유하도록 강제합니다.

세 가지 매개변수입니다.

  • N: 비용 인수(일반적으로 2^14에서 2^20). 메모리 사용량은 대략 128 * N * r 바이트.
  • r: 블록 크기(일반적으로 8). 메모리와 GHASH 반복 횟수에 영향.
  • p: 병렬화(일반적으로 1). 높은 값은 합법적인 계산을 빠르게 하지만 공격자도 빠르게 만들어 보통 1로 유지.

OWASP는 기준선으로 N=2^17, r=8, p=1을 권장합니다. 약 128 MiB를 소비하고 현대 하드웨어에서 약 800ms가 걸립니다.

scrypt는 Web Crypto API에 없습니다. JavaScript에서는 scrypt-js, @noble/hashes, 또는 libsodium.js를 사용하세요. Litecoin과 Dogecoin이 scrypt를 작업 증명으로 사용해 scrypt 전용 ASIC 개발을 촉진했고, 이로 인해 ASIC에 대한 원래의 비대칭적 이점이 다소 약화되었습니다.

Argon2id: 2026년 기본값

Argon2는 2015년 Password Hashing Competition을 우승했습니다. 세 가지 변형이 있습니다. Argon2d(가장 빠르지만 데이터 의존적이고 사이드 채널 취약), Argon2i(데이터 독립적이지만 느림), Argon2id(하이브리드, 대부분의 용도에 권장). RFC 9106이 2021년 표준화했습니다.

세 가지 매개변수입니다.

  • t (시간): 메모리를 통한 반복 횟수. 일반적으로 2~3.
  • m (메모리): KiB 단위의 메모리. 일반적으로 65536(64 MiB) 이상.
  • p (병렬성): 병렬성 정도. 일반적으로 1~4.

OWASP 2024 기준선: 최소값은 t=2, m=19456(19 MiB), p=1이고, 강한 보호는 t=3, m=65536(64 MiB), p=4입니다.

import { argon2id } from '@noble/hashes/argon2';
import { utf8ToBytes } from '@noble/hashes/utils';

const derivedKey = argon2id(utf8ToBytes(password), salt, {
  t: 3, m: 65536, p: 4, dkLen: 32
});

또는 argon2-browser(WASM)를 통해:

import argon2 from 'argon2-browser';
const hash = await argon2.hash({
  pass: password, salt,
  type: argon2.ArgonType.Argon2id,
  time: 3, mem: 65536, parallelism: 4, hashLen: 32
});

Argon2id는 메모리 접근 패턴이 대량 메모리 설계에 덜 적합하므로 scrypt보다 GPU 공격자를 더 효과적으로 무력화합니다. Argon2용 ASIC은 연구 단계에 있지만 공격자 규모로 경제적으로 배포되지는 않았습니다.

앱에 맞는 매개변수 선택

보정 방법: 사용자가 허용하는 가장 긴 대기 시간(보통 500ms~2초)을 선택하고, 가장 느린 대상 장치에서 측정한 뒤, 해당 예산에 맞게 매개변수를 설정합니다.

HexaTransfer 방식의 비밀번호 보호 파일 공유에서(파생이 업로드 시 한 번, 다운로드 시 한 번 발생) 1~2초는 허용 가능합니다. 매개변수 기준입니다.

  • PBKDF2-SHA-256: 600,000~1,200,000회 반복
  • scrypt: N=2^17, r=8, p=1
  • Argon2id: t=3, m=65536, p=4

사용자가 비밀번호를 입력한 후 기다리는 로그인 시스템에서는 300~500ms가 UX 한계입니다. 매개변수를 대략 절반으로 줄이세요. 사용자가 기다리지 않는 배치 시나리오(예: 백그라운드 재암호화)에서는 3~5초로 늘리세요.

솔트 관리

세 KDF 모두 솔트가 필요합니다. 규칙입니다.

  • 최소 16바이트 무작위
  • Math.random()이 아닌 crypto.getRandomValues()를 통해 생성
  • 비밀번호당 고유(Alice와 Bob이 같은 비밀번호를 사용해도 솔트가 달라 파생 키가 달라야 함)
  • 비밀 아님 — 암호문과 함께 저장

모든 파생에 추가되는 비밀인 페퍼가 가끔 논의됩니다. 파일 전송에서 "서버"가 단순한 blob 저장소인 경우 페퍼는 서버 측 비밀이 없으므로 가치가 없습니다. 계정 기반 시스템에서 비밀번호 데이터베이스와 별도로 저장된 서버 측 페퍼는 데이터베이스 덤프의 유용성을 낮춥니다.

KDF 간 마이그레이션

PBKDF2 기존 배포에서 Argon2id로 이동하려는 경우입니다.

  • 암호문 메타데이터에 KDF 식별자를 저장합니다("kdf": "pbkdf2-sha256-600000" 또는 "kdf": "argon2id-3-65536-4")
  • 새 업로드에는 Argon2id 사용
  • 복호화 시 KDF 식별자를 읽고 일치하는 함수 사용
  • 이전 암호문을 맹목적으로 업그레이드하지 마세요. 재파생하려면 비밀번호가 필요합니다

LastPass, 1Password, Bitwarden은 모두 이 마이그레이션을 거쳤습니다. 세 서비스 모두 현재 600,000회 이상 반복의 PBKDF2를 기본값으로 하고, 새 버전에서는 Argon2id를 제공합니다.

bcrypt는 어떤가요?

bcrypt(1999년)는 적당한 메모리 경도를 가진 훌륭한 비밀번호 해시 함수입니다. 입력을 72바이트로 제한합니다(긴 비밀번호가 조용히 잘려나가는 악명 높은 함정). Ruby on Rails와 많은 PHP 프레임워크의 역사적 기본값이었습니다. 2026년 신규 코드에서는 Argon2id를 선호하세요. bcrypt는 기존 시스템 유지에는 괜찮습니다.

GDPR, HIPAA, PIPA 컴플라이언스

GDPR 제32조, HIPAA의 기술적 세이프가드, 한국의 개인정보 보호법(PIPA) 모두 비밀번호 파생 암호화의 경우 적절한 키 강화를 요구합니다. OWASP 권장 매개변수를 사용하고 KDF 선택을 문서화하세요. 감사 기관은 점점 구체적인 반복 횟수와 알고리즘 선택을 확인하고 있습니다.

2026년 신규 파일 전송 서비스를 위한 실질적 권장 사항입니다.

  • 15~200KB 의존성을 허용할 수 있다면: @noble/hashes나 libsodium.js를 통한 Argon2id
  • 의존성 없이 번들 크기를 최소화해야 한다면: Web Crypto를 통한 PBKDF2-SHA-256 600,000회 반복
  • 레거시 scrypt를 상속하는 암호화폐 지갑이나 시스템을 작성 중이라면: N=2^17의 scrypt

민감한 파일을 처리하는 프로덕션 앱에서는 Argon2id가 WASM 의존성의 가치가 있습니다.

hexatransfer.com에서 사용해 보세요 — 무료, 계정 불필요, 최대 10GB.

엔드투엔드 암호화로 대용량 파일을 안전하게 전송

엔드투엔드 암호화로 최대 10GB의 파일을 무료로 전송하세요. 계정이 필요하지 않습니다. 업로드 전에 브라우저에서 파일이 암호화되어 다른 사람은 읽을 수 없습니다.

파일 보내기