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

WebAssembly 암호화: 브라우저에서 네이티브에 가까운 암호화 속도

WebAssembly로 브라우저에서 네이티브에 가까운 암호화 속도를 달성하세요. WASM 암호화 구현을 비교하세요.

WASM으로 컴파일된 암호화 라이브러리는 브라우저에서 네이티브 C 속도의 약 60~80%로 실행됩니다. 이는 순수 JavaScript 구현보다 3~10배 빠른 속도입니다. Web Crypto API에 없는 암호화 프리미티브(ChaCha20-Poly1305, Argon2id, XChaCha20, Kyber 같은 양자 내성 KEM)의 경우 WASM이 실용적인 성능을 얻을 수 있는 현실적인 경로입니다. AES-256-GCM처럼 Web Crypto가 이미 지원하는 프리미티브는 브라우저의 하드웨어 가속 구현이 WASM을 압도합니다. 이 글은 언제 WASM을 선택해야 하는지와 트레이드오프를 벤치마크하는 방법을 다룹니다.

네이티브 Web Crypto가 이기는 경우

Web Crypto가 이미 제공하는 알고리즘(AES-GCM, AES-CBC, AES-CTR, PBKDF2, HMAC, RSA-OAEP, ECDH, ECDSA, SHA-256/384/512)의 경우, 브라우저의 기본 구현은 x86의 AES-NI와 모바일의 ARMv8 암호화 확장을 통해 하드웨어 가속을 사용합니다. AES-256-GCM의 일반적인 처리량 비교:

  • Web Crypto (Apple Silicon Chrome): 1.7GB/s
  • Web Crypto (AES-NI 탑재 Intel x86 Chrome): 1.2GB/s
  • libsodium WASM AES-GCM: 400~800MB/s
  • 네이티브 OpenSSL 참조값: 3~5GB/s

WASM은 샌드박스 내부에서 AES-NI에 접근할 수 없어 더 느리지만 상수 시간(constant-time) 처리를 보장하는 비트슬라이스 AES 구현으로 대체합니다. Web Crypto가 지원하는 알고리즘은 항상 Web Crypto를 먼저 사용하세요.

WASM이 이기는 경우

Web Crypto에 없는 알고리즘에서 WASM이 빛을 발합니다.

  • ChaCha20-Poly1305: AES-NI가 없는 기기에서 AES보다 빠르며 2026년 현재 브라우저 기본 지원 없음
  • XChaCha20-Poly1305: 192비트 논스로 모든 규모에서 랜덤 논스 사용이 안전
  • Argon2id: PHC 우승 패스워드 해싱 함수, 브라우저 기본 지원 없음
  • X25519 / Ed25519: Safari 17과 Firefox 129가 기본 지원을 추가했지만 WASM이 여전히 이식성 있는 경로
  • BLAKE2b / BLAKE3: Web Crypto에 없는 빠른 해시 함수
  • Kyber, Dilithium, SPHINCS+: 양자 내성 프리미티브, 라이브러리 영역

이 알고리즘들의 JavaScript 구현은 존재하지만 WASM보다 5~20배 느립니다. 1GB 파일에 ChaCha20-Poly1305를 적용할 때 WASM은 약 2초, 순수 JS는 15~40초가 걸립니다.

libsodium.js: 핵심 라이브러리

libsodium-wrappers(Emscripten으로 빌드된 libsodium과 JS 래퍼)는 기본 선택입니다. 200KB WASM + 약 50KB JS 래퍼이며 압축 후 크기입니다.

import _sodium from 'libsodium-wrappers';
await _sodium.ready;
const sodium = _sodium;

// ChaCha20-Poly1305 인증 암호화
const key = sodium.crypto_aead_xchacha20poly1305_ietf_keygen();
const nonce = sodium.randombytes_buf(
  sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
);
const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
  plaintext, null, null, nonce, key
);

초기 페이지 렌더링을 차단하지 않도록 동적으로 로드하세요:

async function getSodium() {
  if (!window._sodium) {
    const mod = await import('libsodium-wrappers');
    await mod.ready;
    window._sodium = mod;
  }
  return window._sodium;
}

crypto_secretstream을 통한 스트리밍 AEAD

대용량 파일 전송에 libsodium의 crypto_secretstream_xchacha20poly1305는 브라우저에서 가장 깔끔한 스트리밍 AEAD입니다.

const { state, header } =
  sodium.crypto_secretstream_xchacha20poly1305_init_push(key);
const chunk1 = sodium.crypto_secretstream_xchacha20poly1305_push(
  state, plaintext1, null,
  sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
);
// 마지막 청크는 TAG_FINAL을 사용해 수신자가 잘림을 감지할 수 있게 함
const chunkLast = sodium.crypto_secretstream_xchacha20poly1305_push(
  state, plaintextLast, null,
  sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);

