browser-file-cryptoライブラリを公開しました.

TimeFileのセキュア共有機能のために開発したクライアントサイドファイル暗号化モジュールをオープンソースとして公開します。

イ・ミンギュ
イ・ミンギュ
2026年1月2日
1分で読める
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を使えば、これが2行になります!


機能

パスワード暗号化

一番基本的な使い方です。パスワードを渡すとPBKDF2で鍵を導出し、AES-256-GCMで暗号化します。

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

キーファイル暗号化

パスワードの代わりに256ビットのランダムキーを使うこともできます。 パスワードよりセキュリティが高いです。

TypeScript
1import { generateKeyFile, encryptFile } from '@time-file/browser-file-crypto';
2
3const keyFile = generateKeyFile();
4// keyFile.keyにbase64エンコードされた256ビット鍵が入っています
5
6const encrypted = await encryptFile(file, { keyData: keyFile.key });

TimeFileでは、このキーを.tfkeyファイルとしてダウンロードできるようにして、後で復号化する時にアップロードしてもらう形にしています。

進捗コールバック

大きなファイルを暗号化する時にプログレスバーを表示したい場合は、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イテレーション, 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