browser-file-crypto 라이브러리를 소개합니다.

TimeFile에서 보안 공유 기능을 위해 개발하여 사용하던 클라이언트 사이드 파일 암호화 모듈을 오픈소스 라이브러리로 공개합니다.

이민규
이민규
2026년 1월 2일
3분 소요
browser-file-crypto 라이브러리를 소개합니다.

TimeFile의 보안 공유 기능에서 사용하던 암호화 코드를 오픈소스로 공개합니다.

브라우저에서 파일을 암호화하는 건 생각보다 번거롭습니다. Web Crypto API를 직접 쓰면 salt 생성하고, IV 만들고, 키 유도하는 등.. 매번 비슷한 코드를 100줄 넘게 작성해야 하거든요. TimeFile은 이 작업을 줄이기 위해 내부적으로 모듈을 개발하였었고, 더 많은 분들께 도움이 되었으면 하는 마음으로 저희가 사용하던 코드를 정리해서 오픈소스로 공개하게 되었습니다.


설치

npm
Bash
1npm install @time-file/browser-file-crypto
pnpm
Bash
1pnpm add @time-file/browser-file-crypto
yarn
Bash
1yarn add @time-file/browser-file-crypto

사용법

TypeScript
1import { encryptFile, decryptFile } from '@time-file/browser-file-crypto';
2
3// 암호화
4const encrypted = await encryptFile(file, { password: 'secret' });
5
6// 복호화
7const decrypted = await decryptFile(encrypted, { password: 'secret' });

왜 만들었나요?

TimeFile에는 보안 공유 기능이 있어요. 파일을 업로드할 때 비밀번호를 설정하면, 서버에는 암호화된 파일만 저장되고, 비밀번호를 아는 사람만 다운로드할 수 있습니다. 이걸 구현하려면 클라이언트에서 파일을 암호화해야 해요. 서버가 평문을 보면 안 되니까요. 처음엔 crypto-js를 쓰려고 했는데, 개발이 중단된 상태더라고요. 게다가 내부적으로 Math.random()을 쓰는 부분이 있어서 보안상 권장되지 않았어요. aws-crypto도 봤는데, Safari에서 호환성 이슈가 있고 번들 사이즈가 200KB가 넘었습니다. 파일 암호화 하나 하려고 그 정도를 넣기엔 좀 그랬어요. 결국 Web Crypto API를 직접 써서 구현하게 되었습니다.

Web Crypto API, 직접 쓰면 이렇습니다.

참고로 Web Crypto API를 직접 쓰면 이런 코드를 작성해야 해요

JavaScript
1const salt = crypto.getRandomValues(new Uint8Array(16));
2const iv = crypto.getRandomValues(new Uint8Array(12));
3
4const keyMaterial = await crypto.subtle.importKey(
5 "raw",
6 new TextEncoder().encode(password),
7 "PBKDF2",
8 false,
9 ["deriveKey"],
10);
11
12const key = await crypto.subtle.deriveKey(
13 {
14 name: "PBKDF2",
15 salt: salt.buffer,
16 iterations: 100000,
17 hash: "SHA-256",
18 },
19 keyMaterial,
20 { name: "AES-GCM", length: 256 },
21 false,
22 ["encrypt"],
23);
24
25const ciphertext = await crypto.subtle.encrypt(
26 { name: "AES-GCM", iv },
27 key,
28 data,
29);
30
31// 여기서 끝이 아니고, salt랑 iv를 결과에 붙여야 나중에 복호화할 수 있음
32const result = new Uint8Array(1 + 16 + 12 + ciphertext.byteLength);
33result[0] = 0x01; // marker
34result.set(salt, 1);
35result.set(iv, 17);
36result.set(new Uint8Array(ciphertext), 29);

암호화 한 번 하려고 이 정도를 써야 합니다. 복호화는 또 따로 작성해야 하고요. browser-file-crypto를 쓰면 이게 두 줄로 줄어들게 됩니다!


기능

비밀번호 암호화

가장 기본적인 사용법이에요. 비밀번호를 넣으면 PBKDF2로 키를 유도하고, AES-256-GCM으로 암호화합니다.

TypeScript
1const encrypted = await encryptFile(file, { password: 'my-password' });
2const decrypted = await decryptFile(encrypted, { password: 'my-password' });

