LoginSignup
10
13

More than 5 years have passed since last update.

Web Crypto API で公開暗号鍵を用いてメッセージを送る

Posted at

用途

  • 自分はサービス運営者で、ユーザー同士のメッセージ機能を作りたいが、その内容を知りたくない。

  • SlackやChatworkなどのメッセージングサービスを使ってパスワードなどを送りたいが、運営事業者様のサーバーにログが残るのが嫌だ。

要素技術

流れ

  1. 秘密を受け取る側:メッセージを受け取りたい側が鍵ペアを生成
  2. 秘密を受け取る側:既存のメッセージ機能を使って相手に公開鍵を送信
  3. 秘密を送る側:公開鍵を使ってメッセージを暗号化
  4. 秘密を送る側:既存のメッセージ機能を使って相手に暗号化したメッセージを送信
  5. 秘密を受け取る側:秘密鍵を使って複合する

コード

generate_key_pair.ts
let key: CryptoKeyPair;

// 鍵ペアを生成して保持する
// この関数の戻り値はJSONなので、Slackなど好きな手段で送る。
// https://github.com/diafygi/webcrypto-examples
async function createPublicKey(): Promise<JsonWebKey> {
   let key = await window.crypto.subtle.generateKey(
       {
           name: "RSA-OAEP",
           modulusLength: 2048, //can be 1024, 2048, or 4096
           publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
           hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
       },
       false, //whether the key is extractable (i.e. can be used in exportKey)
       ["encrypt", "decrypt"] //must be ["encrypt", "decrypt"] or ["wrapKey", "unwrapKey"]
   );
   // エクスポートとはJSオブジェクトである鍵をJSONに変換することのようだ
   return await window.crypto.subtle.exportKey(
       "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
       key.publicKey //can be a publicKey or privateKey, as long as extractable was true
   );
}
send_message.ts

// 受け取った公開鍵でメッセージを暗号化する
async function messageEncrypting(pubKeyJson: JsonWebKey): Promise<string> {
   // importとはJSON形式の公開鍵をJSオブジェクトに変換することのようだ
   let importKey = await window.crypto.subtle.importKey(
       "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
       pubKeyJson,
       {   //these are the algorithm options
           name: "RSA-OAEP",
           hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
       },
       false, //whether the key is extractable (i.e. can be used in exportKey)
       ["encrypt"] //"encrypt" or "wrapKey" for public key import or
       //"decrypt" or "unwrapKey" for private key imports
   );
   let message = window.prompt("メッセージを入力してください");

   let encMessage = await window.crypto.subtle.encrypt(
       {
           name: "RSA-OAEP",
           //label: Uint8Array([...]) //optional
       },
       importKey, //from generateKey or importKey above

       // stringをArrayBufferに変換しなければいけない
       string_to_buffer(message) //ArrayBuffer of data you want to encrypt
   );

   // encMessageはArrayBuffer形式なので、BASE64に変換しないとテキストチャット等で送ることができない
   // https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string
   return btoa(String.fromCharCode.apply(null, new Uint8Array(encMessage)));
}


// https://gist.github.com/kawanet/352a2ed1d1656816b2bc
function string_to_buffer(src) {
   return (new Uint16Array([].map.call(src, function (c) {
       return c.charCodeAt(0)
   }))).buffer;
}
receive.ts
// 受信
async function receiveEncMessage(encMessage: string) {
   // stringをArrayBufferに変換しないといけない
   let msg = base64ToArrayBuffer(encMessage);
   let plain = await window.crypto.subtle.decrypt(
       {
           name: "RSA-OAEP",
       },
       key.privateKey, //from generateKey or importKey above
       msg //ArrayBuffer of the data
   );
   window.alert(buffer_to_string(plain));
}

function buffer_to_string(buf) {
   return String.fromCharCode.apply("", new Uint16Array(buf))
}

function base64ToArrayBuffer(base64) {
    var binary_string =  window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array( len );
    for (var i = 0; i < len; i++)        {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
}

10
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
13