JavaScript 暗号化・復号化

# はじめに
MH ソフトウェア & サービスが開発・公開している、Webアプリケーションサーバ AiRの便利かな?と思われるコードの紹介です。
Webアプリケーションサーバ AiRはPython、Tornado(Webフレームワーク、Webサーバ)、JavaScript、その他のモジュールで構成されています。
***
Webアプリケーションサーバ AiRは、httpとhttps(SSL)で稼働させることが可能です。
https(SSL)では通信内容は暗号化されると言われています。
AiRをhttp通信で使用する場合もあります。

そこで、AiRはPythonとJavaScriptで共通の暗号化・復号化を実現し、JavaScriptからのAjaxデータを暗号化して通信する方法を実現しています。
***
crypto-js.min.jsとbase64.min.jsを使用します。
***
暗号化キーの長さは16文字です  
暗号化キー: 1234567890123456  

この文字列を暗号化します: MH ソフトウェア & サービス  
暗号化された文字列: beb80c687e1572b9893299376106b08a83029968c4c1b8a28fd8785d76f875e4bb7bcf35289b598316363b6b5306b205  

'name'と言う文字列: name  
暗号化した'name': 27126826c77d8ebac9b982928795f38a  

暗号化した文字列をjson形式でキーと値に入れて暗号化します  

暗号化前の文字列: {"27126826c77d8ebac9b982928795f38a":"beb80c687e1572b9893299376106b08a83029968c4c1b8a28fd8785d76f875e4bb7bcf35289b598316363b6b5306b205"}  
暗号化: ce7bb1ac32d66c4252dda79d7fc8735129ed1471ab1f9a39fa770ebb6e9bfd28f31c040f42df78327177b8f4b3db2e56a969b132549ee68dce3472ae74f4217decf58c583ad49ab9c40634a2773023902d36a2babef2df4cd3d5a891c073f1be7f9995abaf4da03d94837b6e8e74f178fc21e5bffcffb9eef52fbbca388347601d67d8cf0336fb46781be0c27c1e4a86  

暗号化されたJSON文字列を復号化します
[object Object]  

Objectなので文字列にします(JSON.stringfy)
{"name":"MH ソフトウェア & サービス"}
***

下記が今回のサンプルのHTML JavaScriptです。実際にはAiR内に、'fx'というクラスに含まれています。

コードが長くてすいません。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JavaScript 暗号化・復号化</title>
  <style> input {margin-right: 20px; width: 50%;} </style>
  <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
  <!-- 暗号化・復号化ライブラリを読み込みます -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/js-base64@3.7.5/base64.min.js"></script>
</head>
<body>
  <p>
    暗号化キーの長さは16文字です<br>
    記号も使えます<br>
    <div style="display: flex;">
      暗号化キー: <input id="encryptKey" type="text" value="1234567890123456">
    </div>
  </p>
  <p>
    <div style="display: flex;">
      この文字列を暗号化します: <input id="normalMH" type="text" value="MH ソフトウェア & サービス"><br>
    </div>
    <div style="display: flex;">
      暗号化された文字列:<input id="encryptedMH" type="text" value="">
    </div>
  </p>
  <p>
    <div style="display: flex;">
      'name'と言う文字列:<input id="nameStr" type="text" value="name"><br>
    </div>
    <div style="display: flex;">
      暗号化した'name':<input id="encryptedName" type="text" value="">  
    </div>
  </p>
  <p>
    暗号化した文字列をjson形式でキーと値に入れて暗号化します<br>
    <div style="display: flex;">
      暗号化前の文字列:<input id="normalJson" type="text" value="">
    </div>
    <div style="display: flex;">
      暗号化:<input id="encryptedJson" type="text" value="">
    </div>
  </p>
  <p>
    暗号化されたJSON文字列を復号化します<br>
    <input id="decryptedJson" type="text" value="">
  </p>
  <p>
    Objectなので文字列にします(JSON.stringfy)<br>
    <input id="decryptedJsonStr" type="text" value="">
  </p>
  <p>
    <input type="button" onclick="exec();" id="execute" value="実行">
  </p>

  <script>

