用途
-
自分はサービス運営者で、ユーザー同士のメッセージ機能を作りたいが、その内容を知りたくない。
-
SlackやChatworkなどのメッセージングサービスを使ってパスワードなどを送りたいが、運営事業者様のサーバーにログが残るのが嫌だ。
要素技術
流れ
- 秘密を受け取る側:メッセージを受け取りたい側が鍵ペアを生成
- 秘密を受け取る側:既存のメッセージ機能を使って相手に公開鍵を送信
- 秘密を送る側:公開鍵を使ってメッセージを暗号化
- 秘密を送る側:既存のメッセージ機能を使って相手に暗号化したメッセージを送信
- 秘密を受け取る側:秘密鍵を使って複合する
コード
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;
}