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.
- crossmool-1.3.0/PKG-INFO +106 -0
- crossmool-1.3.0/README.md +79 -0
- crossmool-1.3.0/pyproject.toml +38 -0
- crossmool-1.3.0/setup.cfg +4 -0
- crossmool-1.3.0/src/crossmool/__init__.py +11 -0
- crossmool-1.3.0/src/crossmool/der.py +84 -0
- crossmool-1.3.0/src/crossmool/encoding.py +56 -0
- crossmool-1.3.0/src/crossmool/sm2_util.py +497 -0
- crossmool-1.3.0/src/crossmool/sm3_util.py +117 -0
- crossmool-1.3.0/src/crossmool/sm4_util.py +186 -0
- crossmool-1.3.0/src/crossmool.egg-info/PKG-INFO +106 -0
- crossmool-1.3.0/src/crossmool.egg-info/SOURCES.txt +16 -0
- crossmool-1.3.0/src/crossmool.egg-info/dependency_links.txt +1 -0
- crossmool-1.3.0/src/crossmool.egg-info/requires.txt +4 -0
- crossmool-1.3.0/src/crossmool.egg-info/top_level.txt +1 -0
- crossmool-1.3.0/tests/test_sm2_interop.py +72 -0
- crossmool-1.3.0/tests/test_sm3.py +36 -0
- crossmool-1.3.0/tests/test_sm4.py +49 -0
crossmool-1.3.0/PKG-INFO
ADDED
|
@@ -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,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')
|