0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python 暗号化・復号化

Last updated at Posted at 2024-11-22

はじめに

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=}")
# キーも値も復号化されます

Webアプリケーションサーバ AiR


弊社webページ: AiR

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?