Piping UI: セキュアにOpenPGPで暗号化してファイル転送する技術周り
https://gyazo.com/7fa554d63f8a4c97a7dc68aa43a40f93
サーバーが信用できない場合でも安心して転送するため。
元々サーバー<=>クライアント間ではHTTPSで暗号化されている。 暗号化のフォーマットが規定されていて、ブラウザでもCLIからでも同様に暗号化/複号ができるから。
gpgコマンドは公開鍵暗号方式のイメージがあるが、パスワードを指定して暗号化出来るため事前の準備などが不要で手軽。 opensslコマンドという選択肢もあるが、暗号化のフォーマットがドキュメント化されてない。 使い方
OpenPGP.jsを使って、ブラウザで暗号化/復号は完結している。そのためパスワードがサーバーに送られて暗号化されるわけではなく安心。 code:暗号化.ts
// Encrypt with PGP
const encryptResult = await openpgp.encrypt({
message: openpgp.message.fromBinary(bytes),
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),
format: 'binary'
})).data as Uint8Array;
openpgp.decrypt()にReadableStreamを渡すとストリーミングしながらの復号ができる。この時にopenpgp.config.allow_unauthenticated_stream = true;もする必要がある。
code:sw.js
...
// Allow unauthenticated stream
openpgp.config.allow_unauthenticated_stream = true;
// Decrypt the response body
const decrypted = await openpgp.decrypt({
message: await openpgp.message.read(res.body),
format: 'binary'
});
const plainStream = decrypted.data;
...
今後実装の可能性がある話。
ディフィー・ヘルマン鍵共有でエフェメラルな鍵を送信者と受信者で共有する。そのためユーザーがパスワードを考えて入力する必要が全く不要になる。 この仕組/実装をもう一度見直して、Piping UIでも使えるようになると、ユーザーはパスワードを入力する手間がなくなり、利便性と安全性を両立できるのではと思っている。 ただし、フローがあるためcurlとgpgの既存のCLIと連携はしづらいので、連携がしやすいパスワードによる暗号化の実装を優先した。