Web標準のJavaScriptだけで楕円曲線ディフィー・ヘルマン鍵共有してE2E暗号化用の鍵を生成してAES-GCMで暗号化/復号する一連の流れ
#Web_Crypto #WebブラウザのJavaScript #E2E暗号化 #楕円曲線ディフィー・ヘルマン鍵共有 #セキュリティ
WebのクライアントでのE2E暗号化の推進を願ってノウハウをまとめたい。
コード
Web標準のWeb Cryptoを使っているためサードパーティ製のライブラリなしで完結している。Web Cryptoは多くのブラウザで利用可能。
https://gyazo.com/5caddb58fcc1df3baaddbe889c399ec6
Web Crypto API - Web API | MDN
「ECDHのキーペアの生成 → 公開鍵をJSONにエクスポート/インポート→ ECDHによる共通鍵の導出 → AES-GCMでの暗号化/復号 → 生データと復号後のデータの一致の比較」という一連の流れ書いたもの。
コードを大雑把に見ると「Aさんが暗号化したデータをBさんが復号する」という風になっている。AさんとBさんの間で共通鍵ができているのでBさんが暗号化したデータをAさんが復号する情報も整っている。
"Aさんが..."や"Bさんが..."のように主語でどちら側がやることを想定した処理なのかを分かるようにしている。
実際は以下の処理はネットワーク越しに二つのクライアントがデータをやり取りする。分かりやすいようにすぐに実行可能なように1つになっている。わざわざJSON Web Key (JWK)にするのはネットワーク越しにやり取りすることを想定しているため。
code:js
(async () => {
// Aさんがキーペアを作成
const aKeyPair = await window.crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256'},
false,
'deriveKey', 'deriveBits'
);
// Aさんの公開鍵だけをJWKとしてエクスポート(JSONなのでBさんがネットワーク越しに受け取りやすくなる)
const aPublicKeyJWK = await crypto.subtle.exportKey('jwk', aKeyPair.publicKey);
// Bさんがキーペアを作成
const bKeyPair = await window.crypto.subtle.generateKey(
{ name: 'ECDH', namedCurve: 'P-256'},
false,
'deriveKey', 'deriveBits'
);
// Bさんの公開鍵だけをJWKとしてエクスポート(JSONなのでAさんがネットワーク越しに受け取りやすくなる)
const bPublicKeyJWK = await crypto.subtle.exportKey('jwk', bKeyPair.publicKey);
// AさんがBさんの公開鍵のJWKをインポート
const bRemotePublicKey = await crypto.subtle.importKey(
'jwk',
bPublicKeyJWK,
{name: 'ECDH', namedCurve: 'P-256'},
false,
[]
);
// AES-GCMで使う初期化ベクトルを生成する(推奨は12バイト)
const iv = crypto.getRandomValues(new Uint8Array(12));
// AさんがBさんの公開鍵を使ってAES-GCMでの暗号化用の共有鍵を生成する
const aKey = await crypto.subtle.deriveKey(
{ name: 'ECDH', public: bRemotePublicKey },
aKeyPair.privateKey,
{'name': 'AES-GCM', length: 128},
false,
'encrypt', 'decrypt'
);
// 暗号化したい生データ
const raw = new Uint8Array(1, 2, 3);
// 暗号化する
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
aKey,
raw
);
// BさんがAさんの公開鍵のJWKをインポートする
const aRemotePublicKey = await crypto.subtle.importKey(
'jwk',
aPublicKeyJWK,
{ name: 'ECDH', namedCurve: 'P-256'},
false,
[]
);
// BさんがAさんの公開鍵を使ってAES-GCMでの暗号化用の共有鍵を生成する
// (aKeyもbKeyも等しくなるべき)
const bKey = await crypto.subtle.deriveKey(
{ name: 'ECDH', public: aRemotePublicKey },
bKeyPair.privateKey,
{ name: 'AES-GCM', length: 128 },
false,
'encrypt', 'decrypt'
);
// BさんがAさんが暗号化したデータを復号する
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
bKey,
encrypted
);
// 動作確認用: 生データrawと復号後decryptedが等しいことを雑に確認する。
console.log(JSON.stringify(...raw) === JSON.stringify(...new Uint8Array(decrypted)));
// true
})();
元は「WebCrypto APIでECDH鍵交換を用いた暗号化を使ってみる - Qiita」を参考に書いている。
もともと「Web上でパスワード不要のE2E暗号化してセキュアにファイル転送をしたい - Qiita」で紹介していたものでCodepenは「A Pen by Ryo Ota」にある。
関連: AES-GCMの初期化ベクトルIVは12バイト(96ビット)が推奨
Nipp