TAG_FINAL 마커는 수신자가 잘림 공격을 감지할 수 있게 합니다. 공격자가 뒷부분 청크를 삭제하면 클라이언트에서 복호화가 실패합니다. Web Crypto의 AES-GCM에는 이런 속성이 없어 직접 잘림 감지를 구현해야 합니다.

브라우저에서 Argon2

브라우저에서 패스워드 기반 키 도출에는 WASM을 통한 Argon2id가 2026년 모범 사례입니다.

  • argon2-browser: 전용 Argon2 WASM 라이브러리, 약 200KB
  • libsodium.js: crypto_pwhash를 통한 Argon2id, libsodium을 이미 사용한다면 좋음
  • @noble/hashes: 순수 JS Argon2id, 약 15KB 번들이지만 WASM보다 3~5배 느림

argon2-browser 예시:

import argon2 from 'argon2-browser';
const result = await argon2.hash({
  pass: password,
  salt: salt, // Uint8Array, 16바이트 이상
  type: argon2.ArgonType.Argon2id,
  time: 3,
  mem: 65536, // KiB 단위, 64MiB
  parallelism: 4,
  hashLen: 32,
});
// result.hash는 AES-256 키로 사용할 수 있는 Uint8Array

OWASP 2024 기준선(m=19MiB, t=2, p=1)은 약 300~500ms가 걸립니다. 더 강력한 설정(m=64MiB, t=3, p=4)은 최신 하드웨어에서 약 1~2초가 걸립니다. 대상 사용자의 대기 시간에 맞게 파라미터를 조정하세요.

WASM을 통한 양자 내성 암호화

Kyber(키 캡슐화)와 Dilithium(서명)은 2024년 NIST에 의해 ML-KEM과 ML-DSA로 표준화되었습니다. JavaScript 구현도 있지만 liboqs-js 같은 WASM 버전이 더 빠르고 감사하기 쉽습니다. 파일 전송에서 양자 내성 KEM은 대칭 파일 키를 양자 안전 공개키 프리미티브로 래핑할 수 있게 합니다. 하이브리드 방식(ML-KEM + X25519)은 고전 및 미래 양자 공격자 모두로부터 보호합니다.

번들 크기 고려사항

WASM 바이너리는 JS 번들의 일부로 포함되거나 지연 로드됩니다. 대략적인 압축 크기:

  • libsodium.js 기본: 200KB
  • libsodium.js sumo: 600KB
  • argon2-browser: 200KB
  • liboqs-js (양자 내성): 1MB 이상

사용자 상호작용 후 지연 로드하면 초기 번들을 가볍게 유지할 수 있습니다. rel="modulepreload" 힌트나 서비스 워커 캐싱으로 이후 로드를 즉각적으로 만드세요. 브라우저 개발자 도구의 네트워크 패널에서 첫 로드 후 WASM 바이너리가 캐시되는지 확인하세요. 잘못 구성된 캐시 헤더는 매 방문마다 재다운로드를 유발합니다.

컴파일 및 인스턴스화 오버헤드

WebAssembly.instantiate()는 바이너리를 파싱하고 컴파일합니다. 데스크톱에서 200KB 모듈은 20~100ms, 모바일에서는 100~500ms가 걸립니다. 이는 세션당 한 번만 발생합니다. WebAssembly.instantiateStreaming()은 다운로드와 컴파일을 파이프라인으로 처리해 instantiate()보다 시작 시간을 30~50% 절약합니다.

메모리 관리 주의사항

WASM 모듈은 선형 페이지(각 64KiB) 단위로 메모리를 할당합니다. libsodium은 기본적으로 소량의 초기 메모리로 시작해 필요에 따라 늘리지만 무제한 증가는 브라우저 한계(32비트 WASM에서 일반적으로 4GB)에 도달할 수 있습니다. 멀티 GB 파일 암호화에서는 청크를 WASM 모듈을 통해 스트리밍하고, 각 청크 후 sodium.memzero로 버퍼를 해제하세요.

실용적인 레시피

2026년 파일 전송 앱을 위한 권장 방법:

  • AES-256-GCM, PBKDF2, HMAC, SHA-256, RSA-OAEP에는 Web Crypto 사용
  • Argon2id 패스워드 해싱에는 WASM을 통한 libsodium.js 사용
  • 대용량 파일 스트리밍 암호화에는 libsodium.js(crypto_secretstream) 사용
  • 지원되는 곳에서는 기본 Ed25519/X25519 사용, 그 외에는 libsodium으로 대체
  • 사용자 상호작용 후 WASM을 지연 로드해 초기 번들을 가볍게 유지
  • 대상 기기에서 벤치마크(데스크톱 수치가 모바일과 같다고 가정하지 말 것)

HexaTransfer는 하드웨어 가속 경로가 가장 빠르기 때문에 대용량 파일 암호화에 Web Crypto AES-GCM을 사용합니다. WASM은 기본 API가 제공할 수 없는 알고리즘을 위해 예약합니다.

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

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

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

파일 보내기