diff --git a/bkcrypto/asymmetric/ciphers/rsa.py b/bkcrypto/asymmetric/ciphers/rsa.py index 96af95f..84e39a5 100644 --- a/bkcrypto/asymmetric/ciphers/rsa.py +++ b/bkcrypto/asymmetric/ciphers/rsa.py @@ -14,15 +14,13 @@ """ import typing -from dataclasses import dataclass, field +from dataclasses import dataclass from bkcrypto import constants, types -from Cryptodome.Cipher import PKCS1_OAEP -from Cryptodome.Cipher.PKCS1_OAEP import PKCS1OAEP_Cipher -from Cryptodome.Cipher.PKCS1_v1_5 import PKCS115_Cipher -from Cryptodome.Hash import SHA1 -from Cryptodome.PublicKey import RSA -from Cryptodome.Signature.pss import MGF1 +from cryptography import exceptions +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding as asymmetric_padding +from cryptography.hazmat.primitives.asymmetric import rsa from .. import configs from ..options import RSAAsymmetricOptions @@ -35,38 +33,8 @@ class RSAAsymmetricRuntimeConfig( ): """Store normalized RSA runtime configuration.""" - public_key: typing.Optional[RSA.RsaKey] = None - private_key: typing.Optional[RSA.RsaKey] = None - - cipher_maker: types.RSACipherMaker = field(init=False) - sig_scheme_maker: types.RSASigSchemeMaker = field(init=False) - - def __post_init__(self) -> None: - if self.padding == constants.RSACipherPadding.PKCS1_OAEP: - self.cipher_maker = self._make_oaep_cipher - else: - self.cipher_maker = constants.RSACipherPadding.get_cipher_maker_by_member( - self.padding - ) - self.sig_scheme_maker = constants.RSASigScheme.get_sig_scheme_maker_by_member( - self.sig_scheme - ) - - super().__post_init__() - - def _make_oaep_cipher(self, key: RSA.RsaKey) -> types.RSACipher: - return PKCS1_OAEP.new( - key, - hashAlgo=self.oaep_hash, - mgfunc=self._mgf1, - label=self.oaep_label or b"", - ) - - def _mgf1(self, seed: bytes, length: int) -> bytes: - # PyCryptodome's MGF1 stub does not accept an equivalent external hash - # protocol, so narrow that mismatch at the dependency boundary. - mgf1 = typing.cast("types.MaskFunction", MGF1) - return mgf1(seed, length, self.mgf1_hash) + public_key: typing.Optional[rsa.RSAPublicKey] = None + private_key: typing.Optional[rsa.RSAPrivateKey] = None class RSAAsymmetricCipher(base.BaseAsymmetricCipher[RSAAsymmetricRuntimeConfig]): @@ -78,65 +46,115 @@ class RSAAsymmetricCipher(base.BaseAsymmetricCipher[RSAAsymmetricRuntimeConfig]) OPTIONS_DATA_CLASS = RSAAsymmetricOptions - def _public_key(self) -> RSA.RsaKey: - public_key: typing.Optional[RSA.RsaKey] = self.config.public_key + def _public_key(self) -> rsa.RSAPublicKey: + public_key: typing.Optional[rsa.RSAPublicKey] = self.config.public_key if public_key is None: raise ValueError("RSA public key is not configured") return public_key - def _private_key(self) -> RSA.RsaKey: - private_key: typing.Optional[RSA.RsaKey] = self.config.private_key + def _private_key(self) -> rsa.RSAPrivateKey: + private_key: typing.Optional[rsa.RSAPrivateKey] = self.config.private_key if private_key is None: raise ValueError("RSA private key is not configured") return private_key def export_public_key(self) -> str: - return self._public_key().exportKey().decode(encoding=self.config.encoding) + public_key: bytes = self._public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + return public_key.decode(encoding=self.config.encoding) def export_private_key(self) -> str: - return self._private_key().exportKey().decode(encoding=self.config.encoding) + private_key: bytes = self._private_key().private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + return private_key.decode(encoding=self.config.encoding) + + def _load_public_key( + self, public_key_string: types.PublicKeyString + ) -> rsa.RSAPublicKey: + key_bytes: bytes = public_key_string.encode(self.config.encoding) + key_loaders: tuple[typing.Callable[[], object], ...] = ( + lambda: serialization.load_pem_public_key(key_bytes), + lambda: serialization.load_ssh_public_key(key_bytes), + lambda: serialization.load_pem_private_key(key_bytes, password=None), + ) + last_error: typing.Optional[Exception] = None - def _load_public_key(self, public_key_string: types.PublicKeyString) -> RSA.RsaKey: - try: - public_key: RSA.RsaKey = RSA.import_key( - public_key_string.encode(self.config.encoding) - ) - except (IndexError, TypeError, ValueError) as error: - raise ValueError("Invalid RSA public key") from error - if public_key.has_private(): - return public_key.publickey() - return public_key + for load_key in key_loaders: + try: + loaded_key: object = load_key() + except (TypeError, ValueError, exceptions.UnsupportedAlgorithm) as error: + last_error = error + continue + + if isinstance(loaded_key, rsa.RSAPrivateKey): + return loaded_key.public_key() + if isinstance(loaded_key, rsa.RSAPublicKey): + return loaded_key + raise ValueError("Invalid RSA public key") # noqa: TRY004 + + raise ValueError("Invalid RSA public key") from last_error def _load_private_key( self, private_key_string: types.PrivateKeyString - ) -> RSA.RsaKey: + ) -> rsa.RSAPrivateKey: try: - private_key: RSA.RsaKey = RSA.import_key( - private_key_string.encode(self.config.encoding) + loaded_key: object = serialization.load_pem_private_key( + private_key_string.encode(self.config.encoding), password=None ) - except (IndexError, TypeError, ValueError) as error: + except (TypeError, ValueError, exceptions.UnsupportedAlgorithm) as error: raise ValueError("Invalid RSA private key") from error - if not private_key.has_private(): - raise ValueError("Expected an RSA private key") - return private_key + if not isinstance(loaded_key, rsa.RSAPrivateKey): + raise ValueError("Expected an RSA private key") # noqa: TRY004 + return loaded_key def generate_key_pair( self, ) -> tuple[types.PrivateKeyString, types.PublicKeyString]: - private_key_obj: RSA.RsaKey = RSA.generate(self.config.pkey_bits) - private_key: bytes = private_key_obj.export_key(format="PEM", pkcs=1) - public_key: bytes = private_key_obj.publickey().export_key(format="PEM") + private_key_obj: rsa.RSAPrivateKey = rsa.generate_private_key( + public_exponent=65537, key_size=self.config.pkey_bits + ) + private_key: bytes = private_key_obj.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + public_key: bytes = private_key_obj.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) return private_key.decode(encoding=self.config.encoding), public_key.decode( encoding=self.config.encoding ) + def _encryption_padding(self) -> asymmetric_padding.AsymmetricPadding: + if self.config.padding == constants.RSACipherPadding.PKCS1_OAEP: + return asymmetric_padding.OAEP( + mgf=asymmetric_padding.MGF1(self.config.mgf1_hash), + algorithm=self.config.oaep_hash, + label=self.config.oaep_label, + ) + return asymmetric_padding.PKCS1v15() + + def _signature_padding(self) -> asymmetric_padding.AsymmetricPadding: + if self.config.sig_scheme == constants.RSASigScheme.PKCS1_PSS: + digest_size: int = hashes.SHA1().digest_size + return asymmetric_padding.PSS( + mgf=asymmetric_padding.MGF1(hashes.SHA1()), + salt_length=digest_size, + ) + return asymmetric_padding.PKCS1v15() + def _encrypt(self, plaintext_bytes: bytes) -> bytes: block_size: int = self._get_encrypt_block_size() if not self.config.enable_segmented_encryption: return self._encrypt_bytes(plaintext_bytes) - cipher: types.RSACipher = self.config.cipher_maker(self._public_key()) return b"".join( - cipher.encrypt(block) + self._encrypt_block(block) for block in self.block_list(plaintext_bytes, block_size) ) @@ -147,37 +165,34 @@ def _decrypt(self, ciphertext_bytes: bytes) -> bytes: block_size: int = self.get_block_size(self._private_key(), is_encrypt=False) if len(ciphertext_bytes) % block_size: raise ValueError("Invalid RSA ciphertext length") - cipher: types.RSACipher = self.config.cipher_maker(self._private_key()) return b"".join( - self._decrypt_block(cipher, block) + self._decrypt_block(block) for block in self.block_list(ciphertext_bytes, block_size) ) def _encrypt_bytes(self, plaintext_bytes: bytes) -> bytes: if len(plaintext_bytes) > self._get_encrypt_block_size(): raise ValueError("RSA plaintext is too long") - cipher: types.RSACipher = self.config.cipher_maker(self._public_key()) - return cipher.encrypt(plaintext_bytes) + return self._encrypt_block(plaintext_bytes) def _decrypt_bytes(self, ciphertext_bytes: bytes) -> bytes: block_size: int = self.get_block_size(self._private_key(), is_encrypt=False) if len(ciphertext_bytes) != block_size: raise ValueError("Invalid RSA ciphertext length") - cipher: types.RSACipher = self.config.cipher_maker(self._private_key()) - return self._decrypt_block(cipher, ciphertext_bytes) + return self._decrypt_block(ciphertext_bytes) - def _decrypt_block(self, cipher: types.RSACipher, ciphertext_bytes: bytes) -> bytes: - if self.config.padding == constants.RSACipherPadding.PKCS1_OAEP: - if not isinstance(cipher, PKCS1OAEP_Cipher): - raise TypeError("RSA OAEP factory returned an invalid cipher") - return cipher.decrypt(ciphertext_bytes) + def _encrypt_block(self, plaintext_bytes: bytes) -> bytes: + return self._public_key().encrypt(plaintext_bytes, self._encryption_padding()) - if not isinstance(cipher, PKCS115_Cipher): - raise TypeError("RSA PKCS#1 v1.5 factory returned an invalid cipher") - plaintext_bytes: typing.Optional[bytes] = cipher.decrypt(ciphertext_bytes, None) - if plaintext_bytes is None: - raise ValueError("Invalid RSA ciphertext") - return plaintext_bytes + def _decrypt_block(self, ciphertext_bytes: bytes) -> bytes: + try: + return self._private_key().decrypt( + ciphertext_bytes, self._encryption_padding() + ) + except ValueError as error: + if self.config.padding == constants.RSACipherPadding.PKCS1_OAEP: + raise ValueError("Incorrect decryption.") from error + raise ValueError("Invalid RSA ciphertext") from error def _get_encrypt_block_size(self) -> int: return self.get_block_size( @@ -187,28 +202,27 @@ def _get_encrypt_block_size(self) -> int: ) def _sign(self, plaintext_bytes: bytes) -> bytes: - sig_scheme: types.RSASigScheme = self.config.sig_scheme_maker( - self._private_key() + return self._private_key().sign( + plaintext_bytes, self._signature_padding(), hashes.SHA1() ) - # PyCryptodome's PSS stub names update()'s parameter differently from - # its SHA1 stub, making the documented companion types incompatible. - sha: typing.Any = SHA1.new(plaintext_bytes) - return sig_scheme.sign(sha) def _verify(self, plaintext_bytes: bytes, signature_types: bytes) -> bool: - sig_scheme: types.RSASigScheme = self.config.sig_scheme_maker(self._public_key()) - sha: typing.Any = SHA1.new(plaintext_bytes) try: - sig_scheme.verify(sha, signature_types) - except (TypeError, ValueError): + self._public_key().verify( + signature_types, + plaintext_bytes, + self._signature_padding(), + hashes.SHA1(), + ) + except (exceptions.InvalidSignature, TypeError, ValueError): return False return True @staticmethod - def load_public_key_from_pkey(private_key: object) -> RSA.RsaKey: - if not isinstance(private_key, RSA.RsaKey): + def load_public_key_from_pkey(private_key: object) -> rsa.RSAPublicKey: + if not isinstance(private_key, rsa.RSAPrivateKey): raise TypeError("Expected an RSA private key") - return private_key.publickey() + return private_key.public_key() @staticmethod def block_list(lst: bytes, block_size: int) -> typing.Iterator[bytes]: @@ -226,20 +240,21 @@ def get_block_size( key_obj: object, is_encrypt: bool = True, padding: constants.RSACipherPadding = constants.RSACipherPadding.PKCS1_v1_5, - oaep_hash: types.HashModule = configs.DEFAULT_RSA_HASH, + oaep_hash: hashes.HashAlgorithm = configs.DEFAULT_RSA_HASH, ) -> int: """Return the maximum RSA block size in bytes. :param key_obj: Parsed RSA key whose modulus determines the block size. :param is_encrypt: Whether to calculate plaintext rather than ciphertext size. :param padding: Padding scheme whose overhead limits plaintext capacity. - :param oaep_hash: Hash module used to calculate OAEP padding overhead. + :param oaep_hash: Hash algorithm used to calculate OAEP padding overhead. :return: Maximum plaintext size or ciphertext block size, in bytes. """ - if not isinstance(key_obj, RSA.RsaKey): + if not isinstance(key_obj, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): raise TypeError("Expected an RSA key") + block_size: int = (key_obj.key_size + 7) // 8 if not is_encrypt: - return key_obj.size_in_bytes() + return block_size if padding == constants.RSACipherPadding.PKCS1_OAEP: - return key_obj.size_in_bytes() - 2 * oaep_hash.digest_size - 2 - return key_obj.size_in_bytes() - 11 + return block_size - 2 * oaep_hash.digest_size - 2 + return block_size - 11 diff --git a/bkcrypto/asymmetric/configs.py b/bkcrypto/asymmetric/configs.py index 7dc5ef0..6bba09e 100644 --- a/bkcrypto/asymmetric/configs.py +++ b/bkcrypto/asymmetric/configs.py @@ -14,17 +14,15 @@ """ import typing -from dataclasses import dataclass +from dataclasses import dataclass, field from bkcrypto import constants, types from bkcrypto.utils import convertors -from Cryptodome.Hash import SHA1 +from cryptography.hazmat.primitives import hashes from . import interceptors -# PyCryptodome's runtime module and stub-only hash protocol cannot share one -# nominal type. Keep this single cast at the library boundary. -DEFAULT_RSA_HASH = typing.cast("types.HashModule", SHA1) +DEFAULT_RSA_HASH: hashes.HashAlgorithm = hashes.SHA1() @dataclass @@ -66,10 +64,10 @@ class BaseRSAAsymmetricConfig(BaseAsymmetricConfig): # 加解密填充方案,默认为 `PKCS1_v1_5` padding: constants.RSACipherPadding = constants.RSACipherPadding.PKCS1_v1_5 - # OAEP 哈希算法,默认保留 PyCryptodome 的 SHA-1 行为 - oaep_hash: types.HashModule = DEFAULT_RSA_HASH + # OAEP 哈希算法,默认保留历史 SHA-1 行为 + oaep_hash: hashes.HashAlgorithm = field(default_factory=hashes.SHA1) # MGF1 哈希算法,默认与历史 OAEP 行为一致 - mgf1_hash: types.HashModule = DEFAULT_RSA_HASH + mgf1_hash: hashes.HashAlgorithm = field(default_factory=hashes.SHA1) # OAEP label,None 表示空 label oaep_label: typing.Optional[bytes] = None # 是否按 RSA 最大明文长度分段,默认保留历史行为 @@ -78,7 +76,7 @@ class BaseRSAAsymmetricConfig(BaseAsymmetricConfig): sig_scheme: constants.RSASigScheme = constants.RSASigScheme.PKCS1_v1_5 # 密钥长度(bit) # In 2017, a sufficient length is deemed to be 2048 bits. - # 具体参考 -> https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html + # 具体参考 -> https://cryptography.io/en/stable/hazmat/primitives/asymmetric/rsa/ pkey_bits: int = 2048 diff --git a/bkcrypto/constants.py b/bkcrypto/constants.py index 40d424d..1cb2483 100644 --- a/bkcrypto/constants.py +++ b/bkcrypto/constants.py @@ -14,12 +14,7 @@ """ from enum import Enum - -from Cryptodome.Cipher import PKCS1_OAEP -from Cryptodome.Cipher import PKCS1_v1_5 as PKCS1_v1_5_cipher -from Cryptodome.Signature import pkcs1_15, pss - -from . import types +from functools import cache class AsymmetricKeyAttribute(Enum): @@ -35,16 +30,6 @@ class RSACipherPadding(Enum): PKCS1_v1_5 = "PKCS1_v1_5" PKCS1_OAEP = "PKCS1_OAEP" - @classmethod - def get_cipher_maker_by_member( - cls, member: "RSACipherPadding" - ) -> types.RSACipherMaker: - makers: dict[RSACipherPadding, types.RSACipherMaker] = { - cls.PKCS1_OAEP: PKCS1_OAEP.new, - cls.PKCS1_v1_5: PKCS1_v1_5_cipher.new, - } - return makers[member] - class RSASigScheme(Enum): """签名对象.""" @@ -52,16 +37,6 @@ class RSASigScheme(Enum): PKCS1_v1_5 = "PKCS1_v1_5" PKCS1_PSS = "PKCS1_PSS" - @classmethod - def get_sig_scheme_maker_by_member( - cls, member: "RSASigScheme" - ) -> types.RSASigSchemeMaker: - makers: dict[RSASigScheme, types.RSASigSchemeMaker] = { - cls.PKCS1_PSS: pss.new, - cls.PKCS1_v1_5: pkcs1_15.new, - } - return makers[member] - class SymmetricMode(Enum): """非对称块加密模式.""" @@ -71,6 +46,18 @@ class SymmetricMode(Enum): GCM = "GCM" CFB = "CFB" + @classmethod + @cache + def members(cls) -> frozenset["SymmetricMode"]: + """Return all supported symmetric modes.""" + return frozenset(cls) + + @classmethod + @cache + def block_size_iv_modes(cls) -> frozenset["SymmetricMode"]: + """Return modes whose IV must match the cipher block size.""" + return frozenset({cls.CBC, cls.CFB, cls.CTR}) + class SymmetricPadding(Enum): """对称加密填充方案.""" diff --git a/bkcrypto/symmetric/ciphers/aes.py b/bkcrypto/symmetric/ciphers/aes.py index 2f0632d..239a00e 100644 --- a/bkcrypto/symmetric/ciphers/aes.py +++ b/bkcrypto/symmetric/ciphers/aes.py @@ -17,23 +17,35 @@ from dataclasses import dataclass from bkcrypto import constants -from Cryptodome.Cipher import AES -from Cryptodome.Util import Counter -from Cryptodome.Util.Padding import pad, unpad +from cryptography import exceptions +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from typing_extensions import TypeAlias from .. import configs, options from . import base if typing.TYPE_CHECKING: - # PyCryptodome's public overloads name their return types from these modules. - from Cryptodome.Cipher._mode_cbc import CbcMode - from Cryptodome.Cipher._mode_cfb import CfbMode - from Cryptodome.Cipher._mode_ctr import CtrMode - from Cryptodome.Cipher._mode_gcm import GcmMode - -AESStandardModeContext: TypeAlias = typing.Union["CtrMode", "CbcMode", "CfbMode"] -AESModeContext: TypeAlias = typing.Union[AESStandardModeContext, "GcmMode"] + from cryptography.hazmat.decrepit.ciphers import modes as legacy_modes + from cryptography.hazmat.primitives.ciphers.base import ( + AEADDecryptionContext, + AEADEncryptionContext, + ) +else: + try: + from cryptography.hazmat.decrepit.ciphers import modes as legacy_modes + except ImportError: + from cryptography.hazmat.primitives.ciphers import modes as legacy_modes + +AES_BLOCK_SIZE = 16 +AES_KEY_SIZES = (16, 24, 32) + +AESMode: TypeAlias = typing.Union[ + modes.CBC, + modes.CTR, + legacy_modes.CFB8, + modes.GCM, +] @dataclass @@ -45,25 +57,20 @@ class AESSymmetricRuntimeConfig( def __post_init__(self) -> None: super().__post_init__() - if self.key_size not in AES.key_size: + if self.key_size not in AES_KEY_SIZES: raise ValueError( - f"Optional key sizes are {AES.key_size}, but got {self.key_size}" + f"Optional key sizes are {AES_KEY_SIZES}, but got {self.key_size}" ) if ( - self.mode in {constants.SymmetricMode.CBC, constants.SymmetricMode.CTR} - and self.iv_size != AES.block_size + self.mode in constants.SymmetricMode.block_size_iv_modes() + and self.iv_size != AES_BLOCK_SIZE ): raise ValueError( - f"AES {self.mode.value} IV must be exactly {AES.block_size} bytes" + f"AES {self.mode.value} IV must be exactly {AES_BLOCK_SIZE} bytes" ) - if self.mode not in { - constants.SymmetricMode.CTR, - constants.SymmetricMode.CBC, - constants.SymmetricMode.GCM, - constants.SymmetricMode.CFB, - }: + if self.mode not in constants.SymmetricMode.members(): raise ValueError(f"Unsupported mode: {self.mode}") @@ -98,7 +105,7 @@ def __init__( @staticmethod def get_block_size() -> int: - return AES.block_size + return AES_BLOCK_SIZE def _get_iv( self, encryption_metadata: base.EncryptionMetadata @@ -109,86 +116,107 @@ def _get_iv( if iv is None: raise ValueError("AES IV is required when IV support is enabled") if ( - self.config.mode - in {constants.SymmetricMode.CBC, constants.SymmetricMode.CTR} - and len(iv) != AES.block_size + self.config.mode in constants.SymmetricMode.block_size_iv_modes() + and len(iv) != AES_BLOCK_SIZE ): raise ValueError( - f"AES {self.config.mode.value} IV must be exactly {AES.block_size} bytes" + f"AES {self.config.mode.value} IV must be exactly {AES_BLOCK_SIZE} bytes" ) return iv - def _create_ctx(self, iv: typing.Optional[bytes]) -> AESStandardModeContext: + def _create_cipher( + self, iv: typing.Optional[bytes], tag: typing.Optional[bytes] = None + ) -> Cipher[AESMode]: + if iv is None: + raise ValueError("AES IV is required") + if self.config.mode == constants.SymmetricMode.CTR: - if iv is None: - return AES.new(self.config.key, AES.MODE_CTR) - # Size of the counter block must match block size. - counter = Counter.new( - self.get_block_size() * 8, - initial_value=int.from_bytes(iv, byteorder="big"), - ) - return AES.new(self.config.key, AES.MODE_CTR, counter=counter) - if self.config.mode == constants.SymmetricMode.CBC: - return AES.new(self.config.key, AES.MODE_CBC, iv) - return AES.new(self.config.key, AES.MODE_CFB, iv) + mode: AESMode = modes.CTR(iv) + elif self.config.mode == constants.SymmetricMode.CBC: + mode = modes.CBC(iv) + elif self.config.mode == constants.SymmetricMode.CFB: + # The legacy backend's CFB default uses an 8-bit segment size. + mode = legacy_modes.CFB8(iv) + else: + mode = modes.GCM(iv, tag) + return Cipher(algorithms.AES(self.config.key), mode) + + def _get_aad(self, encryption_metadata: base.EncryptionMetadata) -> bytes: + if not self.config.enable_aad: + return b"" + aad = encryption_metadata.aad + if aad is None: + raise ValueError("AES AAD is required when AAD support is enabled") + return aad - def _create_gcm_ctx(self, iv: typing.Optional[bytes]) -> "GcmMode": - if iv is None: - return AES.new(self.config.key, AES.MODE_GCM) - return AES.new(self.config.key, AES.MODE_GCM, nonce=iv) - - def _init_gcm_ctx(self, encryption_metadata: base.EncryptionMetadata) -> "GcmMode": - cipher_ctx = self._create_gcm_ctx(self._get_iv(encryption_metadata)) - if self.config.enable_aad: - aad = encryption_metadata.aad - if aad is None: - raise ValueError("AES AAD is required when AAD support is enabled") - cipher_ctx.update(aad) - return cipher_ctx - - def init_ctx(self, encryption_metadata: base.EncryptionMetadata) -> AESModeContext: - if self.config.mode == constants.SymmetricMode.GCM: - return self._init_gcm_ctx(encryption_metadata) - return self._create_ctx(self._get_iv(encryption_metadata)) + @staticmethod + def _pad_pkcs7(plaintext_bytes: bytes) -> bytes: + padder = padding.PKCS7(AES_BLOCK_SIZE * 8).padder() + return padder.update(plaintext_bytes) + padder.finalize() + + @staticmethod + def _unpad_pkcs7(plaintext_bytes: bytes) -> bytes: + unpadder = padding.PKCS7(AES_BLOCK_SIZE * 8).unpadder() + try: + return unpadder.update(plaintext_bytes) + unpadder.finalize() + except ValueError as error: + raise ValueError("Padding is incorrect.") from error def _encrypt( self, plaintext_bytes: bytes, encryption_metadata: base.EncryptionMetadata ) -> bytes: if self.config.mode == constants.SymmetricMode.GCM: - cipher_ctx = self._init_gcm_ctx(encryption_metadata) - ciphertext_bytes, tag = cipher_ctx.encrypt_and_digest(plaintext_bytes) - encryption_metadata.tag = tag + cipher = self._create_cipher(self._get_iv(encryption_metadata)) + gcm_encryptor = typing.cast("AEADEncryptionContext", cipher.encryptor()) + gcm_encryptor.authenticate_additional_data( + self._get_aad(encryption_metadata) + ) + ciphertext_bytes: bytes = ( + gcm_encryptor.update(plaintext_bytes) + gcm_encryptor.finalize() + ) + encryption_metadata.tag = gcm_encryptor.tag return ciphertext_bytes if ( self.config.mode == constants.SymmetricMode.CBC and self.config.padding == constants.SymmetricPadding.PKCS7 ): - plaintext_bytes = pad(plaintext_bytes, AES.block_size, style="pkcs7") + plaintext_bytes = self._pad_pkcs7(plaintext_bytes) - standard_ctx = self.init_ctx(encryption_metadata) - return standard_ctx.encrypt(plaintext_bytes) + cipher = self._create_cipher(self._get_iv(encryption_metadata)) + standard_encryptor = cipher.encryptor() + return standard_encryptor.update(plaintext_bytes) + standard_encryptor.finalize() def _decrypt( self, ciphertext_bytes: bytes, encryption_metadata: base.EncryptionMetadata ) -> bytes: if self.config.mode == constants.SymmetricMode.CBC and ( - not ciphertext_bytes or len(ciphertext_bytes) % AES.block_size + not ciphertext_bytes or len(ciphertext_bytes) % AES_BLOCK_SIZE ): raise ValueError("AES CBC ciphertext must be non-empty and block-aligned") if self.config.mode == constants.SymmetricMode.GCM: - cipher_ctx = self._init_gcm_ctx(encryption_metadata) tag = encryption_metadata.tag if tag is None: raise ValueError("AES GCM authentication tag is required") - return cipher_ctx.decrypt_and_verify(ciphertext_bytes, tag) - - standard_ctx = self.init_ctx(encryption_metadata) - decrypted_bytes: bytes = standard_ctx.decrypt(ciphertext_bytes) + cipher = self._create_cipher(self._get_iv(encryption_metadata), tag=tag) + gcm_decryptor = typing.cast("AEADDecryptionContext", cipher.decryptor()) + gcm_decryptor.authenticate_additional_data( + self._get_aad(encryption_metadata) + ) + try: + return gcm_decryptor.update(ciphertext_bytes) + gcm_decryptor.finalize() + except exceptions.InvalidTag as error: + raise ValueError("MAC check failed") from error + + cipher = self._create_cipher(self._get_iv(encryption_metadata)) + standard_decryptor = cipher.decryptor() + decrypted_bytes: bytes = ( + standard_decryptor.update(ciphertext_bytes) + standard_decryptor.finalize() + ) if ( self.config.mode == constants.SymmetricMode.CBC and self.config.padding == constants.SymmetricPadding.PKCS7 ): - return unpad(decrypted_bytes, AES.block_size, style="pkcs7") + return self._unpad_pkcs7(decrypted_bytes) return decrypted_bytes diff --git a/bkcrypto/symmetric/ciphers/base.py b/bkcrypto/symmetric/ciphers/base.py index 0bb344f..307046b 100644 --- a/bkcrypto/symmetric/ciphers/base.py +++ b/bkcrypto/symmetric/ciphers/base.py @@ -20,7 +20,6 @@ from dataclasses import dataclass, field from bkcrypto import constants, types -from Cryptodome.Util.Padding import pad, unpad from dacite import from_dict from typing_extensions import TypeAlias @@ -28,6 +27,26 @@ from ..options import SymmetricOptions SymmetricConfigT = typing.TypeVar("SymmetricConfigT", bound="BaseSymmetricRuntimeConfig") +ISO7816_MARKER = 0x80 + + +def _pad_iso7816(data: bytes, block_size: int) -> bytes: + padding_size: int = block_size - len(data) % block_size + return data + b"\x80" + b"\x00" * (padding_size - 1) + + +def _unpad_iso7816(data: bytes, block_size: int) -> bytes: + if not data or len(data) % block_size: + raise ValueError("Padding is incorrect.") + + marker_index: int = len(data) - 1 + while marker_index >= 0 and data[marker_index] == 0: + marker_index -= 1 + if marker_index < 0 or data[marker_index] != ISO7816_MARKER: + raise ValueError("Padding is incorrect.") + if len(data) - marker_index > block_size: + raise ValueError("Padding is incorrect.") + return data[:marker_index] @dataclass @@ -51,7 +70,7 @@ def __post_init__(self) -> None: self.iv_size = len(self.iv) # 非 GCM 模式下 aad 默认关闭 - if self.mode not in {constants.SymmetricMode.GCM}: + if self.mode != constants.SymmetricMode.GCM: self.enable_aad = False if self.aad and self.enable_aad: @@ -173,10 +192,9 @@ def combine_encryption_metadata_with_bytes( combination_bytes += encryption_metadata.iv if encryption_metadata.tag: # padded_tag_size >= 2 * length(tag),填充后长度固定为 padded_tag_size - combination_bytes += pad( + combination_bytes += _pad_iso7816( encryption_metadata.tag, block_size=self.config.padded_tag_size, - style="iso7816", ) if encryption_metadata.aad: combination_bytes += encryption_metadata.aad @@ -239,11 +257,10 @@ def extract_encryption_metadata_from_bytes( pointer += self.config.iv_size # 只有 GCM 模式支持 tag - if self.config.mode in {constants.SymmetricMode.GCM}: - tag_or_none = unpad( + if self.config.mode == constants.SymmetricMode.GCM: + tag_or_none = _unpad_iso7816( ciphertext_bytes[pointer : pointer + self.config.padded_tag_size], self.config.padded_tag_size, - style="iso7816", ) pointer += self.config.padded_tag_size @@ -273,7 +290,7 @@ def extract_encryption_metadata_from_string_sep( ) iv_or_none = self.config.convertor.from_string(iv_str) # 只有 GCM 模式支持 tag - if self.config.mode in {constants.SymmetricMode.GCM}: + if self.config.mode == constants.SymmetricMode.GCM: tag_str, ciphertext = ciphertext.split( self.config.metadata_combination_separator, 1 ) diff --git a/bkcrypto/symmetric/ciphers/sm4.py b/bkcrypto/symmetric/ciphers/sm4.py index 3d9c81f..d1019ea 100644 --- a/bkcrypto/symmetric/ciphers/sm4.py +++ b/bkcrypto/symmetric/ciphers/sm4.py @@ -50,12 +50,7 @@ def __post_init__(self) -> None: f"Optional key sizes are {SM4_KEY_SIZES}, but got {self.key_size}" ) - if self.mode not in { - constants.SymmetricMode.CTR, - constants.SymmetricMode.CBC, - constants.SymmetricMode.GCM, - constants.SymmetricMode.CFB, - }: + if self.mode not in constants.SymmetricMode.members(): raise ValueError(f"Unsupported mode: {self.mode}") diff --git a/bkcrypto/types.py b/bkcrypto/types.py index d9c10a2..291fbda 100644 --- a/bkcrypto/types.py +++ b/bkcrypto/types.py @@ -13,52 +13,14 @@ specific language governing permissions and limitations under the License. """ -import typing - -from Cryptodome.Cipher.PKCS1_OAEP import PKCS1OAEP_Cipher -from Cryptodome.Cipher.PKCS1_v1_5 import PKCS115_Cipher -from Cryptodome.PublicKey.RSA import RsaKey -from Cryptodome.Signature.pkcs1_15 import PKCS115_SigScheme -from Cryptodome.Signature.pss import PSS_SigScheme from typing_extensions import TypeAlias - -class HashObject(typing.Protocol): - """Describe a hash object consumed by PyCryptodome RSA helpers.""" - - def digest(self) -> bytes: ... - - def update(self, data: bytes) -> None: ... - - -@typing.runtime_checkable -class HashModule(typing.Protocol): - """Describe a PyCryptodome hash module used by RSA operations.""" - - digest_size: int - - def new(self, data: typing.Optional[bytes] = None) -> HashObject: ... - - KeyString: TypeAlias = str -HashSource: TypeAlias = typing.Union[HashObject, HashModule] - -# from pss.MaskFunction -MaskFunction: TypeAlias = typing.Callable[[bytes, int, HashSource], bytes] - PrivateKeyString: TypeAlias = KeyString PublicKeyString: TypeAlias = KeyString -RSACipher: TypeAlias = typing.Union[PKCS1OAEP_Cipher, PKCS115_Cipher] - -RSASigScheme: TypeAlias = typing.Union[PSS_SigScheme, PKCS115_SigScheme] - -RSACipherMaker: TypeAlias = typing.Callable[[RsaKey], RSACipher] - -RSASigSchemeMaker: TypeAlias = typing.Callable[[RsaKey], RSASigScheme] - SymmetricKey: TypeAlias = bytes SymmetricIv: TypeAlias = bytes diff --git a/docs/usage.md b/docs/usage.md index 48b1b64..8bb77bc 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -32,8 +32,8 @@ | 参数 | 类型 | 描述 | |-----------------------------|----------------------------|-----------------------------------------| | padding | constants.RSACipherPadding | 加解密填充方案,默认为 `PKCS1_v1_5` | -| oaep_hash | Hash 模块 | OAEP 哈希算法,默认为 `SHA1` | -| mgf1_hash | Hash 模块 | MGF1 哈希算法,默认为 `SHA1` | +| oaep_hash | hashes.HashAlgorithm | OAEP 哈希算法,默认为 `SHA1` | +| mgf1_hash | hashes.HashAlgorithm | MGF1 哈希算法,默认为 `SHA1` | | oaep_label | typing.Optional[bytes] | OAEP label,默认为空 | | enable_segmented_encryption | bool | 文本接口是否按 RSA 最大明文长度分段,默认为 `True` | | sig_scheme | constants.RSASigScheme | 签名方案,默认为 `PKCS1_v1_5` | @@ -108,15 +108,14 @@ _baseSM4SymmetricConfig_ 类继承自 _BaseSymmetricConfig_ 类,不包含额 与 BK-KMS SDK 的 RSA-OAEP 配置保持一致时,需要同时为 OAEP 和 MGF1 指定 SHA-256,并使用空 label: ```python -from Cryptodome.Hash import SHA256 - from bkcrypto import constants from bkcrypto.asymmetric.ciphers import RSAAsymmetricCipher +from cryptography.hazmat.primitives import hashes rsa_cipher = RSAAsymmetricCipher( padding=constants.RSACipherPadding.PKCS1_OAEP, - oaep_hash=SHA256, - mgf1_hash=SHA256, + oaep_hash=hashes.SHA256(), + mgf1_hash=hashes.SHA256(), oaep_label=None, enable_segmented_encryption=False, ) diff --git a/pyproject.toml b/pyproject.toml index f30a5a3..8b7bf62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "hatchling.build" [project] name = "bk-crypto-python-sdk" -version = "4.0.0" -description = "bk-crypto-python-sdk is a lightweight cryptography toolkit for Python applications based on Cryptodome / tongsuopy and other encryption libraries." +version = "4.1.0" +description = "bk-crypto-python-sdk is a lightweight cryptography toolkit for Python applications based on cryptography / tongsuopy and other encryption libraries." authors = [ { name = "TencentBlueKing", email = "contactus_bk@tencent.com" }, ] @@ -13,7 +13,7 @@ readme = "readme.md" requires-python = ">=3.9,<3.15" dependencies = [ "dacite>=1.8.1,<2.0.0", - "pycryptodomex>=3.20.0,<4.0.0", + "cryptography>=44.0.0,<51.0.0", "typing-extensions>=4.0", ] diff --git a/readme.md b/readme.md index 57e31d8..fc0174d 100644 --- a/readme.md +++ b/readme.md @@ -15,12 +15,12 @@ ## Overview -️🔧 BlueKing crypto-python-sdk 是一个基于 pyCryptodome / tongsuopy 等加密库的轻量级密码学工具包,为 Python 应用统一的加解密实现, +️🔧 BlueKing crypto-python-sdk 是一个基于 cryptography / tongsuopy 等加密库的轻量级密码学工具包,为 Python 应用统一的加解密实现, 便于项目在不同的加密方式之间进行无侵入切换 ## Features -* [Basic] 提供加密统一抽象层,对接 Cryptodome / tongsuopy 等加密库,提供统一的加解密实现 +* [Basic] 提供加密统一抽象层,对接 cryptography / tongsuopy 等加密库,提供统一的加解密实现 * [Basic] 支持国际主流密码学算法:AES、RSA * [Basic] 支持中国商用密码学算法:SM2、SM4 * [Basic] 非对称加密支持 CBC、CTR、GCM、CFB 作为块密码模式 @@ -61,7 +61,7 @@ asymmetric_cipher: BaseAsymmetricCipher = get_asymmetric_cipher( constants.AsymmetricCipherType.SM2.value: options.SM2AsymmetricOptions( private_key_string=None ), - constants.AsymmetricCipherType.RSA.value: options.SM2AsymmetricOptions( + constants.AsymmetricCipherType.RSA.value: options.RSAAsymmetricOptions( private_key_string=None ), }, diff --git a/readme_en.md b/readme_en.md index 66ce094..6e3c2c3 100644 --- a/readme_en.md +++ b/readme_en.md @@ -15,13 +15,13 @@ ## Overview -️🔧 BlueKing crypto-python-sdk is a lightweight cryptography toolkit based on encryption libraries such as pyCryptodome +️🔧 BlueKing crypto-python-sdk is a lightweight cryptography toolkit based on encryption libraries such as cryptography and tongsuopy, providing a unified encryption and decryption implementation for Python applications, making it easy for projects to switch between different encryption methods without intrusion. ## Features -* [Basic] Provides a unified encryption abstraction layer, docking with Cryptodome / tongsuopy and other encryption +* [Basic] Provides a unified encryption abstraction layer, docking with cryptography / tongsuopy and other encryption libraries, providing a unified encryption and decryption implementation * [Basic] Supports mainstream international cryptography algorithms: AES, RSA * [Basic] Supports Chinese commercial cryptography algorithms: SM2, SM4 @@ -64,7 +64,7 @@ asymmetric_cipher: BaseAsymmetricCipher = get_asymmetric_cipher( constants.AsymmetricCipherType.SM2.value: options.SM2AsymmetricOptions( private_key_string=None ), - constants.AsymmetricCipherType.RSA.value: options.SM2AsymmetricOptions( + constants.AsymmetricCipherType.RSA.value: options.RSAAsymmetricOptions( private_key_string=None ), }, diff --git a/release.md b/release.md index 351f03f..42023c4 100644 --- a/release.md +++ b/release.md @@ -1,5 +1,14 @@ **# 版本日志 +## 4.1.0 - 2026-08-27 + +### Improved + +* [ Improved ] Replace PyCryptodome with `cryptography` for RSA and AES + ([#41](https://github.com/TencentBlueKing/crypto-python-sdk/pull/41)) +* [ Improved ] Preserve RSA key formats, PEM and OpenSSH public-key loading, and RSA padding and signature behavior +* [ Improved ] Preserve AES-CBC, AES-CTR, AES-CFB, and AES-GCM behavior and BK-KMS ciphertext compatibility + ## 4.0.0 - 2026-08-27 ### Feature diff --git a/tests/asymmetric/test_rsa.py b/tests/asymmetric/test_rsa.py index 415f26f..1222330 100644 --- a/tests/asymmetric/test_rsa.py +++ b/tests/asymmetric/test_rsa.py @@ -3,19 +3,26 @@ import pytest from bkcrypto import constants from bkcrypto.asymmetric.ciphers import RSAAsymmetricCipher -from Cryptodome.Hash import SHA256 -from Cryptodome.IO import PEM -from Cryptodome.PublicKey import RSA +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa -class TestRSAKey: +class TestRSASerialization: @classmethod def test_generate_key_pair__uses_bk_kms_formats(cls) -> None: cipher = RSAAsymmetricCipher() + public_key = serialization.load_pem_public_key( + cipher.export_public_key().encode() + ) + private_key = serialization.load_pem_private_key( + cipher.export_private_key().encode(), password=None + ) - assert cipher.config.public_key is not None - assert cipher.config.public_key.size_in_bits() == 2048 - assert cipher.config.public_key.e == 65537 + assert isinstance(public_key, rsa.RSAPublicKey) + assert isinstance(private_key, rsa.RSAPrivateKey) + assert public_key.key_size == 2048 + assert public_key.public_numbers().e == 65537 + assert private_key.public_key().public_numbers() == public_key.public_numbers() assert cipher.export_public_key().startswith("-----BEGIN PUBLIC KEY-----") assert cipher.export_private_key().startswith("-----BEGIN RSA PRIVATE KEY-----") @@ -23,26 +30,53 @@ def test_generate_key_pair__uses_bk_kms_formats(cls) -> None: def test_load_private_key__accepts_pkcs1_and_pkcs8( cls, rsa_private_key: str ) -> None: - private_key = RSA.import_key(rsa_private_key) - pkcs8_private_key = PEM.encode( - private_key.export_key(format="DER", pkcs=8), "PRIVATE KEY" + private_key = serialization.load_pem_private_key( + rsa_private_key.encode(), password=None ) + assert isinstance(private_key, rsa.RSAPrivateKey) + pkcs8_private_key: str = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ).decode() pkcs1_cipher = RSAAsymmetricCipher(private_key_string=rsa_private_key) pkcs8_cipher = RSAAsymmetricCipher(private_key_string=pkcs8_private_key) - assert pkcs1_cipher.config.private_key is not None - assert pkcs8_cipher.config.private_key is not None - assert pkcs1_cipher.config.private_key.has_private() - assert pkcs8_cipher.config.private_key.has_private() + + for cipher in (pkcs1_cipher, pkcs8_cipher): + assert cipher.decrypt(cipher.encrypt("key format")) == "key format" + assert cipher.export_private_key().startswith( + "-----BEGIN RSA PRIVATE KEY-----" + ) @classmethod def test_load_public_key__keeps_private_key_input_compatible( cls, rsa_private_key: str ) -> None: - cipher = RSAAsymmetricCipher(public_key_string=rsa_private_key) + private_cipher = RSAAsymmetricCipher(private_key_string=rsa_private_key) + public_cipher = RSAAsymmetricCipher(public_key_string=rsa_private_key) + ciphertext = public_cipher.encrypt("private PEM as public input") - assert cipher.config.public_key is not None - assert not cipher.config.public_key.has_private() + assert private_cipher.decrypt(ciphertext) == "private PEM as public input" + assert public_cipher.export_public_key() == private_cipher.export_public_key() + + @classmethod + def test_load_public_key__accepts_openssh_format(cls, rsa_private_key: str) -> None: + private_cipher = RSAAsymmetricCipher(private_key_string=rsa_private_key) + public_key = serialization.load_pem_public_key( + private_cipher.export_public_key().encode() + ) + assert isinstance(public_key, rsa.RSAPublicKey) + openssh_public_key: str = public_key.public_bytes( + encoding=serialization.Encoding.OpenSSH, + format=serialization.PublicFormat.OpenSSH, + ).decode() + + public_cipher = RSAAsymmetricCipher(public_key_string=openssh_public_key) + ciphertext = public_cipher.encrypt("OpenSSH public key") + + assert private_cipher.decrypt(ciphertext) == "OpenSSH public key" + assert public_cipher.export_public_key() == private_cipher.export_public_key() @classmethod @pytest.mark.parametrize( @@ -65,17 +99,22 @@ def test_block_list__accepts_legacy_lst_keyword(cls) -> None: assert list(blocks) == [b"ab", b"c"] @classmethod - def test_encrypt__requires_public_key(cls, rsa_private_key: str) -> None: - cipher = RSAAsymmetricCipher(private_key_string=rsa_private_key) - cipher.config.public_key = None + def test_decrypt__requires_private_key(cls, rsa_private_key: str) -> None: + private_cipher = RSAAsymmetricCipher(private_key_string=rsa_private_key) + cipher = RSAAsymmetricCipher( + public_key_string=private_cipher.export_public_key() + ) + ciphertext = cipher.encrypt("message") - with pytest.raises(ValueError, match="call encrypt"): - cipher.encrypt("message") + with pytest.raises(ValueError, match="call decrypt"): + cipher.decrypt(ciphertext) @classmethod def test_sign__requires_private_key(cls, rsa_private_key: str) -> None: - cipher = RSAAsymmetricCipher(private_key_string=rsa_private_key) - cipher.config.private_key = None + private_cipher = RSAAsymmetricCipher(private_key_string=rsa_private_key) + cipher = RSAAsymmetricCipher( + public_key_string=private_cipher.export_public_key() + ) with pytest.raises(ValueError, match="call sign"): cipher.sign("message") @@ -134,8 +173,8 @@ def make_cipher(cls, rsa_private_key: str, **options) -> RSAAsymmetricCipher: return RSAAsymmetricCipher( private_key_string=rsa_private_key, padding=constants.RSACipherPadding.PKCS1_OAEP, - oaep_hash=SHA256, - mgf1_hash=SHA256, + oaep_hash=hashes.SHA256(), + mgf1_hash=hashes.SHA256(), **options, ) diff --git a/tests/test_bk_kms_interop.py b/tests/test_bk_kms_interop.py index d80fd3e..48c084c 100644 --- a/tests/test_bk_kms_interop.py +++ b/tests/test_bk_kms_interop.py @@ -2,7 +2,7 @@ from bkcrypto import constants from bkcrypto.asymmetric.ciphers import RSAAsymmetricCipher from bkcrypto.symmetric.ciphers import AESSymmetricCipher -from Cryptodome.Hash import SHA256 +from cryptography.hazmat.primitives import hashes from tests.fixtures.bk_kms_vectors import ( AES_KEY, GO_AES_CBC_CIPHERTEXT, @@ -22,8 +22,8 @@ def make_cipher(cls) -> RSAAsymmetricCipher: return RSAAsymmetricCipher( private_key_string=RSA_PRIVATE_KEY, padding=constants.RSACipherPadding.PKCS1_OAEP, - oaep_hash=SHA256, - mgf1_hash=SHA256, + oaep_hash=hashes.SHA256(), + mgf1_hash=hashes.SHA256(), enable_segmented_encryption=False, ) diff --git a/tests/test_constants.py b/tests/test_constants.py new file mode 100644 index 0000000..6e87393 --- /dev/null +++ b/tests/test_constants.py @@ -0,0 +1,21 @@ +from bkcrypto import constants + + +class TestSymmetricMode: + @classmethod + def test_members__returns_all_modes_as_frozenset(cls) -> None: + members = constants.SymmetricMode.members() + + assert members == frozenset(constants.SymmetricMode) + + @classmethod + def test_block_size_iv_modes__returns_expected_modes(cls) -> None: + modes = constants.SymmetricMode.block_size_iv_modes() + + assert modes == frozenset( + { + constants.SymmetricMode.CBC, + constants.SymmetricMode.CFB, + constants.SymmetricMode.CTR, + } + ) diff --git a/tests/test_factories.py b/tests/test_factories.py index 04d6991..710c427 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -7,7 +7,7 @@ from bkcrypto.contrib.django.init_configs import SymmetricCipherInitConfig from bkcrypto.contrib.django.settings import DEFAULTS, CryptoSettings from bkcrypto.symmetric.options import AESSymmetricOptions -from Cryptodome.Hash import SHA256 +from cryptography.hazmat.primitives import hashes class TestBasicFactory: @@ -20,7 +20,7 @@ def test_get_symmetric_cipher__rejects_non_cipher_class(cls) -> None: get_symmetric_cipher(symmetric_cipher_classes=invalid_cipher_classes) @classmethod - def test_get_asymmetric_cipher__supports_hash_module_options( + def test_get_asymmetric_cipher__supports_hash_algorithm_options( cls, rsa_private_key: str ) -> None: cipher = get_asymmetric_cipher( @@ -28,8 +28,8 @@ def test_get_asymmetric_cipher__supports_hash_module_options( constants.AsymmetricCipherType.RSA.value: RSAAsymmetricOptions( private_key_string=rsa_private_key, padding=constants.RSACipherPadding.PKCS1_OAEP, - oaep_hash=SHA256, - mgf1_hash=SHA256, + oaep_hash=hashes.SHA256(), + mgf1_hash=hashes.SHA256(), ) } ) diff --git a/uv.lock b/uv.lock index 08e1581..8b4b42d 100644 --- a/uv.lock +++ b/uv.lock @@ -51,11 +51,12 @@ wheels = [ [[package]] name = "bk-crypto-python-sdk" -version = "4.0.0" +version = "4.1.0" source = { editable = "." } dependencies = [ + { name = "cryptography", version = "47.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.9'" }, + { name = "cryptography", version = "50.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version > '3.9'" }, { name = "dacite" }, - { name = "pycryptodomex" }, { name = "typing-extensions" }, ] @@ -85,8 +86,8 @@ dev = [ [package.metadata] requires-dist = [ + { name = "cryptography", specifier = ">=44.0.0,<51.0.0" }, { name = "dacite", specifier = ">=1.8.1,<2.0.0" }, - { name = "pycryptodomex", specifier = ">=3.20.0,<4.0.0" }, { name = "tongsuopy-crayon", marker = "extra == 'gm'", specifier = ">=1.0.2b5,<2.0.0" }, { name = "typing-extensions", specifier = ">=4.0" }, ] @@ -732,6 +733,132 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/d9/e70c286c979378f061d8266e279b686ab0b0b688e1fe0af864684f23a77d/coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84", size = 214332, upload-time = "2026-08-06T13:50:22.192Z" }, ] +[[package]] +name = "cryptography" +version = "47.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version <= '3.9'", +] +dependencies = [ + { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version <= '3.9' and platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version <= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/98/40dfe932134bdcae4f6ab5927c87488754bf9eb79297d7e0070b78dd58e9/cryptography-47.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:160ad728f128972d362e714054f6ba0067cab7fb350c5202a9ae8ae4ce3ef1a0", size = 7912214, upload-time = "2026-04-24T19:53:03.864Z" }, + { url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" }, + { url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5e/13ed0cdd0eb88ba159d6dd5ebfece8cb901dbcf1ae5ac4072e28b55d3153/cryptography-47.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92", size = 5252906, upload-time = "2026-04-24T19:53:13.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" }, + { url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/86/53/5395d944dfd48cb1f67917f533c609c34347185ef15eb4308024c876f274/cryptography-47.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f", size = 5207817, upload-time = "2026-04-24T19:53:22.498Z" }, + { url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" }, + { url = "https://files.pythonhosted.org/packages/54/ed/5f524db1fade9c013aa618e1c99c6ed05e8ffc9ceee6cda22fed22dda3f4/cryptography-47.0.0-cp311-abi3-win32.whl", hash = "sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203", size = 3258581, upload-time = "2026-04-24T19:53:31.058Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/1b901990b174786569029f67542b3edf72ac068b6c3c8683c17e6a2f5363/cryptography-47.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa", size = 3775309, upload-time = "2026-04-24T19:53:33.054Z" }, + { url = "https://files.pythonhosted.org/packages/14/88/7aa18ad9c11bc87689affa5ce4368d884b517502d75739d475fc6f4a03c7/cryptography-47.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:be12cb6a204f77ed968bcefe68086eb061695b540a3dd05edac507a3111b25f0", size = 7904299, upload-time = "2026-04-24T19:53:35.003Z" }, + { url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" }, + { url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/300327b0a47f6dc94dd8b71b57052aefe178bb51745073d73d80604f11ab/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829", size = 5238019, upload-time = "2026-04-24T19:53:44.577Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d7/0b3c71090a76e5c203164a47688b697635ece006dcd2499ab3a4dbd3f0bd/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736", size = 5194988, upload-time = "2026-04-24T19:53:52.962Z" }, + { url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" }, + { url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" }, + { url = "https://files.pythonhosted.org/packages/31/98/dc4ad376ac5f1a1a7d4a83f7b0c6f2bcad36b5d2d8f30aeb482d3a7d9582/cryptography-47.0.0-cp314-cp314t-win32.whl", hash = "sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63", size = 3237158, upload-time = "2026-04-24T19:54:02.606Z" }, + { url = "https://files.pythonhosted.org/packages/bc/da/97f62d18306b5133468bc3f8cc73a3111e8cdc8cf8d3e69474d6e5fd2d1b/cryptography-47.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b", size = 3758706, upload-time = "2026-04-24T19:54:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e0/34/a4fae8ae7c3bc227460c9ae43f56abf1b911da0ec29e0ebac53bb0a4b6b7/cryptography-47.0.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4", size = 7904072, upload-time = "2026-04-24T19:54:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" }, + { url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/2c5fbeea70adbbca2bbae865e1d605d6a4a7f8dbd9d33eaf69645087f06c/cryptography-47.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74", size = 5225777, upload-time = "2026-04-24T19:54:15.18Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" }, + { url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/a5/51/661cbee74f594c5d97ff82d34f10d5551c085ca4668645f4606ebd22bd5d/cryptography-47.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76", size = 5181411, upload-time = "2026-04-24T19:54:24.376Z" }, + { url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" }, + { url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/bd/0a9d3edbf5eadbac926d7b9b3cd0c4be584eeeae4a003d24d9eda4affbbd/cryptography-47.0.0-cp38-abi3-win32.whl", hash = "sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310", size = 3248487, upload-time = "2026-04-24T19:54:33.494Z" }, + { url = "https://files.pythonhosted.org/packages/60/80/5681af756d0da3a599b7bdb586fac5a1540f1bcefd2717a20e611ddade45/cryptography-47.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769", size = 3755737, upload-time = "2026-04-24T19:54:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a0/928c9ce0d120a40a81aa99e3ba383e87337b9ac9ef9f6db02e4d7822424d/cryptography-47.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f1207974a904e005f762869996cf620e9bf79ecb4622f148550bb48e0eb35a7", size = 3909893, upload-time = "2026-04-24T19:54:38.334Z" }, + { url = "https://files.pythonhosted.org/packages/81/75/d691e284750df5d9569f2b1ce4a00a71e1d79566da83b2b3e5549c84917f/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1a405c08857258c11016777e11c02bacbe7ef596faf259305d282272a3a05cbe", size = 4587867, upload-time = "2026-04-24T19:54:40.619Z" }, + { url = "https://files.pythonhosted.org/packages/07/d6/1b90f1a4e453009730b4545286f0b39bb348d805c11181fc31544e4f9a65/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:20fdbe3e38fb67c385d233c89371fa27f9909f6ebca1cecc20c13518dae65475", size = 4627192, upload-time = "2026-04-24T19:54:42.849Z" }, + { url = "https://files.pythonhosted.org/packages/dc/53/cb358a80e9e359529f496870dd08c102aa8a4b5b9f9064f00f0d6ed5b527/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f7db373287273d8af1414cf95dc4118b13ffdc62be521997b0f2b270771fef50", size = 4587486, upload-time = "2026-04-24T19:54:44.908Z" }, + { url = "https://files.pythonhosted.org/packages/8b/57/aaa3d53876467a226f9a7a82fd14dd48058ad2de1948493442dfa16e2ffd/cryptography-47.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9fe6b7c64926c765f9dff301f9c1b867febcda5768868ca084e18589113732ab", size = 4626327, upload-time = "2026-04-24T19:54:47.813Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9c/51f28c3550276bcf35660703ba0ab829a90b88be8cd98a71ef23c2413913/cryptography-47.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cffbba3392df0fa8629bb7f43454ee2925059ee158e23c54620b9063912b86c8", size = 3698916, upload-time = "2026-04-24T19:54:49.782Z" }, +] + +[[package]] +name = "cryptography" +version = "50.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version == '3.10.*'", + "python_full_version > '3.9' and python_full_version < '3.10'", +] +dependencies = [ + { name = "cffi", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version > '3.9' and python_full_version < '3.10' and platform_python_implementation != 'PyPy'" }, + { name = "cffi", version = "2.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' and platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version > '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/ad/5d6702db60b1e40b41ef513b6967ff5848f307d50f8449baf1634f5908f1/cryptography-50.0.1.tar.gz", hash = "sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20", size = 880381, upload-time = "2026-08-25T19:45:45.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/19/797e2aaac9df6a66f1550f49979dc1b1e39ecd2077501c30efa81e8d5d67/cryptography-50.0.1-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986", size = 4010153, upload-time = "2026-08-25T19:44:03.155Z" }, + { url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" }, + { url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" }, + { url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" }, + { url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" }, + { url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" }, + { url = "https://files.pythonhosted.org/packages/42/8b/cb12b1b60c91b074ca6bf0fdd59aa8f10d8bc5f73af8faece86ef0421b37/cryptography-50.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648", size = 3842826, upload-time = "2026-08-25T19:44:28.784Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f0/424cb557d99aa86ac55da5e2add02e2882e44047b6264f93ade1b975a993/cryptography-50.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f", size = 3973525, upload-time = "2026-08-25T19:44:30.7Z" }, + { url = "https://files.pythonhosted.org/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" }, + { url = "https://files.pythonhosted.org/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" }, + { url = "https://files.pythonhosted.org/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" }, + { url = "https://files.pythonhosted.org/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" }, + { url = "https://files.pythonhosted.org/packages/4d/56/bc4f2b209e766c93372cfcd59b781a0b2b59700f62a969580415b699c2b2/cryptography-50.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf", size = 3825806, upload-time = "2026-08-25T19:44:56.209Z" }, + { url = "https://files.pythonhosted.org/packages/84/a9/ee16a903f13755e914d1eecc482fe64d1f10761c3960e5d8fa6837377aff/cryptography-50.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0", size = 4035307, upload-time = "2026-08-25T19:44:58.305Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" }, + { url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" }, + { url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/87ef49ffe383ef4e147d27b7bf2088fb0b54ea409dd87b5a89442e5828a5/cryptography-50.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2", size = 3875429, upload-time = "2026-08-25T19:45:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/c7/27/8d207af749c453ee17ea087340b3f2b4adef75aadd1d277b1b129bdda84e/cryptography-50.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94", size = 3974350, upload-time = "2026-08-25T19:45:26.551Z" }, + { url = "https://files.pythonhosted.org/packages/14/9a/6d3a4d7852e22d657438b7bf51f66102c7d71c0e1fafeec652281d0403e5/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f", size = 4698675, upload-time = "2026-08-25T19:45:28.658Z" }, + { url = "https://files.pythonhosted.org/packages/73/35/5c3717edf9e68a0550ce04e28eab493fe545eccd81742af03f6a75fe260b/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671", size = 4707410, upload-time = "2026-08-25T19:45:30.816Z" }, + { url = "https://files.pythonhosted.org/packages/1d/e0/e786934472e3ac4ecdecc7b129a0ca1a2a40dffdafcf2c3ea9d4397f8def/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e", size = 4698378, upload-time = "2026-08-25T19:45:33.043Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/5b3f53a0b74d122f023476ede40ba5d3e70d5cf475f73b899740d26a4fb2/cryptography-50.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6", size = 4706889, upload-time = "2026-08-25T19:45:35.086Z" }, + { url = "https://files.pythonhosted.org/packages/71/44/711e61f7d014be825ef79b285b047292d1bf893732ac1bc030a351fb517f/cryptography-50.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b", size = 3824006, upload-time = "2026-08-25T19:45:37.281Z" }, +] + [[package]] name = "dacite" version = "1.9.2" @@ -1342,46 +1469,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] -[[package]] -name = "pycryptodomex" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/85/e24bf90972a30b0fcd16c73009add1d7d7cd9140c2498a68252028899e41/pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da", size = 4922157, upload-time = "2025-05-17T17:23:41.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/00/10edb04777069a42490a38c137099d4b17ba6e36a4e6e28bdc7470e9e853/pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886", size = 2498764, upload-time = "2025-05-17T17:22:21.453Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3f/2872a9c2d3a27eac094f9ceaa5a8a483b774ae69018040ea3240d5b11154/pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d", size = 1643012, upload-time = "2025-05-17T17:22:23.702Z" }, - { url = "https://files.pythonhosted.org/packages/70/af/774c2e2b4f6570fbf6a4972161adbb183aeeaa1863bde31e8706f123bf92/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa", size = 2187643, upload-time = "2025-05-17T17:22:26.37Z" }, - { url = "https://files.pythonhosted.org/packages/de/a3/71065b24cb889d537954cedc3ae5466af00a2cabcff8e29b73be047e9a19/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8", size = 2273762, upload-time = "2025-05-17T17:22:28.313Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0b/ff6f43b7fbef4d302c8b981fe58467b8871902cdc3eb28896b52421422cc/pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5", size = 2313012, upload-time = "2025-05-17T17:22:30.57Z" }, - { url = "https://files.pythonhosted.org/packages/02/de/9d4772c0506ab6da10b41159493657105d3f8bb5c53615d19452afc6b315/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314", size = 2186856, upload-time = "2025-05-17T17:22:32.819Z" }, - { url = "https://files.pythonhosted.org/packages/28/ad/8b30efcd6341707a234e5eba5493700a17852ca1ac7a75daa7945fcf6427/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006", size = 2347523, upload-time = "2025-05-17T17:22:35.386Z" }, - { url = "https://files.pythonhosted.org/packages/0f/02/16868e9f655b7670dbb0ac4f2844145cbc42251f916fc35c414ad2359849/pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462", size = 2272825, upload-time = "2025-05-17T17:22:37.632Z" }, - { url = "https://files.pythonhosted.org/packages/ca/18/4ca89ac737230b52ac8ffaca42f9c6f1fd07c81a6cd821e91af79db60632/pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328", size = 1772078, upload-time = "2025-05-17T17:22:40Z" }, - { url = "https://files.pythonhosted.org/packages/73/34/13e01c322db027682e00986873eca803f11c56ade9ba5bbf3225841ea2d4/pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708", size = 1803656, upload-time = "2025-05-17T17:22:42.139Z" }, - { url = "https://files.pythonhosted.org/packages/54/68/9504c8796b1805d58f4425002bcca20f12880e6fa4dc2fc9a668705c7a08/pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4", size = 1707172, upload-time = "2025-05-17T17:22:44.704Z" }, - { url = "https://files.pythonhosted.org/packages/dd/9c/1a8f35daa39784ed8adf93a694e7e5dc15c23c741bbda06e1d45f8979e9e/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6", size = 2499240, upload-time = "2025-05-17T17:22:46.953Z" }, - { url = "https://files.pythonhosted.org/packages/7a/62/f5221a191a97157d240cf6643747558759126c76ee92f29a3f4aee3197a5/pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545", size = 1644042, upload-time = "2025-05-17T17:22:49.098Z" }, - { url = "https://files.pythonhosted.org/packages/8c/fd/5a054543c8988d4ed7b612721d7e78a4b9bf36bc3c5ad45ef45c22d0060e/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587", size = 2186227, upload-time = "2025-05-17T17:22:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a9/8862616a85cf450d2822dbd4fff1fcaba90877907a6ff5bc2672cafe42f8/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c", size = 2272578, upload-time = "2025-05-17T17:22:53.676Z" }, - { url = "https://files.pythonhosted.org/packages/46/9f/bda9c49a7c1842820de674ab36c79f4fbeeee03f8ff0e4f3546c3889076b/pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c", size = 2312166, upload-time = "2025-05-17T17:22:56.585Z" }, - { url = "https://files.pythonhosted.org/packages/03/cc/870b9bf8ca92866ca0186534801cf8d20554ad2a76ca959538041b7a7cf4/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003", size = 2185467, upload-time = "2025-05-17T17:22:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/96/e3/ce9348236d8e669fea5dd82a90e86be48b9c341210f44e25443162aba187/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744", size = 2346104, upload-time = "2025-05-17T17:23:02.112Z" }, - { url = "https://files.pythonhosted.org/packages/a5/e9/e869bcee87beb89040263c416a8a50204f7f7a83ac11897646c9e71e0daf/pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd", size = 2271038, upload-time = "2025-05-17T17:23:04.872Z" }, - { url = "https://files.pythonhosted.org/packages/8d/67/09ee8500dd22614af5fbaa51a4aee6e342b5fa8aecf0a6cb9cbf52fa6d45/pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c", size = 1771969, upload-time = "2025-05-17T17:23:07.115Z" }, - { url = "https://files.pythonhosted.org/packages/69/96/11f36f71a865dd6df03716d33bd07a67e9d20f6b8d39820470b766af323c/pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9", size = 1803124, upload-time = "2025-05-17T17:23:09.267Z" }, - { url = "https://files.pythonhosted.org/packages/f9/93/45c1cdcbeb182ccd2e144c693eaa097763b08b38cded279f0053ed53c553/pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51", size = 1707161, upload-time = "2025-05-17T17:23:11.414Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b8/3e76d948c3c4ac71335bbe75dac53e154b40b0f8f1f022dfa295257a0c96/pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5", size = 1627695, upload-time = "2025-05-17T17:23:17.38Z" }, - { url = "https://files.pythonhosted.org/packages/6a/cf/80f4297a4820dfdfd1c88cf6c4666a200f204b3488103d027b5edd9176ec/pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798", size = 1675772, upload-time = "2025-05-17T17:23:19.202Z" }, - { url = "https://files.pythonhosted.org/packages/d1/42/1e969ee0ad19fe3134b0e1b856c39bd0b70d47a4d0e81c2a8b05727394c9/pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f", size = 1668083, upload-time = "2025-05-17T17:23:21.867Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c3/1de4f7631fea8a992a44ba632aa40e0008764c0fb9bf2854b0acf78c2cf2/pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea", size = 1706056, upload-time = "2025-05-17T17:23:24.031Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5f/af7da8e6f1e42b52f44a24d08b8e4c726207434e2593732d39e7af5e7256/pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe", size = 1806478, upload-time = "2025-05-17T17:23:26.066Z" }, - { url = "https://files.pythonhosted.org/packages/e2/eb/022ae689a90f4101847d3f43c2319b3f7f5ed53ba6a49b2c7af7d72c2523/pycryptodomex-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7de1e40a41a5d7f1ac42b6569b10bcdded34339950945948529067d8426d2785", size = 1627595, upload-time = "2025-05-17T17:23:28.211Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5f/566de54abb78a0a7f4ca7730e8a1fd372509e257d15d9f0f076aa30e73a5/pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bffc92138d75664b6d543984db7893a628559b9e78658563b0395e2a5fb47ed9", size = 1675678, upload-time = "2025-05-17T17:23:30.36Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e4/8240294e46b1ceb027b432be861b641752486691f675b9f0a4b0495c1cb5/pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df027262368334552db2c0ce39706b3fb32022d1dce34673d0f9422df004b96a", size = 1667977, upload-time = "2025-05-17T17:23:32.922Z" }, - { url = "https://files.pythonhosted.org/packages/0e/ac/2b8eee86b73811e3d814e429f3aeebf84ca07a5c1912a0a33246bfc4675f/pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e79f1aaff5a3a374e92eb462fa9e598585452135012e2945f96874ca6eeb1ff", size = 1705980, upload-time = "2025-05-17T17:23:34.796Z" }, - { url = "https://files.pythonhosted.org/packages/37/be/2e75f36f368068d87656a04e07f998fd345a5ba7a3a56fa8c3f80484a506/pycryptodomex-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:27e13c80ac9a0a1d050ef0a7e0a18cc04c8850101ec891815b6c5a0375e8a245", size = 1806361, upload-time = "2025-05-17T17:23:37.331Z" }, -] - [[package]] name = "pygments" version = "2.21.0"