crossmool 1.3.0__tar.gz

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.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: crossmool
3
+ Version: 1.3.0
4
+ Summary: Cross-language SM2/SM3/SM4 cryptographic utilities, interoperable with the Java/Go/Rust/C/C++/C#/JavaScript implementations
5
+ Author: crossmool
6
+ License-Expression: LGPL-3.0-only
7
+ Project-URL: Homepage, https://gitee.com/iilic/crossmool
8
+ Project-URL: Repository, https://gitee.com/iilic/crossmool
9
+ Keywords: sm2,sm3,sm4,cryptography,gmssl,sm-crypto,chinese-cryptography,encryption
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Security :: Cryptography
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: gmssl>=3.2.2
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=7.0; extra == "test"
27
+
28
+ # crossmool
29
+
30
+ Cross-language SM2/SM3/SM4 cryptographic utilities. The Python implementation is fully interoperable with the Java/Go/Rust/C/C++/C#/JavaScript implementations in the [crossmool](https://gitee.com/iilic/crossmool) project.
31
+
32
+ ## Features
33
+
34
+ - **SM2** — asymmetric encryption/decryption and signature (C1C3C2 ciphertext, X.509 / PKCS#8 DER keys, DER signatures).
35
+ - **SM3** — hash and HMAC-SM3.
36
+ - **SM4** — symmetric GCM encryption/decryption.
37
+ - Hex and Base64 encodings supported throughout.
38
+ - Guaranteed cross-language interop (simplified Chinese, traditional Chinese and English Unicode input).
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install crossmool
44
+ ```
45
+
46
+ Requires Python 3.8+.
47
+
48
+ ## Quick start
49
+
50
+ ### SM2 — encrypt / decrypt / sign / verify
51
+
52
+ ```python
53
+ from crossmool import SM2Util
54
+
55
+ priv, pub = SM2Util.generate_keypair_raw()
56
+
57
+ # encrypt then decrypt (Hex)
58
+ cipher = SM2Util.encrypt('hello', pub)
59
+ assert SM2Util.decrypt(cipher, priv) == 'hello'
60
+
61
+ # sign then verify
62
+ sig = SM2Util.sign(cipher, priv)
63
+ assert SM2Util.verify_str(cipher, pub, sig)
64
+ ```
65
+
66
+ ### SM3 — hash / HMAC
67
+
68
+ ```python
69
+ from crossmool import SM3Util
70
+
71
+ digest = SM3Util.digest_str('hello 国密')
72
+ key = SM3Util.generate_hmac_key_to_str()
73
+ mac = SM3Util.hmac_str(key, 'hello 国密')
74
+ assert SM3Util.hmac_verify(key, 'hello 国密', mac)
75
+ ```
76
+
77
+ ### SM4 — GCM encrypt / decrypt
78
+
79
+ ```python
80
+ from crossmool import SM4Util
81
+
82
+ key = SM4Util.generate_key()
83
+ cipher = SM4Util.encrypt_gcm_string(key, 'hello 国密')
84
+ plain = SM4Util.decrypt_gcm_to_string(key, cipher)
85
+ assert plain == 'hello 国密'
86
+ ```
87
+
88
+ ## Development
89
+
90
+ Editable install for development:
91
+
92
+ ```bash
93
+ cd <repo-root>
94
+ pip install -e python/crossmool
95
+ ```
96
+
97
+ Run the test suite:
98
+
99
+ ```bash
100
+ cd python/crossmool
101
+ pytest -q
102
+ ```
103
+
104
+ ## License
105
+
106
+ LGPL-v3. See the [LICENSE](https://gitee.com/iilic/crossmool) file for details.
@@ -0,0 +1,79 @@
1
+ # crossmool
2
+
3
+ Cross-language SM2/SM3/SM4 cryptographic utilities. The Python implementation is fully interoperable with the Java/Go/Rust/C/C++/C#/JavaScript implementations in the [crossmool](https://gitee.com/iilic/crossmool) project.
4
+
5
+ ## Features
6
+
7
+ - **SM2** — asymmetric encryption/decryption and signature (C1C3C2 ciphertext, X.509 / PKCS#8 DER keys, DER signatures).
8
+ - **SM3** — hash and HMAC-SM3.
9
+ - **SM4** — symmetric GCM encryption/decryption.
10
+ - Hex and Base64 encodings supported throughout.
11
+ - Guaranteed cross-language interop (simplified Chinese, traditional Chinese and English Unicode input).
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install crossmool
17
+ ```
18
+
19
+ Requires Python 3.8+.
20
+
21
+ ## Quick start
22
+
23
+ ### SM2 — encrypt / decrypt / sign / verify
24
+
25
+ ```python
26
+ from crossmool import SM2Util
27
+
28
+ priv, pub = SM2Util.generate_keypair_raw()
29
+
30
+ # encrypt then decrypt (Hex)
31
+ cipher = SM2Util.encrypt('hello', pub)
32
+ assert SM2Util.decrypt(cipher, priv) == 'hello'
33
+
34
+ # sign then verify
35
+ sig = SM2Util.sign(cipher, priv)
36
+ assert SM2Util.verify_str(cipher, pub, sig)
37
+ ```
38
+
39
+ ### SM3 — hash / HMAC
40
+
41
+ ```python
42
+ from crossmool import SM3Util
43
+
44
+ digest = SM3Util.digest_str('hello 国密')
45
+ key = SM3Util.generate_hmac_key_to_str()
46
+ mac = SM3Util.hmac_str(key, 'hello 国密')
47
+ assert SM3Util.hmac_verify(key, 'hello 国密', mac)
48
+ ```
49
+
50
+ ### SM4 — GCM encrypt / decrypt
51
+
52
+ ```python
53
+ from crossmool import SM4Util
54
+
55
+ key = SM4Util.generate_key()
56
+ cipher = SM4Util.encrypt_gcm_string(key, 'hello 国密')
57
+ plain = SM4Util.decrypt_gcm_to_string(key, cipher)
58
+ assert plain == 'hello 国密'
59
+ ```
60
+
61
+ ## Development
62
+
63
+ Editable install for development:
64
+
65
+ ```bash
66
+ cd <repo-root>
67
+ pip install -e python/crossmool
68
+ ```
69
+
70
+ Run the test suite:
71
+
72
+ ```bash
73
+ cd python/crossmool
74
+ pytest -q
75
+ ```
76
+
77
+ ## License
78
+
79
+ LGPL-v3. See the [LICENSE](https://gitee.com/iilic/crossmool) file for details.
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "crossmool"
7
+ version = "1.3.0"
8
+ description = "Cross-language SM2/SM3/SM4 cryptographic utilities, interoperable with the Java/Go/Rust/C/C++/C#/JavaScript implementations"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "LGPL-3.0-only"
12
+ authors = [{ name = "crossmool" }]
13
+ keywords = ["sm2", "sm3", "sm4", "cryptography", "gmssl", "sm-crypto", "chinese-cryptography", "encryption"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Security :: Cryptography",
27
+ ]
28
+ dependencies = ["gmssl>=3.2.2"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://gitee.com/iilic/crossmool"
32
+ Repository = "https://gitee.com/iilic/crossmool"
33
+
34
+ [project.optional-dependencies]
35
+ test = ["pytest>=7.0"]
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ from crossmool.sm2_util import SM2Util
2
+ from crossmool.sm3_util import SM3Util
3
+ from crossmool.sm4_util import SM4Util
4
+
5
+ __all__ = [
6
+ "SM2Util",
7
+ "SM3Util",
8
+ "SM4Util",
9
+ ]
10
+
11
+ __version__ = "1.3.0"
@@ -0,0 +1,84 @@
1
+ """
2
+ Minimal DER reader to parse SPKI (X.509) and PKCS8 used by crossmool JS/Java utils.
3
+ This is a small port of the project's JS `DerReader` used to extract raw EC keys.
4
+ """
5
+ from typing import Tuple
6
+
7
+
8
+ class DerReader:
9
+ def __init__(self, data: bytes):
10
+ self.data = data
11
+ self.pos = 0
12
+ self.len = len(data)
13
+
14
+ def _read_length(self) -> int:
15
+ first = self._read(1)[0]
16
+ if first & 0x80 == 0:
17
+ return first
18
+ num = first & 0x7F
19
+ val = 0
20
+ for _ in range(num):
21
+ val = (val << 8) | self._read(1)[0]
22
+ return val
23
+
24
+ def _read(self, n: int) -> bytes:
25
+ if self.pos + n > self.len:
26
+ raise ValueError('Truncated DER')
27
+ out = self.data[self.pos:self.pos + n]
28
+ self.pos += n
29
+ return out
30
+
31
+ def read_sequence(self) -> 'DerReader':
32
+ tag = self._read(1)[0]
33
+ if tag != 0x30:
34
+ raise ValueError('Expected SEQUENCE')
35
+ length = self._read_length()
36
+ content = self._read(length)
37
+ return DerReader(content)
38
+
39
+ def read_integer_bytes(self) -> bytes:
40
+ tag = self._read(1)[0]
41
+ if tag != 0x02:
42
+ raise ValueError('Expected INTEGER')
43
+ length = self._read_length()
44
+ return self._read(length)
45
+
46
+ def read_oid(self) -> str:
47
+ tag = self._read(1)[0]
48
+ if tag != 0x06:
49
+ raise ValueError('Expected OID')
50
+ length = self._read_length()
51
+ oid_bytes = self._read(length)
52
+ # decode OID
53
+ first = oid_bytes[0]
54
+ oid_nodes = [str(first // 40), str(first % 40)]
55
+ value = 0
56
+ for b in oid_bytes[1:]:
57
+ value = (value << 7) | (b & 0x7F)
58
+ if not (b & 0x80):
59
+ oid_nodes.append(str(value))
60
+ value = 0
61
+ return '.'.join(oid_nodes)
62
+
63
+ def read_bit_string(self) -> Tuple[int, bytes]:
64
+ tag = self._read(1)[0]
65
+ if tag != 0x03:
66
+ raise ValueError('Expected BIT STRING')
67
+ length = self._read_length()
68
+ content = self._read(length)
69
+ unused_bits = content[0]
70
+ data = content[1:]
71
+ return unused_bits, data
72
+
73
+ def read_octet_string(self) -> bytes:
74
+ tag = self._read(1)[0]
75
+ if tag != 0x04:
76
+ raise ValueError('Expected OCTET STRING')
77
+ length = self._read_length()
78
+ return self._read(length)
79
+
80
+ def read(self, n: int) -> bytes:
81
+ return self._read(n)
82
+
83
+ def eof(self) -> bool:
84
+ return self.pos >= self.len
@@ -0,0 +1,56 @@
1
+ import base64
2
+ import re
3
+
4
+
5
+ def is_hex_string(s: str) -> bool:
6
+ if not isinstance(s, str):
7
+ return False
8
+ s = s.strip()
9
+ return bool(re.fullmatch(r"[0-9a-fA-F]+", s))
10
+
11
+
12
+ def is_base64_string(s: str) -> bool:
13
+ if not isinstance(s, str):
14
+ return False
15
+ s = s.strip()
16
+ try:
17
+ # len check: base64 length divisible by 4 (with padding)
18
+ if len(s) % 4 != 0:
19
+ return False
20
+ base64.b64decode(s, validate=True)
21
+ return True
22
+ except Exception:
23
+ return False
24
+
25
+
26
+ def hex_to_bytes(hex_str: str) -> bytes:
27
+ return bytes.fromhex(hex_str.strip())
28
+
29
+
30
+ def bytes_to_hex(b: bytes) -> str:
31
+ return b.hex()
32
+
33
+
34
+ def base64_to_bytes(s: str) -> bytes:
35
+ return base64.b64decode(s)
36
+
37
+
38
+ def bytes_to_base64(b: bytes) -> str:
39
+ return base64.b64encode(b).decode('ascii')
40
+
41
+
42
+ def detect_string_encoding(s: str) -> str:
43
+ s = s.strip()
44
+ if is_hex_string(s):
45
+ return 'hex'
46
+ if is_base64_string(s):
47
+ return 'base64'
48
+ return 'unknown'
49
+
50
+
51
+ def utf8_to_bytes(s: str) -> bytes:
52
+ return s.encode('utf-8')
53
+
54
+
55
+ def bytes_to_utf8(b: bytes) -> str:
56
+ return b.decode('utf-8')