はじめに
MH ソフトウェア & サービスが開発・公開している、Webアプリケーションサーバ AiRの便利かな?と思われるコードの紹介です。
Webアプリケーションサーバ AiRはPython、Tornado(Webフレームワーク、Webサーバ)、JavaScript、その他のモジュールで構成されています。
Webアプリケーションサーバ AiRは、httpとhttps(SSL)で稼働させることが可能です。
https(SSL)では通信内容は暗号化されると言われています。
AiRをhttp通信で使用する場合もあります。
そこで、AiRはPythonとJavaScriptで共通の暗号化・復号化を実現し、JavaScriptからのAjaxデータを暗号化して通信する方法を実現しています。
まず、"MH ソフトウェア & サービス"という文字列を暗号化してみます。
# 暗号化キーの長さは16文字です
ENCRYPT_KEY = "1234567890123456"
NORMAL_STR = "MH ソフトウェア & サービス"
encrypted_str = encrypt(NORMAL_STR, ENCRYPT_KEY)
print(f"{encrypted_str=}")
encrypted_str='beb80c687e1572b9893299376106b08a83029968c4c1b8a28fd8785d76f875e4bb7bcf35289b598316363b6b5306b205'
次に暗号化された文字列を復号化してみます。
dec = Decrypt()
decrypted_str = dec.core(encrypted_str, ENCRYPT_KEY)
print(f"{decrypted_str=}")
decrypted_str='MH ソフトウェア & サービス'
次に"name"という文字列を暗号化しdictのキーに、さらに暗号化された"MH ソフトウェア & サービス"という文字列を値にしたdictをjson.dumpsで文字列にし、暗号化します。
# 暗号化した文字列をjson形式でキーと値に入れて暗号化します
encrypted_name = encrypt("name", ENCRYPT_KEY)
print(f"{encrypted_name=}")
encrypted_json = encrypt(json.dumps({encrypted_name: encrypted_str}), ENCRYPT_KEY)
print(f"{encrypted_json=}")
encrypted_name='27126826c77d8ebac9b982928795f38a'
encrypted_json='ce7bb1ac32d66c4252dda79d7fc8735129ed1471ab1f9a39fa770ebb6e9bfd289a55c6a7bd0a979ec0980175fb5d13a2be30c74a58ddb8479842fa5f20a069ed3b1d96fbf74d556b00b14d94b088f0b95eabdc2167d766aa52fb316abc5de8551a59c835f52e8b2ccd871d5f3483a581f9763b8ace30a4ffd76aa968ed746fff26fb0b7f98b04b42d0a7e9961abf5556'
暗号化されたjson文字列を復号化すると、dict形式に戻ります。
decrypted_json = dec.core(encrypted_json, ENCRYPT_KEY)
print(type(decrypted_json), f"{decrypted_json=}")
# キーも値も復号化されます
<class 'dict'> decrypted_json={'name': 'MH ソフトウェア & サービス'}
下記が今回のサンプルのPythonスクリプトです。実際にはAiR内に、"fx"というモジュールに含まれています。
コードが長くてすいません。
"""
暗号化・復号化サンプル
"""
import binascii
import copy
import datetime
import json
from typing import (
Any,
overload,
Union,
)
from Crypto.Cipher import AES
from Crypto.Util.Padding import (
pad,
unpad,
)
import numpy as np
def encrypt(value: Any, encrypt_key: str) -> str:
"""Encrypt
:sig: (Any, str) -> str
"""
result = value
if len(encrypt_key) != 16:
return result
if value == "":
return result
if any([
isinstance(value, dict),
isinstance(value, list),
]):
_value = copy.deepcopy(value)
result = encrypt(json.dumps(_value, default=json_default), encrypt_key)
else:
_key = encrypt_key.encode()
_cipher = AES.new(_key, AES.MODE_CBC, _key) # type: ignore
try:
_encrypted = _cipher.encrypt(pad(value.encode('utf-8'), 16)) # type: ignore
except AttributeError:
pass
try:
result = binascii.hexlify(_encrypted).decode() # type: ignore
except UnboundLocalError:
pass
return result
def json_default(value: Any) -> Any:
"""Return default date format of json.
:sig: (Any) -> Any
"""
result = value
if isinstance(value, (datetime.datetime, datetime.date)):
result = value.isoformat()
elif isinstance(value, np.int64): # type: ignore
result = int(value) # type: ignore
elif isinstance(value, np.bool_):
result = bool(value)
elif isinstance(value, np.ndarray):
result = list(value) # type: ignore
#raise TypeError(repr(value) + " is not JSON serializable")
return result
# pylint: disable=too-few-public-methods
class Decrypt():
"""
Decrypt class
Decryptクラスは、受け取った暗号化されたデータの型を返します。
json.dumps()でjson文字列の暗号化された文字列は、再帰的に
"""
@overload
def core(self, encrypted: dict[str, Any], password: str) -> dict[str, Any]:
"""Decrypt. If decrypted value is list, decrypt inside.
:sig: (dict[str, Any], str) -> dict[str, Any]
"""
@overload
def core(self, encrypted: list[Any], password: str) -> list[Any]:
"""Decrypt. If decrypted value is list, decrypt inside.
:sig: (list[Any], str) -> list[Any]
"""
@overload
def core(self, encrypted: str, password: str) -> str:
"""Decrypt. If decrypted value is list, decrypt inside.
:sig: (str, str) -> str
"""
#pylint: disable-next=too-many-branches
def core(
self,
encrypted: Any,
password: str
) -> Union[dict[str, Any], list[Any], str]:
"""Decrypt. If decrypted value is list or dict, decrypt inside.
If modify this function, modify project://fx/_.js#258 #df473
:sig: (Any, str) -> Any
"""
result: Any = encrypted
if not self._decryptable(encrypted, password):
if isinstance(result, str):
try:
result = json.loads(result)
except json.decoder.JSONDecodeError:
pass
return result
# NOTE: Check encrypted is list or dict?
if isinstance(encrypted, dict) | isinstance(encrypted, list):
return self._object(encrypted, password)
# NOTE: If encrypted is json string, loads to dict or list or number.
try:
encrypted = json.loads(encrypted)
except json.decoder.JSONDecodeError:
pass
try:
_text = binascii.unhexlify(encrypted)
except binascii.Error:
return result
except ValueError:
# NOTE: "string argument should contain only ASCII characters"
# at Japanese charactor.
return result
_key = password.encode()
_cipher = AES.new(_key, AES.MODE_CBC, _key) # type: ignore
decrypted: Any = None
try:
decrypted = unpad(_cipher.decrypt(_text), 16).decode('utf-8') # type: ignore
except ValueError:
pass
# NOTE: Check decrypted is list or dict?
if decrypted is not None:
#self._redecrypt()
try:
decrypted_loads = json.loads(decrypted)
except json.decoder.JSONDecodeError:
result = decrypted
else:
if isinstance(decrypted_loads, dict):
result = self._dict(decrypted_loads, password)
elif isinstance(decrypted_loads, list):
result = self._list(decrypted_loads, password)
else:
result = decrypted_loads
return result
def _decryptable(self, encrypted: Any, password: str) -> bool:
"""Retrun encryptable.
:sig: (Any, str) -> bool
"""
result = False
if any([
encrypted is None,
len(password) != 16,
isinstance(encrypted, bool),
isinstance(encrypted, complex),
isinstance(encrypted, float),
isinstance(encrypted, datetime.datetime),
isinstance(encrypted, int),
]):
result = False
elif any([
isinstance(encrypted, dict),
isinstance(encrypted, list),
]):
result = True
elif len(encrypted) == 0:
result = False
else:
try:
_loads = json.loads(encrypted)
# NOTE: If loads is number, can't decrypt.
if any([
isinstance(_loads, complex),
isinstance(_loads, float),
isinstance(_loads, int),
]):
result = False
except json.decoder.JSONDecodeError:
result = True
return result
def _dict(self, encrypted: Any, password: str) -> dict[str, Any]:
"""Decrypt. If decrypted value is dict, decrypt inside.
:sig: (Any, str) -> dict[str, Any]
"""
result: dict[str, Any] = {}
for item in encrypted.items():
result.update({self.core(item[0], password): self.core(item[1], password)})
return result
def _list(self, encrypted: Any, password: str) -> list[Any]:
"""Decrypt. If decrypted value is list, decrypt inside.
:sig: (Any, str) -> list[Any]
"""
result = encrypted
for i, _list in enumerate(result):
result[i] = self.core(_list, password)
return result
def _object(
self,
encrypted: Any,
password: str
) -> Union[dict[str, Any], list[Any]]:
"""Decrypt. If decrypted value is list, decrypt inside.
:sig: (Any, str) -> Union[dict[str, Any], list[Any]]
"""
result: Any = None
if isinstance(encrypted, dict):
result = self._dict(encrypted, password)
if isinstance(encrypted, list):
result = self._list(encrypted, password)
return result
# 暗号化キーの長さは16文字です
ENCRYPT_KEY = "1234567890123456"
# 記号も使えます
# ENCRYPT_KEY = "#<?_'g(d%Z&9f+[!"
NORMAL_STR = "MH ソフトウェア & サービス"
encrypted_str = encrypt(NORMAL_STR, ENCRYPT_KEY)
print(f"{encrypted_str=}")
dec = Decrypt()
decrypted_str = dec.core(encrypted_str, ENCRYPT_KEY)
print(f"{decrypted_str=}")
# 暗号化した文字列をjson形式でキーと値に入れて暗号化します
encrypted_name = encrypt("name", ENCRYPT_KEY)
print(f"{encrypted_name=}")
encrypted_json = encrypt(json.dumps({encrypted_name: encrypted_str}), ENCRYPT_KEY)
print(f"{encrypted_json=}")
decrypted_json = dec.core(encrypted_json, ENCRYPT_KEY)
print(type(decrypted_json), f"{decrypted_json=}")
# キーも値も復号化されます