diff --git a/bkcrypto/contrib/django/ciphers.py b/bkcrypto/contrib/django/ciphers.py index 3ab4ed7..e64cc16 100644 --- a/bkcrypto/contrib/django/ciphers.py +++ b/bkcrypto/contrib/django/ciphers.py @@ -17,7 +17,7 @@ from bkcrypto.symmetric.ciphers import BaseSymmetricCipher from bkcrypto.symmetric.options import SymmetricOptions -from .init_configs import SymmetricCipherInitConfig +from .init_configs import AsymmetricCipherInitConfig, SymmetricCipherInitConfig from .settings import crypto_settings @@ -80,4 +80,32 @@ def cipher( return cipher +class AsymmetricCipherManager: + _cache: typing.Optional[typing.Dict[str, BaseAsymmetricCipher]] = None + + def __init__(self): + self._cache: [str, BaseAsymmetricCipher] = {} + + def cipher( + self, using: typing.Optional[str] = None, cipher_type: typing.Optional[str] = None + ) -> BaseAsymmetricCipher: + + using: str = using or "default" + if using not in crypto_settings.ASYMMETRIC_CIPHERS: + raise RuntimeError(f"Invalid using {using}") + + cipher_type: str = cipher_type or crypto_settings.ASYMMETRIC_CIPHER_TYPE + cache_key: str = f"{using}-{cipher_type}" + if cache_key in self._cache: + return self._cache[cache_key] + + init_config: AsymmetricCipherInitConfig = crypto_settings.ASYMMETRIC_CIPHERS[using] + cipher: BaseAsymmetricCipher = get_asymmetric_cipher(**init_config.as_get_cipher_params(cipher_type)) + self._cache[cache_key] = cipher + return cipher + + symmetric_cipher_manager = SymmetricCipherManager() + + +asymmetric_cipher_manager = AsymmetricCipherManager() diff --git a/bkcrypto/contrib/django/init_configs.py b/bkcrypto/contrib/django/init_configs.py index 304b9c4..81f2093 100644 --- a/bkcrypto/contrib/django/init_configs.py +++ b/bkcrypto/contrib/django/init_configs.py @@ -14,31 +14,34 @@ import typing from dataclasses import asdict, dataclass -from bkcrypto.symmetric.configs import KeyConfig +from bkcrypto.asymmetric.configs import KeyConfig as AsymmetricKeyConfig +from bkcrypto.asymmetric.options import AsymmetricOptions +from bkcrypto.symmetric.configs import KeyConfig as SymmetricKeyConfig from bkcrypto.symmetric.options import SymmetricOptions from bkcrypto.utils.module_loding import import_string @dataclass -class SymmetricCipherInitConfig: - get_key_config: typing.Optional[str] = None +class CipherInitConfig: # 默认取值 f"{cipher_type}_str:::" db_prefix_map: typing.Dict[str, str] = None prefix_cipher_type_map: typing.Dict[str, str] = None - get_key_config_func: typing.Optional[typing.Callable[[str], KeyConfig]] = None + get_key_config: typing.Optional[str] = None + get_key_config_func: typing.Optional[ + typing.Callable[[str], typing.Union[AsymmetricKeyConfig, SymmetricKeyConfig]] + ] = None common: typing.Optional[typing.Dict[str, typing.Any]] = None - cipher_options: typing.Optional[typing.Dict[str, typing.Optional[SymmetricOptions]]] = None + cipher_options: typing.Optional[typing.Dict[str, typing.Union[SymmetricOptions, AsymmetricOptions, None]]] = None def __post_init__(self): - self.db_prefix_map = self.db_prefix_map or {} - if self.get_key_config: self.get_key_config_func = import_string(self.get_key_config) + self.db_prefix_map = self.db_prefix_map or {} def as_get_cipher_params(self, cipher_type: str): # get key hook 不为空,优先从此处取 key if self.get_key_config_func: - key_config: KeyConfig = self.get_key_config_func(cipher_type) + key_config = self.get_key_config_func(cipher_type) key_dict: typing.Dict = asdict(key_config) else: key_dict: typing.Dict = {} @@ -46,3 +49,13 @@ def as_get_cipher_params(self, cipher_type: str): common = self.common or {} common.update(key_dict) return {"cipher_type": cipher_type, "common": common, "cipher_options": self.cipher_options} + + +@dataclass +class SymmetricCipherInitConfig(CipherInitConfig): + get_key_config_func: typing.Optional[typing.Callable[[str], SymmetricKeyConfig]] = None + + +@dataclass +class AsymmetricCipherInitConfig(CipherInitConfig): + get_key_config_func: typing.Optional[typing.Callable[[str], AsymmetricKeyConfig]] = None diff --git a/bkcrypto/contrib/django/settings.py b/bkcrypto/contrib/django/settings.py index c754ac7..7ea7f9b 100644 --- a/bkcrypto/contrib/django/settings.py +++ b/bkcrypto/contrib/django/settings.py @@ -17,7 +17,7 @@ from bkcrypto import constants from bkcrypto.asymmetric.ciphers import RSAAsymmetricCipher, SM2AsymmetricCipher -from bkcrypto.contrib.django.init_configs import SymmetricCipherInitConfig +from bkcrypto.contrib.django.init_configs import AsymmetricCipherInitConfig, CipherInitConfig, SymmetricCipherInitConfig from bkcrypto.symmetric.ciphers import AESSymmetricCipher, SM4SymmetricCipher from bkcrypto.utils import module_loding @@ -43,6 +43,17 @@ }, }, }, + "ASYMMETRIC_CIPHERS": { + "default": { + # 可选,用于在 settings 没法直接获取 key 的情况 + "get_key_config": None, + # 前缀和 cipher type 必须一一对应,且不能有前缀匹配关系 + "db_prefix_map": { + constants.AsymmetricCipherType.RSA.value: f"{constants.AsymmetricCipherType.RSA.value.lower()}_str:::", + constants.AsymmetricCipherType.SM2.value: f"{constants.AsymmetricCipherType.SM2.value.lower()}_str:::", + }, + }, + }, } IMPORT_STRINGS = [] @@ -107,12 +118,16 @@ def __getattr__(self, attr): for cipher_type, cipher_import_path in val.items() } - if attr in ["SYMMETRIC_CIPHERS"]: - using__init_config_map: typing.Dict[str, SymmetricCipherInitConfig] = {} + if attr in ["SYMMETRIC_CIPHERS", "ASYMMETRIC_CIPHERS"]: + using__init_config_map: typing.Dict[str, CipherInitConfig] = {} for using, init_config_params in val.items(): prefix_cipher_type_map: typing.Dict[str, str] = {} db_prefix_map: typing.Dict[str, str] = init_config_params.get("db_prefix_map") or {} - for cipher_type in self.SYMMETRIC_CIPHER_CLASSES.keys(): + cipher_types: typing.List[str] = [ + self.SYMMETRIC_CIPHER_CLASSES.keys(), + self.ASYMMETRIC_CIPHER_CLASSES.keys(), + ][attr == "ASYMMETRIC_CIPHERS"] + for cipher_type in cipher_types: if cipher_type in db_prefix_map: prefix_cipher_type_map[db_prefix_map[cipher_type]] = cipher_type continue @@ -120,7 +135,10 @@ def __getattr__(self, attr): prefix_cipher_type_map[f"{cipher_type.lower()}_str:::"] = cipher_type init_config_params["db_prefix_map"] = db_prefix_map init_config_params["prefix_cipher_type_map"] = prefix_cipher_type_map - using__init_config_map[using] = from_dict(SymmetricCipherInitConfig, init_config_params) + using__init_config_map[using] = from_dict( + [SymmetricCipherInitConfig, AsymmetricCipherInitConfig][attr == "ASYMMETRIC_CIPHER_CLASSES"], + init_config_params, + ) val = using__init_config_map # Coerce import strings into classes diff --git a/pyproject.toml b/pyproject.toml index 17930d2..a4404f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bk-crypto-python-sdk" -version = "1.0.2" +version = "1.0.3" description = "bk-crypto-python-sdk is a lightweight cryptography toolkit for Python applications based on Cryptodome / tongsuopy and other encryption libraries." authors = ["TencentBlueKing "] readme = "readme.md" diff --git a/readme.md b/readme.md index 68dbb40..12d8952 100644 --- a/readme.md +++ b/readme.md @@ -41,9 +41,9 @@ $ pip install bk-crypto-python-sdk 在项目中配置 ```python -import os from bkcrypto import constants from bkcrypto.symmetric.options import AESSymmetricOptions, SM4SymmetricOptions +from bkcrypto.asymmetric.options import RSAAsymmetricOptions BKCRYPTO = { # 声明项目所使用的非对称加密算法 @@ -69,12 +69,26 @@ BKCRYPTO = { constants.SymmetricCipherType.SM4.value: SM4SymmetricOptions(mode=constants.SymmetricMode.CTR) } }, + }, + "ASYMMETRIC_CIPHERS": { + # 配置同 SYMMETRIC_CIPHERS + "default": { + "common": {"public_key_string": "your key"}, + "cipher_options": { + constants.AsymmetricCipherType.RSA.value: RSAAsymmetricOptions( + padding=constants.RSACipherPadding.PKCS1_OAEP + ), + constants.AsymmetricCipherType.SM2.value: SM4SymmetricOptions() + }, + }, } } ``` #### 非对称加密 +使用 `get_asymmetric_cipher` 获取 `cipher` + ```python from bkcrypto.asymmetric.ciphers import BaseAsymmetricCipher from bkcrypto.contrib.django.ciphers import get_asymmetric_cipher @@ -87,8 +101,34 @@ assert "123" == asymmetric_cipher.decrypt(asymmetric_cipher.encrypt("123")) assert asymmetric_cipher.verify(plaintext="123", signature=asymmetric_cipher.sign("123")) ``` +使用 `asymmetric_cipher_manager` 获取 `BKCRYPTO.ASYMMETRIC_CIPHERS` 配置的 `cipher` + +```python +from bkcrypto.asymmetric.ciphers import BaseAsymmetricCipher +from bkcrypto.contrib.django.ciphers import asymmetric_cipher_manager + +asymmetric_cipher: BaseAsymmetricCipher = asymmetric_cipher_manager.cipher(using="default") + +# 加解密 +assert "123" == asymmetric_cipher.decrypt(asymmetric_cipher.encrypt("123")) +# 验签 +assert asymmetric_cipher.verify(plaintext="123", signature=asymmetric_cipher.sign("123")) +``` + #### 对称加密 +使用 `get_symmetric_cipher` 获取 `cipher` + +```python +from bkcrypto.symmetric.ciphers import BaseSymmetricCipher +from bkcrypto.contrib.django.ciphers import get_symmetric_cipher + +symmetric_cipher: BaseSymmetricCipher = get_symmetric_cipher() +assert "123" == symmetric_cipher.decrypt(symmetric_cipher.encrypt("123")) +``` + +使用 `symmetric_cipher_manager` 获取 `BKCRYPTO.SYMMETRIC_CIPHERS` 配置的 `cipher` + ```python from bkcrypto.symmetric.ciphers import BaseSymmetricCipher from bkcrypto.contrib.django.ciphers import symmetric_cipher_manager diff --git a/readme_en.md b/readme_en.md index f162451..4683adb 100644 --- a/readme_en.md +++ b/readme_en.md @@ -38,45 +38,60 @@ $ pip install bk-crypto-python-sdk ### Usage -> For more usage, refer +> For more usage guidelines, please refer > to: [Usage Documentation](https://github.com/TencentBlueKing/crypto-python-sdk/blob/main/docs/usage.md) -Configure in the project +Configure in the project: ```python -import os from bkcrypto import constants from bkcrypto.symmetric.options import AESSymmetricOptions, SM4SymmetricOptions +from bkcrypto.asymmetric.options import RSAAsymmetricOptions BKCRYPTO = { - # Declare the asymmetric encryption algorithm used in the project + # Specify the asymmetric encryption algorithm used in the project "ASYMMETRIC_CIPHER_TYPE": constants.AsymmetricCipherType.SM2.value, - # Declare the symmetric encryption algorithm used in the project + # Specify the symmetric encryption algorithm used in the project "SYMMETRIC_CIPHER_TYPE": constants.SymmetricCipherType.SM4.value, "SYMMETRIC_CIPHERS": { - # default - The configured symmetric encryption instance, multiple instances can be configured according to project needs + # default - The configured symmetric encryption instance can be configured with multiple instances according to project needs "default": { - # Optional, used when the key cannot be obtained directly in settings + # Optional, for scenarios where the key cannot be obtained directly from settings # "get_key_config": "apps.utils.encrypt.key.get_key_config", - # Optional, used for ModelField, when encrypting, it carries this prefix into the database, when decrypting, it analyzes this prefix and selects the corresponding decryption algorithm - # ⚠️ Prefix and cipher type must correspond one-to-one, and there can be no prefix matching relationship + # Optional, used for ModelField; carry the prefix when encrypting and store it in the database, + # analyze the prefix when decrypting and choose the appropriate decryption algorithm + # ⚠️ Prefix and cipher type must correspond one-to-one, and no prefix match relationship is allowed # "db_prefix_map": { # SymmetricCipherType.AES.value: "aes_str:::", # SymmetricCipherType.SM4.value: "sm4_str:::" # }, - # Common parameter configuration, different cipher initialization shares these parameters + # Common parameter configuration, shared by different ciphers during initialization "common": {"key": "your key"}, "cipher_options": { constants.SymmetricCipherType.AES.value: AESSymmetricOptions(key_size=16), - # BlueKing recommended configuration + # Recommended configuration by BlueKing constants.SymmetricCipherType.SM4.value: SM4SymmetricOptions(mode=constants.SymmetricMode.CTR) } }, + }, + "ASYMMETRIC_CIPHERS": { + # Configuration is the same as SYMMETRIC_CIPHERS + "default": { + "common": {"public_key_string": "your key"}, + "cipher_options": { + constants.AsymmetricCipherType.RSA.value: RSAAsymmetricOptions( + padding=constants.RSACipherPadding.PKCS1_OAEP + ), + constants.AsymmetricCipherType.SM2.value: SM4SymmetricOptions() + }, + }, } } ``` -#### Asymmetric encryption +#### Asymmetric Encryption + +Use `get_asymmetric_cipher` to get the `cipher` ```python from bkcrypto.asymmetric.ciphers import BaseAsymmetricCipher @@ -86,17 +101,43 @@ asymmetric_cipher: BaseAsymmetricCipher = get_asymmetric_cipher() # Encrypt and decrypt assert "123" == asymmetric_cipher.decrypt(asymmetric_cipher.encrypt("123")) -# Verify signature +# Signature verification +assert asymmetric_cipher.verify(plaintext="123", signature=asymmetric_cipher.sign("123")) +``` + +Use `asymmetric_cipher_manager` to get the `BKCRYPTO.ASYMMETRIC_CIPHERS` configured `cipher` + +```python +from bkcrypto.asymmetric.ciphers import BaseAsymmetricCipher +from bkcrypto.contrib.django.ciphers import asymmetric_cipher_manager + +asymmetric_cipher: BaseAsymmetricCipher = asymmetric_cipher_manager.cipher(using="default") + +# Encrypt and decrypt +assert "123" == asymmetric_cipher.decrypt(asymmetric_cipher.encrypt("123")) +# Signature verification assert asymmetric_cipher.verify(plaintext="123", signature=asymmetric_cipher.sign("123")) ``` -#### Symmetric encryption +#### Symmetric Encryption + +Use `get_symmetric_cipher` to get the `cipher` + +```python +from bkcrypto.symmetric.ciphers import BaseSymmetricCipher +from bkcrypto.contrib.django.ciphers import get_symmetric_cipher + +symmetric_cipher: BaseSymmetricCipher = get_symmetric_cipher() +assert "123" == symmetric_cipher.decrypt(symmetric_cipher.encrypt("123")) +``` + +Use `symmetric_cipher_manager` to get the `BKCRYPTO.SYMMETRIC_CIPHERS` configured `cipher` ```python from bkcrypto.symmetric.ciphers import BaseSymmetricCipher from bkcrypto.contrib.django.ciphers import symmetric_cipher_manager -# using - Specify the symmetric encryption instance, the default is `default` +# using - Specify the symmetric encryption instance, using `default` by default symmetric_cipher: BaseSymmetricCipher = symmetric_cipher_manager.cipher(using="default") assert "123" == symmetric_cipher.decrypt(symmetric_cipher.encrypt("123")) ``` diff --git a/release.md b/release.md index 38a7508..38d1d5d 100644 --- a/release.md +++ b/release.md @@ -24,3 +24,10 @@ ### Feature * [ Feature ] Add support for backward compatibility to Python v3.6.2 ([#12](https://github.com/TencentBlueKing/crypto-python-sdk/issues/12)) + + +## 1.0.3 - 2023-07-19 + +### Feature + +* [ Feature ] Support configuring AsymmetricCipherManager through Django settings ([#14](https://github.com/TencentBlueKing/crypto-python-sdk/issues/14))