Piping UI: セキュアにOpenPGPで暗号化してファイル転送する技術周り
Piping UI 0.2.0でOpenPGP.jsを使ってパスワードによるエンドツーエンド暗号化(E2E暗号化)機能が搭載された。
Piping UI:
https://gyazo.com/7fa554d63f8a4c97a7dc68aa43a40f93
使い方は「Piping UI: パスワードでのエンドツーエンド暗号化のファイル転送の使い方」。
なぜエンドツーエンド暗号化をするか?
サーバーが信用できない場合でも安心して転送するため。
元々サーバー<=>クライアント間ではHTTPSで暗号化されている。
エンドツーエンド暗号化をしていればサーバー内でも内容を盗み見れないことが保証できる。
なぜOpenPGP?
暗号化のフォーマットが規定されていて、ブラウザでもCLIからでも同様に暗号化/複号ができるから。
OpenPGPやgpgコマンドでもパスワードでの共通鍵暗号方式での暗号化は可能。
gpgコマンドは公開鍵暗号方式のイメージがあるが、パスワードを指定して暗号化出来るため事前の準備などが不要で手軽。
RFC: RFC 4880 - OpenPGP Message Format
opensslコマンドという選択肢もあるが、暗号化のフォーマットがドキュメント化されてない。
詳細: 「「OpenSSLの暗号方式が非標準でドキュメントにも記載がない」と書かれている場所」
使い方
「Piping UI: パスワードでのエンドツーエンド暗号化のファイル転送の使い方」
ブラウザのPiping UIでもgpgコマンドでも同じように暗号化と複号出来る。
OpenPGP.js
OpenPGP.jsを使って、ブラウザで暗号化/復号は完結している。そのためパスワードがサーバーに送られて暗号化されるわけではなく安心。
OpenPGP.jsはとてもシンプルで、以下のようにすればパスワードで暗号化できる。
code:暗号化.ts
// Encrypt with PGP
const encryptResult = await openpgp.encrypt({
message: openpgp.message.fromBinary(bytes),
passwords: password,
armor: false
});
// Get encrypted
const encrypted: Uint8Array = encryptResult.message.packets.write() as Uint8Array;
以下は復号。
code:復号.ts
const plain = (await openpgp.decrypt({
message: await openpgp.message.read(bytes),
passwords: password,
format: 'binary'
})).data as Uint8Array;
Service Worker内でストリーミングしながらの復号
「/nwtgck/ファイルのストリーミング強制保存をクロスオリジンでも実現させるService Workerの裏技ぽい使い方」を使って、Service WorkerでクロスオリジンなPiping Serverからのデータをストリーミングしながらダウンロードしている。
このダウンロード中に復号も行えると時間的に効率がとても良い。そのためにService Worker内でもOpenPGP.jsを使って復号している。
openpgp.decrypt()にReadableStreamを渡すとストリーミングしながらの復号ができる。この時にopenpgp.config.allow_unauthenticated_stream = true;もする必要がある。
詳細: 「OpenPGP.js: デフォルトだと復号がストリーミングされない理由と解決策」
code:sw.js
...
// Allow unauthenticated stream
// (see: https://github.com/openpgpjs/openpgpjs/releases/tag/v4.0.0)
openpgp.config.allow_unauthenticated_stream = true;
// Decrypt the response body
const decrypted = await openpgp.decrypt({
message: await openpgp.message.read(res.body),
passwords: password,
format: 'binary'
});
const plainStream = decrypted.data;
...
今後: パスワード入力不要でE2E暗号化 + Forward secrecyでよりセキュアな暗号化
今後実装の可能性がある話。
追記: 0.4.0で実装された: Web上でパスワード不要のE2E暗号化してセキュアにファイル転送をしたい - Qiita
ディフィー・ヘルマン鍵共有でエフェメラルな鍵を送信者と受信者で共有する。そのためユーザーがパスワードを考えて入力する必要が全く不要になる。
Piping Chunkでパスワードの入力不要でセキュアなランダムな使い捨ての鍵を共有してエンドツーエンド暗号化するようになっている。また暗号化だけではなく相手を認証して中間者攻撃に関しても安全性を確保したいプロジェクト。
Piping Chunk動作デモ: エンドツーエンド暗号化ファイル転送 + 受信者検証 (Piping Chunk 0.4.0) - Piping Server経由
Piping Chunkの認証の流れ: 「中間者攻撃を防ぐためのPiping Chunk 0.6.x以降の認証方式の流れ」
この仕組/実装をもう一度見直して、Piping UIでも使えるようになると、ユーザーはパスワードを入力する手間がなくなり、利便性と安全性を両立できるのではと思っている。
ブラウザだけ(Piping UIだけ)で送受信する場合はとても手軽。
ただし、フローがあるためcurlとgpgの既存のCLIと連携はしづらいので、連携がしやすいパスワードによる暗号化の実装を優先した。