Web標準のJavaScriptだけで楕円曲線ディフィー・ヘルマン鍵共有してE2E暗号化用の鍵を生成してAES-GCMで暗号化/復号する一連の流れ
コード
https://gyazo.com/5caddb58fcc1df3baaddbe889c399ec6
「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,
);
// 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,
);
// 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,
);
// 暗号化したい生データ
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,
);
// BさんがAさんが暗号化したデータを復号する
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
bKey,
encrypted
);
// 動作確認用: 生データrawと復号後decryptedが等しいことを雑に確認する。
// true
})();