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

TimeFile의 보안 공유 기능에서 사용하던 암호화 코드를 오픈소스로 공개합니다.
브라우저에서 파일을 암호화하는 건 생각보다 번거롭습니다. Web Crypto API를 직접 쓰면 salt 생성하고, IV 만들고, 키 유도하는 등.. 매번 비슷한 코드를 100줄 넘게 작성해야 하거든요. TimeFile은 이 작업을 줄이기 위해 내부적으로 모듈을 개발하였었고, 더 많은 분들께 도움이 되었으면 하는 마음으로 저희가 사용하던 코드를 정리해서 오픈소스로 공개하게 되었습니다.
설치
1npm install @time-file/browser-file-crypto
1pnpm add @time-file/browser-file-crypto
1yarn add @time-file/browser-file-crypto
사용법
1import { encryptFile, decryptFile } from '@time-file/browser-file-crypto';23// 암호화4const encrypted = await encryptFile(file, { password: 'secret' });56// 복호화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를 직접 쓰면 이런 코드를 작성해야 해요
1const salt = crypto.getRandomValues(new Uint8Array(16));2const iv = crypto.getRandomValues(new Uint8Array(12));34const keyMaterial = await crypto.subtle.importKey(5 "raw",6 new TextEncoder().encode(password),7 "PBKDF2",8 false,9 ["deriveKey"],10);1112const 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);2425const ciphertext = await crypto.subtle.encrypt(26 { name: "AES-GCM", iv },27 key,28 data,29);3031// 여기서 끝이 아니고, salt랑 iv를 결과에 붙여야 나중에 복호화할 수 있음32const result = new Uint8Array(1 + 16 + 12 + ciphertext.byteLength);33result[0] = 0x01; // marker34result.set(salt, 1);35result.set(iv, 17);36result.set(new Uint8Array(ciphertext), 29);
암호화 한 번 하려고 이 정도를 써야 합니다. 복호화는 또 따로 작성해야 하고요. browser-file-crypto를 쓰면 이게 두 줄로 줄어들게 됩니다!
기능
비밀번호 암호화
가장 기본적인 사용법이에요. 비밀번호를 넣으면 PBKDF2로 키를 유도하고, AES-256-GCM으로 암호화합니다.
1const encrypted = await encryptFile(file, { password: 'my-password' });2const decrypted = await decryptFile(encrypted, { password: 'my-password' });
키파일 암호화
비밀번호 대신 256-bit 랜덤 키를 쓸 수도 있어요. 비밀번호보다 보안성이 높습니다.
1import { generateKeyFile, encryptFile } from '@time-file/browser-file-crypto';23const keyFile = generateKeyFile();4// keyFile.key에 base64로 인코딩된 256-bit 키가 들어있어요56const encrypted = await encryptFile(file, { keyData: keyFile.key });
TimeFile에서는 이 키를 .tfkey 파일로 다운로드받게 해서, 나중에 복호화할 때 업로드하도록 했어요.
진행률 Callback
대용량 파일을 암호화할 때 프로그레스 바를 보여주고 싶으면 onProgress를 쓰면 됩니다.
1await encryptFile(file, {2 password: "secret",3 onProgress: ({ phase, progress }) => {4 // phase: 'deriving_key' | 'encrypting' | 'decrypting' | 'complete'5 updateProgressBar(progress);6 },7});
암호화 타입 감지
암호화된 파일이 비밀번호로 암호화된 건지, 키파일로 암호화된 건지 확인할 수 있어요.
1import { getEncryptionType } from "@time-file/browser-file-crypto";23const type = getEncryptionType(encryptedBlob);4// 'password' | 'keyfile' | 'unknown'
TimeFile에서는 이걸로 복호화 UI를 다르게 보여줍니다. 비밀번호 암호화면 비밀번호 입력창을, 키파일 암호화면 키파일 업로드 버튼을 보여주는 식으로요.
에러 처리
비밀번호가 틀리거나 파일이 손상됐을 때 CryptoError가 발생해요.
1import { CryptoError } from "@time-file/browser-file-crypto";23try {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)라서 데이터 변조도 감지할 수 있어요. 누군가 암호화된 파일을 조작하면 복호화할 때 실패합니다.
파일 포맷
암호화된 파일의 구조는 이렇게 생겼어요

번들 사이즈
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에 남겨주세요.


