sm3hash 0.0.1__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.
sm3hash/__init__.py ADDED
@@ -0,0 +1,198 @@
1
+ """
2
+ Author: lqxnjk <lqxnjk@qq.com>
3
+ Created: 2026-10-23 10:06
4
+ Modified: 2026-10-23 10:06
5
+ Python Version: 3.10
6
+ Pure-Python SM3 hash (GB/T 32905-2016). Zero dependencies.
7
+
8
+ Standalone package: stdlib only, no internal dependencies.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import pathlib
15
+ import struct
16
+
17
+ __all__ = ["sm3_hex", "sm3_of_file"]
18
+
19
+ # SM3 初始向量(IV)与 64 轮常量 T:GB/T 32905-2016 固定值,此处为唯一定义点。
20
+ _IV: tuple[int, ...] = (
21
+ 0x7380166F,
22
+ 0x4914B2B9,
23
+ 0x172442D7,
24
+ 0xDA8A0600,
25
+ 0xA96F30BC,
26
+ 0x163138AA,
27
+ 0xE38DEE4D,
28
+ 0xB0FB0E4E,
29
+ )
30
+ _T: tuple[int, ...] = tuple(0x79CC4519 if _i < 16 else 0x7A879D8A for _i in range(64))
31
+
32
+ # 32 位取模掩码:SM3 全部运算都在模 2^32 下进行,加/移位后统一按此掩码收敛。
33
+ _MASK32 = 0xFFFFFFFF
34
+
35
+
36
+ def _left_rotate(m_value: int, m_bits: int) -> int:
37
+ """对 32 位无符号整数做循环左移,返回 32 位结果。
38
+
39
+ Args:
40
+ m_value: 必填,待移位的 32 位无符号整数。
41
+ m_bits: 必填,左移位数,取值 0~31;为 0 时原样返回。
42
+
43
+ Returns:
44
+ 循环左移后的 32 位无符号整数。
45
+ """
46
+ return ((m_value << m_bits) & _MASK32) | ((m_value >> (32 - m_bits)) & _MASK32)
47
+
48
+
49
+ def _p0(m_value: int) -> int:
50
+ """置换函数 P0:``X ^ ROTL(X, 9) ^ ROTL(X, 17)``。"""
51
+ return m_value ^ _left_rotate(m_value, 9) ^ _left_rotate(m_value, 17)
52
+
53
+
54
+ def _p1(m_value: int) -> int:
55
+ """置换函数 P1:``X ^ ROTL(X, 15) ^ ROTL(X, 23)``。"""
56
+ return m_value ^ _left_rotate(m_value, 15) ^ _left_rotate(m_value, 23)
57
+
58
+
59
+ def _ff(m_x: int, m_y: int, m_z: int, m_round: int) -> int:
60
+ """布尔函数 FF:前 16 轮为异或,其后为三变量多数表决。"""
61
+ return (m_x ^ m_y ^ m_z) if m_round < 16 else ((m_x & m_y) | (m_x & m_z) | (m_y & m_z))
62
+
63
+
64
+ def _gg(m_x: int, m_y: int, m_z: int, m_round: int) -> int:
65
+ """布尔函数 GG:前 16 轮为异或,其后为选择函数 ``(X & Y) | (~X & Z)``。"""
66
+ return (m_x ^ m_y ^ m_z) if m_round < 16 else ((m_x & m_y) | (~m_x & m_z))
67
+
68
+
69
+ def _padding(m_data: bytes) -> bytes:
70
+ """按 SM3 规范填充消息,使总长度为 64 字节的整数倍。
71
+
72
+ Args:
73
+ m_data: 必填,原始消息字节。
74
+
75
+ Returns:
76
+ 追加 ``0x80``、若干 ``0x00`` 与 64 位原始比特长度后的完整消息。
77
+
78
+ Note:
79
+ 长度字段为原始消息的**比特**长度,按大端 64 位写入。
80
+ """
81
+ _message = bytearray(m_data)
82
+ _bit_length = len(_message) * 8
83
+ _message.append(0x80)
84
+ _message.extend(b"\x00" * ((56 - len(_message) % 64) % 64))
85
+ _message += struct.pack(">Q", _bit_length)
86
+ return bytes(_message)
87
+
88
+
89
+ def _compress(m_state: tuple[int, ...], m_block: bytes) -> tuple[int, ...]:
90
+ """对单个 512 位消息块执行 64 轮压缩,返回更新后的 8 字状态。
91
+
92
+ Args:
93
+ m_state: 必填,压缩前的 8 个 32 位状态字。
94
+ m_block: 必填,长度恰为 64 字节的消息块。
95
+
96
+ Returns:
97
+ 压缩后的 8 个 32 位状态字元组。
98
+ """
99
+ _words = list(struct.unpack(">16L", m_block))
100
+ # 消息扩展:W[16..67] 由前 16 字递推,W'[0..63] 为 W[j] ^ W[j+4]
101
+ for _j in range(16, 68):
102
+ _words.append(_p1(_words[_j - 16] ^ _words[_j - 9] ^ _left_rotate(_words[_j - 3], 15)) ^ _left_rotate(_words[_j - 13], 7) ^ _words[_j - 6])
103
+ _words_ = [_words[_j] ^ _words[_j + 4] for _j in range(64)]
104
+
105
+ _a, _b, _c, _d, _e, _f, _g, _h = m_state
106
+ for _j in range(64):
107
+ _ss1 = _left_rotate((_left_rotate(_a, 12) + _e + _left_rotate(_T[_j], _j % 32)) & _MASK32, 7)
108
+ _ss2 = _ss1 ^ _left_rotate(_a, 12)
109
+ _tt1 = (_ff(_a, _b, _c, _j) + _d + _ss2 + _words_[_j]) & _MASK32
110
+ _tt2 = (_gg(_e, _f, _g, _j) + _h + _ss1 + _words[_j]) & _MASK32
111
+ _d = _c
112
+ _c = _left_rotate(_b, 9)
113
+ _b = _a
114
+ _a = _tt1
115
+ _h = _g
116
+ _g = _left_rotate(_f, 19)
117
+ _f = _e
118
+ _e = _p0(_tt2)
119
+
120
+ return tuple((m_state[_i] ^ _value) & _MASK32 for _i, _value in enumerate((_a, _b, _c, _d, _e, _f, _g, _h)))
121
+
122
+
123
+ def sm3_hex(m_data: bytes | str) -> str:
124
+ """计算数据的 SM3 杂凑值,返回 64 位小写十六进制字符串。
125
+
126
+ 加速路径:优先用 ``hashlib.new("sm3")``(OpenSSL 的 C 实现),不可用时回退本模块的
127
+ 纯 Python 实现(``_sm3_pure``)。两者结果经 GB/T 32905-2016 标准向量与多组随机输入
128
+ 交叉校验一致。
129
+
130
+ Args:
131
+ m_data: 必填,待计算的数据;``str`` 按 UTF-8 编码后参与计算,``bytes`` 原样参与。
132
+
133
+ Returns:
134
+ 32 字节摘要的十六进制表示(定长 64 字符,小写)。
135
+
136
+ Note:
137
+ 等价迁移自旧项目 py-tasks 的 ``etls._extra.SM3`` 类(``str(SM3(x))`` 即本函数返回值),
138
+ 用模块级函数替代实例构造,避免为一次性摘要创建对象;算法与轮常量按 GB/T 32905-2016
139
+ 实现。纯 Python 路径保留为**兜底**:部分 OpenSSL 构建未启用 sm3,此时 C 路径不可用。
140
+ 实测 1 MiB 输入 C 路径约 3 ms、纯 Python 约 2.5 s(约 800 倍差距)。
141
+ """
142
+ _bytes_data = m_data.encode("utf-8") if isinstance(m_data, str) else m_data
143
+ try:
144
+ return hashlib.new("sm3", _bytes_data).hexdigest()
145
+ except (ValueError, TypeError):
146
+ # OpenSSL 未启用 sm3(ValueError: unsupported hash type)或入参类型异常时走纯 Python
147
+ return _sm3_pure(_bytes_data)
148
+
149
+
150
+ def sm3_of_file(m_path: str, n_chunk: int = 1024 * 1024) -> str:
151
+ """按块读取文件并计算 SM3,返回 64 位小写十六进制;与 :func:`sm3_hex` 一次性计算**同值**。
152
+
153
+ 用于「数据已落盘」的场景:此时若走 :func:`sm3_hex` 就得先把整份读进内存,
154
+ 等于在摘要之外又添一份全量拷贝;而SM3 属Merkle–Damgård 型摘要,逐块 ``update``
155
+ 与一次性传入等价,可安全分块。
156
+
157
+ Args:
158
+ m_path: 必填,待计算文件的本地路径(``str`` 或 ``pathlib.Path``)。
159
+ n_chunk: 选填,读块字节数,默认 1 MiB;仅影响读取次数,不影响结果。
160
+
161
+ Returns:
162
+ 32 字节摘要的十六进制表示(定长 64 字符,小写)。
163
+
164
+ Raises:
165
+ OSError: 文件不存在或读取失败时抛出。
166
+ ValueError: OpenSSL 未启用 sm3 且纯 Python 路径也不可用时抛出。
167
+
168
+ Note:
169
+ 峰值内存 ≈ 一个 ``n_chunk``,**与文件体积无关**。
170
+ """
171
+ try:
172
+ _hasher = hashlib.new("sm3")
173
+ except (ValueError, TypeError):
174
+ # C 路径不可用:纯 Python 实现暂不支持增量,退回整份读入(此路径本就不该是大文件)
175
+ return sm3_hex(pathlib.Path(m_path).read_bytes())
176
+ with open(m_path, "rb") as _file:
177
+ while True:
178
+ _block = _file.read(n_chunk)
179
+ if not _block:
180
+ break
181
+ _hasher.update(_block)
182
+ return _hasher.hexdigest()
183
+
184
+
185
+ def _sm3_pure(m_bytes: bytes) -> str:
186
+ """纯 Python 的 SM3 实现(兜底路径,不依赖 OpenSSL 是否启用 sm3)。
187
+
188
+ Args:
189
+ m_bytes: 必填,原始消息字节。
190
+
191
+ Returns:
192
+ 32 字节摘要的十六进制字符串(定长 64 字符,小写)。
193
+ """
194
+ _state: tuple[int, ...] = _IV
195
+ _padded = _padding(m_bytes)
196
+ for _offset in range(0, len(_padded), 64):
197
+ _state = _compress(_state, _padded[_offset: _offset + 64])
198
+ return "".join(f"{_value:08x}" for _value in _state)
sm3hash/py.typed ADDED
File without changes
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.5
2
+ Name: sm3hash
3
+ Version: 0.0.1
4
+ Summary: Pure-Python SM3 hash (GB/T 32905-2016). Zero dependencies.
5
+ Project-URL: Homepage, https://github.com/lqxnjk/sm3hash
6
+ Project-URL: Source, https://github.com/lqxnjk/sm3hash
7
+ Author: lqxnjk
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: cryptography,digest,gm,hash,sm3,国密
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Security :: Cryptography
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+
22
+ # sm3hash
23
+
24
+ Pure-Python implementation of **SM3**, the Chinese national cryptographic hash
25
+ standard (GB/T 32905-2016). No compiled extensions, no dependencies, works
26
+ anywhere Python 3.10+ runs.
27
+
28
+ Produces the standard 256-bit (64 hex character) digest.
29
+
30
+ ## Install
31
+
32
+ ```console
33
+ pip install sm3hash
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ```python
39
+ from sm3hash import sm3_hex, sm3_of_file
40
+
41
+ # text or bytes
42
+ assert sm3_hex("abc") == "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0"
43
+
44
+ # bytes are hashed as-is
45
+ sm3_hex(b"abc")
46
+
47
+ # large files, streamed in 1 MiB chunks
48
+ sm3_of_file("big.iso")
49
+ sm3_of_file("big.iso", n_chunk=4 * 1024 * 1024)
50
+ ```
51
+
52
+ ## API
53
+
54
+ | Function | Description |
55
+ | --- | --- |
56
+ | `sm3_hex(data)` | Digest a `str` (UTF-8 encoded) or `bytes`, returns hex string |
57
+ | `sm3_of_file(path, chunk=1 MiB)` | Digest a file by streaming, returns hex string |
58
+
59
+ Strings are encoded as UTF-8 before hashing. Pass `bytes` to control the
60
+ encoding yourself.
61
+
62
+ ## Performance
63
+
64
+ Pure Python, roughly an order of magnitude slower than `hashlib.sha256` on the
65
+ same input. Fine for hashing tokens, small payloads and verifying signatures;
66
+ for bulk throughput consider a C-backed SM3 binding instead.
67
+
68
+ ## License
69
+
70
+ MIT
@@ -0,0 +1,6 @@
1
+ sm3hash/__init__.py,sha256=JCflj6dEUGyFSRxkWVor-QM6ybKvcr-kTTvlu9P_Fc0,7746
2
+ sm3hash/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ sm3hash-0.0.1.dist-info/METADATA,sha256=QUIpbhu2BGOIzLrSoS5DGpLzXnhM0A7k8x9fL9NrhHM,1952
4
+ sm3hash-0.0.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
5
+ sm3hash-0.0.1.dist-info/licenses/LICENSE,sha256=Q2c07NeDARb1XSKFLKAaKA8pCiR_C4baR6PRyCYj-t8,1063
6
+ sm3hash-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lqxnjk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.