// インデントは気にしないでね・・・　
class Decryptc {
  /**
   * @param {any} encrypted
   * @param {Boolean} [log=false]
   * @returns {any}
   */
  _(encrypted, log=false) {
    // If modify this function, modify project://fx/__init__.py#75 def decript
    return this.#_(encrypted, log=false);
  };
  /**
   * @param {any} encrypted
   * @param {Boolean} [log=false]
   * @returns {any}
   */
  #_(encrypted, log=false) {
    if (!this.#decryptable(encrypted)) {
      return encrypted;
    };

    // NOTE: Create result.
    const json = JSON.parse(JSON.stringify(encrypted));

    // NOTE: Check encrypted is list or dict?
    if (this.#typeof(json) === 'array' || this.#typeof(json) === 'object') {
      return this.#object(json);
    };

    const decrypted = this.#decrypt(json);
    const decryptedStr = this.#toString(encrypted, decrypted);
    let result = JSON.parse(JSON.stringify(decryptedStr));

    // NOTE: Check encrypted is list or dict?
    if (this.#typeof(result) === 'array' || this.#typeof(result) === 'object') {
      result = this.#object(result);
    };

    // NOTE: Check decrypted is list or dict?
    if (result !== '') {
      result = this.#reDecrypt(result, log);
    } else {
      // IMPORTANCE: decrypted.toString(window['CryptoJS'].enc.Utf8) return ""
      // at plane text.
      result = encrypted;
    };
    return result;
  };

  /**
   * @param {String} encrypted
   * @returns {any}
   */
  #decrypt(encrypted) {
    const reb64 = window['CryptoJS'].enc.Hex.parse(encrypted);
    const result = window['CryptoJS'].AES.decrypt(
      reb64.toString(window['CryptoJS'].enc.Base64),
      window['CryptoJS'].enc.Base64.parse(window['Base64'].encode(this.#encryptKey)),
      {
        keySize: 128 / 8,
        iv: window['CryptoJS'].enc.Base64.parse(window['Base64'].encode(this.#encryptKey)),
        mode: window['CryptoJS'].mode.CBC,
        padding: window['CryptoJS'].pad.Pkcs7
      },
    );
    return result;
  };

  /**
   * @param {Array | Number | Object | String} encrypted
   * @returns {Boolean}
   */
  #decryptable(encrypted) {
    let result = false;
    if (
      this.#notDecrytableTypes.includes(this.#typeof(encrypted))
      || encrypted === void(0)
      || encrypted === null
    ) {
      result = false;
    } else if (this.#decrytableTypes.includes(this.#typeof(encrypted))) {
      result = true;
    } else if (encrypted.length === 0) {
      result = false;
    } else {
      try {
        const _loads = JSON.parse(encrypted);
        // NOTE: If loads is number, can't decrypt.
        if (this.#typeof(_loads) == 'number') {
          result = false;
        };
      } catch (e) {
        result = true;
      };
    };
    return result;
  };

  /**
   * @param {Object} encrypted
   * @param {Boolean} [log=false]
   * @returns {Object}
   */
  #dict(encrypted, log=false) {
    /** @type {Object} */
    let result = {};
    Object.keys(encrypted).forEach((key) => {
      result[this.#_(key)] = this.#_(encrypted[key], log);
    });
    return result;
  };

  /**
   * @returns {String}
   */
  get #encryptKey() {
    const result = $('#encryptKey').val();
    return result;
  };

  /**
   * @param {any[]} encrypted
   * @returns {Array}
   */
  #list(encrypted) {
    let result = JSON.parse(JSON.stringify(encrypted));
    encrypted.forEach((data, index) => {
      result[index] = this.#_(data);
    });
    return result;
  };

  /**
   * @param {Array | Object} value
   * @returns {Array | Object}
   */
  #object(value) {
    let result;
    if (this.#typeof(value) === 'object') {
      result = this.#dict(value);
    } else if (this.#typeof(value) === 'array') {;
      result = this.#list(value);
    };
    return result;
  };

  /**
   * @param {String} value
   * @param {Boolean} log
   * @returns {Array | Object | String}
   */
  #reDecrypt(value, log) {
    let result;
    const defaultValue = value;
    try {
      const decryptedParse = JSON.parse(value);
      if (this.#typeof(decryptedParse) === 'object') {
        result = this.#dict(decryptedParse, log);
      } else if (this.#typeof(decryptedParse) === 'array') {
        result = this.#list(decryptedParse);
      } else {
        result = decryptedParse;
      };
    } catch(e) {
      result = defaultValue;
    };
    return result;
  };

  /**
   * @param {String} encrypted
   * @param {Object} decrypted
   * @returns 
   */
  #toString(encrypted, decrypted) {
    let result;
    // IMPORTANCE: Error happen at decrypted is normaly string.
    try {
      result = decrypted.toString(window['CryptoJS'].enc.Utf8);
    } catch (e) {
      result = encrypted;
    };
    return result;
  };

  /**
   * @param {any} object
   * @returns {String}
   */
  #typeof(object) {
    return Object.prototype.toString.call(object).slice(8, -1).toLowerCase();
  };

  #decrytableTypes = ['object', 'array', 'string'];
  #notDecrytableTypes = ['boolean', 'number'];
};

