Web Crypto API：RSAプライベート鍵で復号

#はじめに
ブラウザ側はRSAの公開鍵鍵で暗号化し、サーバ側（Node.js）はプライベート鍵で復号をしようとすると、下記のエラーが発生し、webcryptoモジュールを使って解消できました。
>Error: error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error
    at Object.privateDecrypt (internal/crypto/cipher.js:53:12)

#Web Crypto APIとは
仕様をまとめているページ：https://developer.mozilla.org/ja/docs/Web/API/Web_Crypto_API
ページにある警告の通り、慎重に対応する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/346425/209c17c0-746d-5cb2-b0a9-9c36a7fbe19a.png)

出典：https://developer.mozilla.org/ja/docs/Web/API/Web_Crypto_API

#Web Crypto API主なメソッド
Window.cryptoまたはcryptoオブジェクトでSubtleCryptoというインタフェースにていくつかのメソッドを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/346425/97fd5666-b957-31e5-bff6-e4368b5ab20d.png)
##SubtleCrypto

- SubtleCrypto.encrypt()：暗号化
- SubtleCrypto.decrypt()：復号
- SubtleCrypto.sign()：署名
- SubtleCrypto.verify()：署名検証
- SubtleCrypto.digest()：ダイジェスト生成
- SubtleCrypto.generateKey()：鍵生成
- SubtleCrypto.deriveKey()：マスタキーとアルゴリズムによって新しいCryptoKeyを派生
- SubtleCrypto.deriveBits()：マスタキーとアルゴリズムによって疑似乱数ビットの新たに生成されたバッファーを生成
- SubtleCrypto.importKey()：公開鍵、プライベート鍵などからCryptoKeyを生成
- SubtleCrypto.exportKey()：鍵をExport
- SubtleCrypto.wrapKey():ラップされた鍵
- SubtleCrypto.unwrapKey():ラップされた鍵からCryptoKeyを生成

##疑似乱数生成：Crypto.getRandomValues
対称暗号化のIV生成に必要な疑似乱数生成メソッドもよく使うメソッドではあります。

```.js
typedArray = cryptoObj.getRandomValues(typedArray);
```

使い例：

```js
var array = new Uint32Array(10);
window.crypto.getRandomValues(array);
```

結果：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/346425/7dcd982d-8a49-f296-1ef3-a7624c0471c9.png)



#サポートされたアルゴリズム
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/346425/35f49e14-d87c-af60-7b0f-42d858fd4758.png)

出典：https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto

#ブラウザ側：公開鍵で暗号化
公開鍵で暗号化には、公開鍵のインポートと暗号化の２つの手続きがあります。
・公開鍵のインポート：SubtleCrypto.importKey()

```js
const result = crypto.subtle.importKey(
    format, // raw(AESの鍵),pkcs8（RSAのプライベート鍵）,spki(SubjectPublicKeyInfo:RSAまたはElliptic Curveの公開鍵),jwk(JSON Web Key)
    keyData, // キーデータ。ArrayBuffer、TypedArray、JSONWebKeyオブジェクト
    algorithm, //　①、RsaHashedImportParams ：RSA-OAEP、RSA-PSS。②、EcKeyImportParams：ECDSA、ECDH。③、HmacImportParams ：HMAC　④、{ "name": ALGORITHM }：AES-CTR, AES-CBC, AES-GCM、AES-KW。　⑤、PBKDF2
    extractable, // true/false。 SubtleCrypto.exportKey() or SubtleCrypto.wrapKey()メソッドを使うか
    usages // ["encrypt", "decrypt", "sign", "verify", "deriveKey", "deriveBits", "wrapKey", "unwrapKey"]
);
```

公開鍵インポート例(https://developer.mozilla.org/ja/docs/Web/API/SubtleCrypto/importKey)：

```js
 // from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
  function str2ab(str) {
    const buf = new ArrayBuffer(str.length);
    const bufView = new Uint8Array(buf);
    for (let i = 0, strLen = str.length; i < strLen; i++) {
      bufView[i] = str.charCodeAt(i);
    }
    return buf;
  }

  const pemEncodedKey = `-----BEGIN PUBLIC KEY-----
.......
-----END PUBLIC KEY-----`;

  function importRsaKey(pem) {
    const pemHeader = "-----BEGIN PUBLIC KEY-----";
    const pemFooter = "-----END PUBLIC KEY-----";
    const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
    // base64 decode the string to get the binary data
    const binaryDerString = window.atob(pemContents);
    // convert from a binary string to an ArrayBuffer
    const binaryDer = str2ab(binaryDerString);

    return window.crypto.subtle.importKey(
      "spki",
      binaryDer,
      {
        name: "RSA-OAEP",
        hash: "SHA-256"
      },
      true,
      ["encrypt"]
    );
  }
```


※SubtleCrypto.generateKey()で生成することも可能ですが、一般的に先にOpenSSLなど事前に生成した公開鍵をインポートすると思われます。

・暗号化：SubtleCrypto.encrypt()

```js
function encryptMessage(publicKey, message) {
  let enc = new TextEncoder();
  let encoded = enc.encode(message);
  return window.crypto.subtle.encrypt(
    {
      name: "RSA-OAEP"
    },
    publicKey,
    encoded
  );
}
```

#サーバ側（Node.js）：プライベート鍵で復号
復号にはプライベート鍵のインポート、復号の２つの手続きがあります。

Node.jsのCryptoモジュールを使いたいですが、復号できませんでした。
https://nodejs.org/api/crypto.html#crypto_crypto_privatedecrypt_privatekey_buffer

@peculiar/webcryptoを使って復号できました。
webcrypto：https://github.com/PeculiarVentures/webcrypto

・プライベート鍵のインポート：SubtleCrypto.importKey()

```node.js
const webcrypto = require("@peculiar/webcrypto");
const crypto = new webcrypto.Crypto();
const {TextDecoder} = require('util');

const pemText = "xxxxx";
const pkcs8 = Buffer.from(pemText, "base64");
const key = crypto.subtle.importKey("pkcs8", pkcs8, { name: "RSA-OAEP", hash: "SHA-256" }, true, ["decrypt"]);
```

・復号：SubtleCrypto.decrypt()

```node.js
const decryptedStr = await crypto.subtle.decrypt("RSA-OAEP", key, Buffer.from(encryptedMessage, "base64"));
const result = new TextDecoder("utf-8").decode(new Uint8Array(decryptedStr ));
```
参考：https://stackoverflow.com/questions/56058153/rsa-decryption-with-crypto-module-gives-rsa-oaep-sha512


#その他：対称暗号化例（AES-GCM）
```js
function encryptMessage(key, message) {
  let enc = new TextEncoder();
  let encoded = enc.encode(message);
  iv = window.crypto.getRandomValues(new Uint8Array(12));
  return window.crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv: iv
    },
    key,
    encoded
  );
}

```




以上