키파일 암호화

비밀번호 대신 256-bit 랜덤 키를 쓸 수도 있어요. 비밀번호보다 보안성이 높습니다.

TypeScript
1import { generateKeyFile, encryptFile } from '@time-file/browser-file-crypto';
2
3const keyFile = generateKeyFile();
4// keyFile.key에 base64로 인코딩된 256-bit 키가 들어있어요
5
6const encrypted = await encryptFile(file, { keyData: keyFile.key });

TimeFile에서는 이 키를 .tfkey 파일로 다운로드받게 해서, 나중에 복호화할 때 업로드하도록 했어요.

진행률 Callback

대용량 파일을 암호화할 때 프로그레스 바를 보여주고 싶으면 onProgress를 쓰면 됩니다.

TypeScript
1await encryptFile(file, {
2 password: "secret",
3 onProgress: ({ phase, progress }) => {
4 // phase: 'deriving_key' | 'encrypting' | 'decrypting' | 'complete'
5 updateProgressBar(progress);
6 },
7});

암호화 타입 감지

암호화된 파일이 비밀번호로 암호화된 건지, 키파일로 암호화된 건지 확인할 수 있어요.

TypeScript
1import { getEncryptionType } from "@time-file/browser-file-crypto";
2
3const type = getEncryptionType(encryptedBlob);
4// 'password' | 'keyfile' | 'unknown'

TimeFile에서는 이걸로 복호화 UI를 다르게 보여줍니다. 비밀번호 암호화면 비밀번호 입력창을, 키파일 암호화면 키파일 업로드 버튼을 보여주는 식으로요.

에러 처리

비밀번호가 틀리거나 파일이 손상됐을 때 CryptoError가 발생해요.

TypeScript
1import { CryptoError } from "@time-file/browser-file-crypto";
2
3try {
4 await decryptFile(encrypted, { password: "wrong" });
5} catch (error) {
6 if (error instanceof CryptoError) {
7 if (error.code === "INVALID_PASSWORD") {
8 alert("비밀번호가 틀렸습니다");
9 }
10 }
11}

에러 코드는 INVALID_PASSWORD, INVALID_KEYFILE, INVALID_ENCRYPTED_DATA 등이 있어요.


기술적인 부분

암호화 스펙

  • 알고리즘: AES-256-GCM
  • 키 유도: PBKDF2 (100,000 iterations, SHA-256)
  • Salt: 16바이트 (매번 랜덤 생성)
  • IV: 12바이트 (매번 랜덤 생성)

AES-GCM은 인증 암호화(Authenticated Encryption)라서 데이터 변조도 감지할 수 있어요. 누군가 암호화된 파일을 조작하면 복호화할 때 실패합니다.

파일 포맷

암호화된 파일의 구조는 이렇게 생겼어요

암호화된 파일 구조
키파일 암호화는 키 유도가 필요 없어서 salt가 없습니다. 그래서 헤더가 16바이트 더 짧아요.

번들 사이즈

gzip 기준 5KB 정도예요. 외부 의존성이 없고 Web Crypto API만 쓰기 때문에 가볍습니다. crypto-js가 50KB, aws-crypto가 200KB 이상인 걸 생각하면 꽤 작은 편이에요.

브라우저 지원

Web Crypto API를 지원하는 모든 브라우저에서 동작해요. Chrome, Firefox, Safari, Edge 다 됩니다. Node.js 18 이상에서도 쓸 수 있어요. webcrypto를 내장하고 있거든요.


향후 계획

지금은 파일 전체를 메모리에 올려서 암호화하는 방식이에요. 작은 파일은 문제없는데, 몇 GB짜리 파일은 브라우저 메모리가 부족할 수 있어요. 다음 버전에서는 스트리밍 암호화를 지원할 계획입니다. Web Worker 지원도 고려하고 있어요. 지금은 메인 스레드에서 암호화하기 때문에 큰 파일을 처리할 때 UI가 잠깐 멈출 수 있거든요.


링크

- npm: https://www.npmjs.com/package/@time-file/browser-file-crypto - GitHub: https://github.com/Time-File/browser-file-crypto 피드백이나 버그 리포트는 GitHub Issues에 남겨주세요.


Features
Footer