transcrypto 1.1.2__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transcrypto/aes.py +4 -3
- transcrypto/base.py +84 -30
- transcrypto/dsa.py +225 -48
- transcrypto/elgamal.py +237 -40
- transcrypto/modmath.py +487 -40
- transcrypto/rsa.py +220 -36
- transcrypto/sss.py +160 -23
- transcrypto/transcrypto.py +429 -191
- {transcrypto-1.1.2.dist-info → transcrypto-1.3.0.dist-info}/METADATA +732 -427
- transcrypto-1.3.0.dist-info/RECORD +15 -0
- transcrypto-1.1.2.dist-info/RECORD +0 -15
- {transcrypto-1.1.2.dist-info → transcrypto-1.3.0.dist-info}/WHEEL +0 -0
- {transcrypto-1.1.2.dist-info → transcrypto-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {transcrypto-1.1.2.dist-info → transcrypto-1.3.0.dist-info}/top_level.txt +0 -0
transcrypto/aes.py
CHANGED
|
@@ -47,7 +47,7 @@ assert _PASSWORD_ITERATIONS == (6075308 + 1) // 3, 'should never happen: constan
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
50
|
-
class AESKey(base.CryptoKey, base.
|
|
50
|
+
class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
51
51
|
"""Advanced Encryption Standard (AES) 256 bits key (32 bytes).
|
|
52
52
|
|
|
53
53
|
No measures are taken here to prevent timing attacks.
|
|
@@ -112,7 +112,7 @@ class AESKey(base.CryptoKey, base.SymmetricCrypto):
|
|
|
112
112
|
salt=_PASSWORD_SALT_256, iterations=_PASSWORD_ITERATIONS)
|
|
113
113
|
return cls(key256=kdf.derive(str_password.encode('utf-8')))
|
|
114
114
|
|
|
115
|
-
class ECBEncoderClass(base.
|
|
115
|
+
class ECBEncoderClass(base.Encryptor, base.Decryptor):
|
|
116
116
|
"""The simplest encryption possible (UNSAFE if misused): 128 bit block AES-ECB, 256 bit key.
|
|
117
117
|
|
|
118
118
|
Please DO **NOT** use this for regular cryptography. For regular crypto use Encrypt()/Decrypt().
|
|
@@ -245,7 +245,8 @@ class AESKey(base.CryptoKey, base.SymmetricCrypto):
|
|
|
245
245
|
InputError: invalid inputs
|
|
246
246
|
CryptoError: internal crypto failures, authentication failure, key mismatch, etc
|
|
247
247
|
"""
|
|
248
|
-
|
|
248
|
+
if len(ciphertext) < 32:
|
|
249
|
+
raise base.InputError(f'AES256+GCM should have ≥32 bytes IV/CT/tag: {len(ciphertext)}')
|
|
249
250
|
iv, tag = ciphertext[:16], ciphertext[-16:]
|
|
250
251
|
decryptor: ciphers.CipherContext = ciphers.Cipher(
|
|
251
252
|
algorithms.AES256(self.key256), modes.GCM(iv, tag)).decryptor()
|
transcrypto/base.py
CHANGED
|
@@ -19,12 +19,11 @@ import pickle
|
|
|
19
19
|
# import pdb
|
|
20
20
|
import secrets
|
|
21
21
|
import time
|
|
22
|
-
from typing import Any, Callable, final, MutableSequence, Self, TypeVar
|
|
23
|
-
|
|
22
|
+
from typing import Any, Callable, final, MutableSequence, Protocol, runtime_checkable, Self, TypeVar
|
|
24
23
|
import zstandard
|
|
25
24
|
|
|
26
25
|
__author__ = 'balparda@github.com'
|
|
27
|
-
__version__ = '1.
|
|
26
|
+
__version__ = '1.3.0' # 2025-09-07, Sun
|
|
28
27
|
__version_tuple__: tuple[int, ...] = tuple(int(v) for v in __version__.split('.'))
|
|
29
28
|
|
|
30
29
|
# MIN_TM = int( # minimum allowed timestamp
|
|
@@ -35,8 +34,8 @@ BytesToInt: Callable[[bytes], int] = lambda b: int.from_bytes(b, 'big', signed=F
|
|
|
35
34
|
BytesToEncoded: Callable[[bytes], str] = lambda b: base64.urlsafe_b64encode(b).decode('ascii')
|
|
36
35
|
|
|
37
36
|
HexToBytes: Callable[[str], bytes] = bytes.fromhex
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
IntToFixedBytes: Callable[[int, int], bytes] = lambda i, n: i.to_bytes(n, 'big', signed=False)
|
|
38
|
+
IntToBytes: Callable[[int], bytes] = lambda i: IntToFixedBytes(i, (i.bit_length() + 7) // 8)
|
|
40
39
|
IntToEncoded: Callable[[int], str] = lambda i: BytesToEncoded(IntToBytes(i))
|
|
41
40
|
EncodedToBytes: Callable[[str], bytes] = lambda e: base64.urlsafe_b64decode(e.encode('ascii'))
|
|
42
41
|
|
|
@@ -46,7 +45,7 @@ PadBytesTo: Callable[[bytes, int], bytes] = lambda b, i: b.rjust((i + 7) // 8, b
|
|
|
46
45
|
# these control the pickling of data, do NOT ever change, or you will break all databases
|
|
47
46
|
# <https://docs.python.org/3/library/pickle.html#pickle.DEFAULT_PROTOCOL>
|
|
48
47
|
_PICKLE_PROTOCOL = 4 # protocol 4 available since python v3.8 # do NOT ever change!
|
|
49
|
-
_PICKLE_AAD = b'transcrypto.base.Serialize' # do NOT ever change!
|
|
48
|
+
_PICKLE_AAD = b'transcrypto.base.Serialize.1.0' # do NOT ever change!
|
|
50
49
|
# these help find compressed files, do NOT change unless zstandard changes
|
|
51
50
|
_ZSTD_MAGIC_FRAME = 0xFD2FB528
|
|
52
51
|
_ZSTD_MAGIC_SKIPPABLE_MIN = 0x184D2A50
|
|
@@ -646,11 +645,11 @@ class CryptoKey(abc.ABC):
|
|
|
646
645
|
return BytesToEncoded(self.blob)
|
|
647
646
|
|
|
648
647
|
@final
|
|
649
|
-
def Blob(self, /, *, key:
|
|
648
|
+
def Blob(self, /, *, key: Encryptor | None = None, silent: bool = True) -> bytes:
|
|
650
649
|
"""Serial (bytes) representation of the object with more options, including encryption.
|
|
651
650
|
|
|
652
651
|
Args:
|
|
653
|
-
key (
|
|
652
|
+
key (Encryptor, optional): if given will key.Encrypt() data before saving
|
|
654
653
|
silent (bool, optional): if True (default) will not log
|
|
655
654
|
|
|
656
655
|
Returns:
|
|
@@ -659,11 +658,11 @@ class CryptoKey(abc.ABC):
|
|
|
659
658
|
return Serialize(self, compress=-2, key=key, silent=silent)
|
|
660
659
|
|
|
661
660
|
@final
|
|
662
|
-
def Encoded(self, /, *, key:
|
|
661
|
+
def Encoded(self, /, *, key: Encryptor | None = None, silent: bool = True) -> str:
|
|
663
662
|
"""Base-64 representation of the object with more options, including encryption.
|
|
664
663
|
|
|
665
664
|
Args:
|
|
666
|
-
key (
|
|
665
|
+
key (Encryptor, optional): if given will key.Encrypt() data before saving
|
|
667
666
|
silent (bool, optional): if True (default) will not log
|
|
668
667
|
|
|
669
668
|
Returns:
|
|
@@ -674,14 +673,13 @@ class CryptoKey(abc.ABC):
|
|
|
674
673
|
@final
|
|
675
674
|
@classmethod
|
|
676
675
|
def Load(
|
|
677
|
-
cls, data: str | bytes, /, *,
|
|
678
|
-
key: SymmetricCrypto | None = None, silent: bool = True) -> Self:
|
|
676
|
+
cls, data: str | bytes, /, *, key: Decryptor | None = None, silent: bool = True) -> Self:
|
|
679
677
|
"""Load (create) object from serialized bytes or string.
|
|
680
678
|
|
|
681
679
|
Args:
|
|
682
680
|
data (str | bytes): if bytes is assumed from CryptoKey.blob/Blob(), and
|
|
683
681
|
if string is assumed from CryptoKey.encoded/Encoded()
|
|
684
|
-
key (
|
|
682
|
+
key (Decryptor, optional): if given will key.Encrypt() data before saving
|
|
685
683
|
silent (bool, optional): if True (default) will not log
|
|
686
684
|
|
|
687
685
|
Returns:
|
|
@@ -698,20 +696,21 @@ class CryptoKey(abc.ABC):
|
|
|
698
696
|
return obj # type:ignore
|
|
699
697
|
|
|
700
698
|
|
|
701
|
-
|
|
702
|
-
|
|
699
|
+
@runtime_checkable
|
|
700
|
+
class Encryptor(Protocol): # pylint: disable=too-few-public-methods
|
|
701
|
+
"""Abstract interface for a class that has encryption
|
|
703
702
|
|
|
704
703
|
Contract:
|
|
705
704
|
- If algorithm accepts a `nonce` or `tag` these have to be handled internally by the
|
|
706
|
-
implementation and appended to the ciphertext
|
|
705
|
+
implementation and appended to the `ciphertext`/`signature`.
|
|
707
706
|
- If AEAD is supported, `associated_data` (AAD) must be authenticated. If not supported
|
|
708
707
|
then `associated_data` different from None must raise InputError.
|
|
709
708
|
|
|
710
709
|
Notes:
|
|
711
710
|
The interface is deliberately minimal: byte-in / byte-out.
|
|
712
711
|
Metadata like nonce/tag may be:
|
|
713
|
-
- returned alongside ciphertext
|
|
714
|
-
- bundled/serialized into `ciphertext` by the implementation.
|
|
712
|
+
- returned alongside `ciphertext`/`signature`, or
|
|
713
|
+
- bundled/serialized into `ciphertext`/`signature` by the implementation.
|
|
715
714
|
"""
|
|
716
715
|
|
|
717
716
|
@abc.abstractmethod
|
|
@@ -732,6 +731,11 @@ class SymmetricCrypto(abc.ABC):
|
|
|
732
731
|
CryptoError: internal crypto failures
|
|
733
732
|
"""
|
|
734
733
|
|
|
734
|
+
|
|
735
|
+
@runtime_checkable
|
|
736
|
+
class Decryptor(Protocol): # pylint: disable=too-few-public-methods
|
|
737
|
+
"""Abstract interface for a class that has decryption (see contract/notes in Encryptor)."""
|
|
738
|
+
|
|
735
739
|
@abc.abstractmethod
|
|
736
740
|
def Decrypt(self, ciphertext: bytes, /, *, associated_data: bytes | None = None) -> bytes:
|
|
737
741
|
"""Decrypt `ciphertext` and return the original `plaintext`.
|
|
@@ -749,9 +753,55 @@ class SymmetricCrypto(abc.ABC):
|
|
|
749
753
|
"""
|
|
750
754
|
|
|
751
755
|
|
|
756
|
+
@runtime_checkable
|
|
757
|
+
class Verifier(Protocol): # pylint: disable=too-few-public-methods
|
|
758
|
+
"""Abstract interface for asymmetric signature verify. (see contract/notes in Encryptor)."""
|
|
759
|
+
|
|
760
|
+
@abc.abstractmethod
|
|
761
|
+
def Verify(
|
|
762
|
+
self, message: bytes, signature: bytes, /, *, associated_data: bytes | None = None) -> bool:
|
|
763
|
+
"""Verify a `signature` for `message`. True if OK; False if failed verification.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
message (bytes): Data that was signed (including any embedded nonce/tag if applicable)
|
|
767
|
+
signature (bytes): Signature data to verify (including any embedded nonce/tag if applicable)
|
|
768
|
+
associated_data (bytes, optional): Optional AAD (must match what was used during signing)
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
True if signature is valid, False otherwise
|
|
772
|
+
|
|
773
|
+
Raises:
|
|
774
|
+
InputError: invalid inputs
|
|
775
|
+
CryptoError: internal crypto failures, authentication failure, key mismatch, etc
|
|
776
|
+
"""
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
@runtime_checkable
|
|
780
|
+
class Signer(Protocol): # pylint: disable=too-few-public-methods
|
|
781
|
+
"""Abstract interface for asymmetric signing. (see contract/notes in Encryptor)."""
|
|
782
|
+
|
|
783
|
+
@abc.abstractmethod
|
|
784
|
+
def Sign(self, message: bytes, /, *, associated_data: bytes | None = None) -> bytes:
|
|
785
|
+
"""Sign `message` and return the `signature`.
|
|
786
|
+
|
|
787
|
+
Args:
|
|
788
|
+
message (bytes): Data to sign.
|
|
789
|
+
associated_data (bytes, optional): Optional AAD for AEAD modes; must be
|
|
790
|
+
provided again on decrypt
|
|
791
|
+
|
|
792
|
+
Returns:
|
|
793
|
+
bytes: Signature; if a nonce/tag is needed for decryption, the implementation
|
|
794
|
+
must encode it within the returned bytes (or document how to retrieve it)
|
|
795
|
+
|
|
796
|
+
Raises:
|
|
797
|
+
InputError: invalid inputs
|
|
798
|
+
CryptoError: internal crypto failures
|
|
799
|
+
"""
|
|
800
|
+
|
|
801
|
+
|
|
752
802
|
def Serialize(
|
|
753
803
|
python_obj: Any, /, *, file_path: str | None = None,
|
|
754
|
-
compress: int | None = 3, key:
|
|
804
|
+
compress: int | None = 3, key: Encryptor | None = None, silent: bool = False) -> bytes:
|
|
755
805
|
"""Serialize a Python object into a BLOB, optionally compress / encrypt / save to disk.
|
|
756
806
|
|
|
757
807
|
Data path is:
|
|
@@ -777,7 +827,7 @@ def Serialize(
|
|
|
777
827
|
file_path (str, optional): full path to optionally save the data to
|
|
778
828
|
compress (int | None, optional): Compress level before encrypting/saving; -22 ≤ compress ≤ 22;
|
|
779
829
|
None is no compression; default is 3, which is fast, see table above for other values
|
|
780
|
-
key (
|
|
830
|
+
key (Encryptor, optional): if given will key.Encrypt() data before saving
|
|
781
831
|
silent (bool, optional): if True will not log; default is False (will log)
|
|
782
832
|
|
|
783
833
|
Returns:
|
|
@@ -819,7 +869,7 @@ def Serialize(
|
|
|
819
869
|
|
|
820
870
|
def DeSerialize(
|
|
821
871
|
*, data: bytes | None = None, file_path: str | None = None,
|
|
822
|
-
key:
|
|
872
|
+
key: Decryptor | None = None, silent: bool = False) -> Any:
|
|
823
873
|
"""Loads (de-serializes) a BLOB back to a Python object, optionally decrypting / decompressing.
|
|
824
874
|
|
|
825
875
|
Data path is:
|
|
@@ -835,7 +885,7 @@ def DeSerialize(
|
|
|
835
885
|
if you use this option, `file_path` will be ignored
|
|
836
886
|
file_path (str, optional): if given, use this as file path to load binary data string (input);
|
|
837
887
|
if you use this option, `data` will be ignored
|
|
838
|
-
key (
|
|
888
|
+
key (Decryptor, optional): if given will key.Decrypt() data before decompressing/loading
|
|
839
889
|
silent (bool, optional): if True will not log; default is False (will log)
|
|
840
890
|
|
|
841
891
|
Returns:
|
|
@@ -893,7 +943,7 @@ def DeSerialize(
|
|
|
893
943
|
|
|
894
944
|
|
|
895
945
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
896
|
-
class
|
|
946
|
+
class PublicBid512(CryptoKey):
|
|
897
947
|
"""Public commitment to a (cryptographically secure) bid that can be revealed/validated later.
|
|
898
948
|
|
|
899
949
|
Bid is computed as: public_hash = Hash512(public_key || private_key || secret_bid)
|
|
@@ -901,6 +951,8 @@ class PublicBid(CryptoKey):
|
|
|
901
951
|
Everything is bytes. The public part is (public_key, public_hash) and the private
|
|
902
952
|
part is (private_key, secret_bid). The whole computation can be checked later.
|
|
903
953
|
|
|
954
|
+
No measures are taken here to prevent timing attacks (probably not a concern).
|
|
955
|
+
|
|
904
956
|
Attributes:
|
|
905
957
|
public_key (bytes): 512-bits random value
|
|
906
958
|
public_hash (bytes): SHA-512 hash of (public_key || private_key || secret_bid)
|
|
@@ -915,7 +967,7 @@ class PublicBid(CryptoKey):
|
|
|
915
967
|
Raises:
|
|
916
968
|
InputError: invalid inputs
|
|
917
969
|
"""
|
|
918
|
-
super(
|
|
970
|
+
super(PublicBid512, self).__post_init__() # pylint: disable=super-with-arguments # needed here b/c: dataclass
|
|
919
971
|
if len(self.public_key) != 64 or len(self.public_hash) != 64:
|
|
920
972
|
raise InputError(f'invalid public_key or public_hash: {self}')
|
|
921
973
|
|
|
@@ -925,7 +977,8 @@ class PublicBid(CryptoKey):
|
|
|
925
977
|
Returns:
|
|
926
978
|
string representation of PublicBid
|
|
927
979
|
"""
|
|
928
|
-
return (
|
|
980
|
+
return ('PublicBid512('
|
|
981
|
+
f'public_key={BytesToEncoded(self.public_key)}, '
|
|
929
982
|
f'public_hash={BytesToHex(self.public_hash)})')
|
|
930
983
|
|
|
931
984
|
def VerifyBid(self, private_key: bytes, secret: bytes, /) -> bool:
|
|
@@ -943,7 +996,7 @@ class PublicBid(CryptoKey):
|
|
|
943
996
|
"""
|
|
944
997
|
try:
|
|
945
998
|
# creating the PrivateBid object will validate everything; InputError we allow to propagate
|
|
946
|
-
|
|
999
|
+
PrivateBid512(
|
|
947
1000
|
public_key=self.public_key, public_hash=self.public_hash,
|
|
948
1001
|
private_key=private_key, secret_bid=secret)
|
|
949
1002
|
return True # if we got here, all is good
|
|
@@ -951,13 +1004,13 @@ class PublicBid(CryptoKey):
|
|
|
951
1004
|
return False # bid does not match the public commitment
|
|
952
1005
|
|
|
953
1006
|
@classmethod
|
|
954
|
-
def Copy(cls, other:
|
|
1007
|
+
def Copy(cls, other: PublicBid512, /) -> Self:
|
|
955
1008
|
"""Initialize a public bid by taking the public parts of a public/private bid."""
|
|
956
1009
|
return cls(public_key=other.public_key, public_hash=other.public_hash)
|
|
957
1010
|
|
|
958
1011
|
|
|
959
1012
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
960
|
-
class
|
|
1013
|
+
class PrivateBid512(PublicBid512):
|
|
961
1014
|
"""Private bid that can be revealed and validated against a public commitment (see PublicBid).
|
|
962
1015
|
|
|
963
1016
|
Attributes:
|
|
@@ -975,7 +1028,7 @@ class PrivateBid(PublicBid):
|
|
|
975
1028
|
InputError: invalid inputs
|
|
976
1029
|
CryptoError: bid does not match the public commitment
|
|
977
1030
|
"""
|
|
978
|
-
super(
|
|
1031
|
+
super(PrivateBid512, self).__post_init__() # pylint: disable=super-with-arguments # needed here b/c: dataclass
|
|
979
1032
|
if len(self.private_key) != 64 or len(self.secret_bid) < 1:
|
|
980
1033
|
raise InputError(f'invalid private_key or secret_bid: {self}')
|
|
981
1034
|
if self.public_hash != Hash512(self.public_key + self.private_key + self.secret_bid):
|
|
@@ -987,7 +1040,8 @@ class PrivateBid(PublicBid):
|
|
|
987
1040
|
Returns:
|
|
988
1041
|
string representation of PrivateBid without leaking secrets
|
|
989
1042
|
"""
|
|
990
|
-
return (
|
|
1043
|
+
return ('PrivateBid512('
|
|
1044
|
+
f'{super(PrivateBid512, self).__str__()}, ' # pylint: disable=super-with-arguments
|
|
991
1045
|
f'private_key={ObfuscateSecret(self.private_key)}, '
|
|
992
1046
|
f'secret_bid={ObfuscateSecret(self.secret_bid)})')
|
|
993
1047
|
|