$(() => {
  window['decryptc'] = new Decryptc();
});

class Encryptc {
  /**
   * @param {String} str
   * @returns {String}
   */
  _(str) {
    return this.#_(str);
  };
  /**
   * @param {String} str
   * @returns {String}
   */
  #_(str) {
    let result = '';
    if (str !== '') {
      const encryptedText = window['CryptoJS'].AES.encrypt(
        window['CryptoJS'].enc.Utf8.parse(str),
        window['CryptoJS'].enc.Base64.parse(window['Base64'].encode(this.#encryptKey)),
        {
          keySize: 128 / 8,
          iv: window['CryptoJS'].enc.Base64.parse(window['Base64'].encode(this.#encryptKey)),
          mode: window['CryptoJS'].mode.CBC,
          padding: window['CryptoJS'].pad.Pkcs7
        },
      );
      const b64 = encryptedText.toString();
      const e64 = window['CryptoJS'].enc.Base64.parse(b64);
      const eHex = e64.toLocaleString(window['CryptoJS'].enc.Hex);
      //result = eHex.toUpperCase();
      result = eHex;
      };
    return result;
  };

  /**
   * @returns {String}
   */
  get #encryptKey() {
    const result = $('#encryptKey').val();
    return result;
  };
};


$(() => {
  window['encryptc'] = new Encryptc();
});

class fx {
  constructor() {
    window['decrypt'] = this.decrypt;
    window['encrypt'] = this.encrypt;
    window['exec'] = this.exec;
  };

  /**
   * @param {String} str
   * @returns {String}
   */
  decrypt(str) {
    return window['decryptc']._(str);
  };

  /**
   * @param {String} str
   * @returns {String}
   */
  encrypt(str) {
    return window['encryptc']._(str);
  };

  exec() {
    const normalMH = $('#normalMH').val();
    const encryptedMH = window['encrypt'](normalMH);
    $('#encryptedMH').val(encryptedMH);

    const nameStr = window['encrypt']($('#nameStr').val());
    $('#encryptedName').val(nameStr);

    const json_ = {[nameStr]: encryptedMH};
    const jsonStr = JSON.stringify(json_)
    $('#normalJson').val(jsonStr);

    const encryptedJson = window['encrypt'](jsonStr);
    $('#encryptedJson').val(encryptedJson);

    const decryptedJson = window['decrypt'](encryptedJson);
    $('#decryptedJson').val(decryptedJson);

    $('#decryptedJsonStr').val(JSON.stringify(decryptedJson));
  };
};

$(() => {
  window['fx'] = new fx();
});

  </script>
</body>
</html>
```
***
[Webアプリケーションサーバ AiR](https://qiita.com/MH-SS/items/7a99e799e4988fdad2cc)
***
[弊社webページ: AiR](https://mhss.server-on.net/?f401c=Index&ae504=air)