Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

ferstar/GmSSL-Python

Open more actions menu
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

89 Commits
89 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GmSSL-Python

简介

gmssl-python是GmSSL密码库 https://github.com/guanzhi/GmSSL 的Python语言封装,以ctypes方式实现,通过Python类和函数提供了如下密码接口:

  • 密码随机数生成器
  • SM2加密和签名,SM2密钥生成、私钥口令加密保护、密钥PEM文件导入导出
  • SM2数字证书的导入、解析和验证
  • SM3哈希函数、HMAC-SM3消息认证码、基于SM3的PBKDF2密钥导出函数
  • SM4分组加密,以及SM4的CBC、CTR、GCM三种加密模式
  • SM9加密和签名,以及SM9密钥生成、密钥口令加密保护、密钥PEM文件导入导出
  • ZUC序列密码加密

目前gmssl-python功能可以覆盖除SSL/TLS/TLCP之外的国密算法主要应用开发场景。

项目结构

本项目采用 Python 包最佳实践的 src/ 布局:

GmSSL-Python/
├── src/
│   └── gmssl/
│       ├── __init__.py    # 公共 API 导出
│       └── _core.py       # 核心实现 (ctypes bindings)
├── tests/
│   └── test_gmssl.py      # 测试套件
└── pyproject.toml         # 包配置

为什么使用 src/ 布局?

  • 强制开发者测试已安装的包,而不是源代码目录
  • 避免开发环境和安装版本之间的导入混淆
  • PyPA (Python Packaging Authority) 推荐
  • 被现代 Python 项目广泛采用 (requests, pytest 等)

安装

快速安装(推荐)

从 v2.2.2 开始,gmssl-python 已经包含了 GmSSL 动态库,可以直接通过 pip 安装使用:

pip install gmssl-python

支持的平台:

  • Linux x86_64 (GLIBC 2.17+) - Ubuntu 14.04+, Debian 8+, RHEL 7+
  • Linux aarch64 (GLIBC 2.17+) - ARM64 服务器和开发板
  • macOS (11.0+) - Intel 和 Apple Silicon 通用二进制
  • Windows x86_64 - Windows 10/11

安装后即可直接使用,无需额外安装 GmSSL 库。

系统要求:

  • Linux: GLIBC 2.17 或更高版本(2012 年发布,几乎所有现代发行版都满足)
  • macOS: macOS 11.0 (Big Sur) 或更高版本
  • Windows: Windows 10 或更高版本

库加载优先级

gmssl-python 使用智能库加载策略(遵循 "Never break userspace" 原则):

  1. 系统库优先:如果系统已安装 GmSSL(如 /usr/local/lib/libgmssl.so),优先使用系统库
  2. 包内库回退:如果系统没有 GmSSL,使用包内打包的库
  3. 版本检查:如果系统库版本 < 3.1.1,自动回退到包内库(如果可用)

这确保了:

  • ✅ 已有系统 GmSSL 的用户继续使用其版本(向后兼容)
  • ✅ 新用户无需手动安装 GmSSL(开箱即用)
  • ✅ 开发者可以通过安装系统 GmSSL 覆盖包内库(灵活性)

手动安装 GmSSL(可选)

如果你希望使用系统级 GmSSL 库(例如需要最新版本或自定义编译),可以按以下步骤手动安装:

安装GmSSL

首先在https://github.com/guanzhi/GmSSL 项目上下载最新的GmSSL代码GmSSL-master.zip,编译并安装。GmSSL代码是C语言编写的,需要安装GCC、CMake来编译,在Ubuntu/Debian系统上可以执行

sudo install build-essentials cmake

安装依赖的编译工具,然后解压GmSSL源代码,进入源码目录GmSSL-master并执行如下指令:

$ mkdir build
$ cd build
$ cmake ..
$ make
$ make test
$ sudo make install

安装完成后可以执行gmssl命令行工具检查是否安装完毕。

$ gmssl help

由于gmssl-python需要libgmssl动态库,因此GmSSL安装时不要改变配置,仅以静态库安装时gmssl-python是不可用的。安装后执行gmssl命令可能提示找不到动态库,在Ubuntu系统下可以执行sudo ldconfig来发现新安装的动态库,在CentOS系统上需要在/etc/ld.so.conf配置文件中将libgmssl动态库的目录/usr/local/lib加入到配置文件中。

从Python代码仓库安装gmssl-python

gmssl-python 会定期发布到Python代码仓库中,可以通过pip工具安装

$ pip install gmssl-python
$ pip show gmssl-python

通过pip show命令可以查看当前安装的gmssl-python的版本信息。

下载源码本地安装

从代码仓库中安装的gmssl-python通常不是最新版本,可以下载最新的GmSSL-Python代码 GmSSL-Python-main.zip,本地安装。

解压缩并进入源代码目录GmSSL-Python-main。由于最新代码可能还处于开发过程中,在安装前必须进行测试确保全部功能正确,gmssl-python中提供了基于 pytest 的测试,执行如下命令

运行测试

$ pytest tests/ -v
================================================= test session starts =================================================
platform darwin -- Python 3.12.9, pytest-8.4.2, pluggy-1.6.0
collected 19 items

tests/test_gmssl.py::TestVersion::test_library_version_num PASSED                                              [  5%]
tests/test_gmssl.py::TestVersion::test_library_version_string PASSED                                           [ 10%]
...
tests/test_gmssl.py::TestSM2Certificate::test_sm2_certificate_parsing PASSED                                   [100%]

================================================= 19 passed in 1.04s ==================================================

上面的输出表明测试通过。

然后可以通过pip命令安装当前目录下的代码

$ pip install .
$ pip show gmssl-python

验证安装成功

注意gmssl-python包中只包含一个gmssl模块(而不是gmssl_python模块)。

可以在Python交互环境中做简单的测试

>>> import gmssl
>>> gmssl.GMSSL_PYTHON_VERSION
>>> gmssl.GMSSL_LIBRARY_VERSION

分别查看当前gmssl-python的版本和libgmssl的版本。

编写一个简单的测试程序demo.py

from gmssl import Sm3

def main():
    """
    Computes the SM3 hash of the string 'abc' and prints it.
    """
    sm3 = Sm3()
    sm3.update(b'abc')
    dgst = sm3.digest()
    print(f"sm3('abc') : {dgst.hex()}")

if __name__ == '__main__':
    main()

执行这个程序

$ python demo.py
sm3('abc') : 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0

可以看到运行成功。通过gmssl命令行验证输出是正确的

echo -n abc | gmssl sm3

可以看到输出相同的SM3哈希值

开发指南

运行测试

项目包含 117 个测试,覆盖所有主要功能、错误处理、边界条件、性能和线程安全测试。

# 运行所有测试(推荐使用 uv)
uv run pytest tests/ -v

# 或使用 pytest 直接运行
pytest tests/ -v

# 运行特定测试文件
uv run pytest tests/test_errors.py -v        # 错误处理测试
uv run pytest tests/test_edge_cases.py -v    # 边界条件测试
uv run pytest tests/test_additional_methods.py -v  # 补充方法测试
uv run pytest tests/test_pygmssl_missing.py -v     # pygmssl缺失测试
uv run pytest tests/test_thread_safety.py -v       # 线程安全测试

测试覆盖:

  • ✅ 功能测试: 17 个
  • ✅ 错误处理测试: 34 个
  • ✅ 边界条件测试: 28 个
  • ✅ 补充方法测试: 15 个
  • ✅ pygmssl缺失测试: 13 个(性能、压力、边界测试)
  • ✅ 线程安全测试: 10 个(多线程并发测试)

线程安全说明

⚠️ 重要: Sm4Gcm 类由于底层 GmSSL 库实现限制,不是线程安全的

如果需要在多线程环境中使用 SM4-GCM,必须使用锁保护:

import threading
from gmssl import Sm4Gcm, DO_ENCRYPT

lock = threading.Lock()

def encrypt_with_gcm(key, iv, aad, plaintext):
    with lock:  # 必须使用锁保护
        sm4_gcm = Sm4Gcm(key, iv, aad, 16, DO_ENCRYPT)
        ciphertext = sm4_gcm.update(plaintext)
        ciphertext += sm4_gcm.finish()
        return ciphertext

其他所有密码算法(SM2, SM3, SM4-CBC/CTR, SM9, ZUC 等)都是线程安全的,可以在多线程环境中直接使用。

修改代码

  1. 编辑 src/gmssl/_core.py 中的实现代码
  2. 如需更新公共 API,修改 src/gmssl/__init__.py
  3. 运行测试验证:uv run pytest tests/ -v
  4. 格式化代码:ruff format src/ tests/
  5. 检查代码:ruff check src/ tests/

发布到 PyPI

参考 https://packaging.python.org/distributing/

  1. 更新版本号:
    • src/gmssl/_core.py 中的 GMSSL_PYTHON_VERSION = "x.y.z"
    • pyproject.toml 中的 version = "x.y.z"
  2. 构建包:python3 -m build
  3. 发布到 PyPI:python3 -m twine upload dist/*

注意: 版本号必须在两个文件中保持同步。

开发手册

随机数生成器

函数rand_bytes实现随机数生成功能。

rand_bytes(size : int) -> bytes

输入参数size 是输出字节数组长度,返回值为size长度的随机字节数组。

通过rand_bytes方法生成的是具备密码安全性的随机数,可以用于密钥、IV或者其他随机数生成器的随机种子。

>>> import gmssl
>>> key = gmssl.rand_bytes(16)
>>> print(key.hex())

rand_bytes是通过调用操作系统的密码随机数生成器(如/dev/urandom)实现的。由于底层操作系统的限制,在一次调用rand_bytes时不要指定明显超过密钥长度的输出长度,例如参数size的值不要超过128,否则可能导致阻塞,或者产生错误和异常。如果应用需要大量的随机数据,不应使用rand_bytes,而是应该考虑其他伪随机数生成算法。

需要注意的是,rand_bytes的安全性依赖于底层的操作系统随机数生成器的安全性。在服务器、笔记本等主流硬件和Windows、Linux、Mac主流服务器、桌面操作系统环境上,当计算机已经启动并且经过一段时间的用户交互和网络通信后,rand_bytes可以输出高质量的随机数。但是在缺乏用户交互和网络通信的嵌入式设备中,rand_bytes返回的随机数可能存在随机性不足的问题,在这些特殊的环境中,开发者需要提前或在运行时检测rand_bytes是否能够提供具有充分的随机性。

SM3哈希

SM3密码杂凑函数可以将任意长度的输入数据计算为固定32字节长度的哈希值。

模块gmssl中包含如下SM3的常量

  • SM3_DIGEST_SIZE 即SM3哈希值的字节长度

Sm3实现了SM3功能,类Sm3的对象是由构造函数生成的

gmssl.Sm3()

对象sm3的方法:

  • sm3.update(data : bytes) 要哈希的消息是通过update方法输入的,输入data的数据类型是bytes类型,如果输入的数据是字符串,需要通过字符串的encode方法转换成bytes,否则无法生成正确的哈希值。
  • sm3.digest() -> bytes 在通过update输入完所有消息后,就可以通过digest方法获得输出的哈希值,输出的结果类型为bytes类型,长度为SM3_DIGEST_SIZE
  • sm3.reset() 在SM3对象完成一个消息的哈希后,可以通过reset方法重置对象状态,效果等同于构造函数,重置后可以通过updatedigest计算新一个消息的哈希值。reset方法使得应用可以只创建一个Sm3的对象,计算任意数量的哈希值。

下面的例子展示了如何通过类Sm3计算字符串的SM3哈希值。

>>> from gmssl import Sm3
>>> sm3 = Sm3()
>>> sm3.update(b'abc')
>>> dgst = sm3.digest()
>>> print(dgst.hex())
'66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0'
$ python examples/sm3.py

打印出的66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0就是字符串abc的哈希值。字符串abc的哈希值也是SM3标准文本中给出的第一个测试数据,通过对比标准文本可以确定这个哈希值是正确的。

也可以通过gmssl命令行来验证Sm3类的计算是正确的。

$ echo -n abc | gmssl sm3
66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0

可以看到输出的结果是一样。

注意,如果将字符串abc写入到文本文件中,文本编辑器通常会在文本结尾处增加格外的结束符,如0x0a字符,那么计算出的哈希值将不是上面的结果,比如可能是12d4e804e1fcfdc181ed383aa07ba76cc69d8aedcbb7742d6e28ff4fb7776c34。如果命令echo不使用-n的参数,也会出现同样的错误。这是很多开发者在初次进行哈希函数开发时容易遇到的错误,哈希函数的安全性质保证,即使输入的消息只差一个比特,那么输出的哈希值也完全不同。

如果需要哈希的数据来自于网络或者文件,那么应用可能需要多次读取才能获得全部的数据。在通过Sm3计算哈希值时,应用不需要通过保存一个缓冲区来保存全部的数据,而是可以通过多次调用update方法,将数据输入给Sm3对象,在数据全都输入完之后,最后调用digest方法得到全部数据的SM3哈希值。下面的代码片段展示了这一用法。

>>> from gmssl import Sm3
>>> sm3 = Sm3()
>>> sm3.update(b"Hello ")
>>> sm3.update(b"world!")
>>> dgst = sm3.digest()

这个例子中两次调用了update方法,效果等同于

sm3.update(b"Hello world!");

注意,SM3算法也支持生成空数据的哈希值,因此下面的代码片段也是合法的。

>>> from gmssl import Sm3
>>> sm3 = Sm3()
>>> dgst = sm3.digest()

GmSSL-Python其他类的update方法通常也都提供了这种形式的接口。在输入完所有的数据之后,通过调用digest方法就可以获得所有输入数据的SM3哈希值了。digest方法输出的是长度为SM3_DIGEST_SIZE字节(即32字节)的二进制哈希值。

如果应用要计算多组数据的不同SM3哈希值,可以通过reset方法重置Sm3对象的状态,然后可以再次调用updatedigest方法计算新一组数据的哈希值。这样只需要一个Sm3对象就可以完成多组哈希值的计算。

>>> from gmssl import Sm3
>>> sm3 = Sm3()
>>> sm3.update(b"abc")
>>> dgst1 = sm3.digest()
>>>
>>> sm3.reset()
>>> sm3.update(b"Hello ")
>>> sm3.update(b"world!")
>>> dgst2 = sm3.digest()

GmSSL-Python的部分其他类也提供了reset方法。

HMAC-SM3消息认证码

HMAC-SM3是基于SM3密码杂凑算法的消息认证码(MAC)算法,消息认证码算法可以看作带密钥的哈希函数,主要用于保护消息不受篡改。通信双方需要事先协商出一个密钥,比如32字节的随机字节序列,数据的发送方用这个密钥对消息计算MAC值,并且把MAC值附在消息后面。消息的接收方在收到消息后,用相同的密钥计算消息的MAC值,并且和发送消息附带的MAC值做对比,如果一致说明消息没有被篡改,如果不一致,说明消息被篡改了。

模块gmssl中包含如下Sm3Hmac的常量:

  • SM3_HMAC_MIN_KEY_SIZE
  • SM3_HMAC_MAX_KEY_SIZE
  • SM3_HMAC_SIZE HMAC-SM3密钥长度,与SM3哈希值的长度相等

Sm3Hmac类实现了基于SM3的HMAC消息认证码算法,类Sm3Hmac的对象是由构造函数生成的。

gmssl.Sm3Hmac(key)

对象Sm3Hmac的方法:

  • Sm3Hmac.update(data : bytes)
  • Sm3Hmac.generate_mac() -> bytes
  • Sm3Hmac.reset()

HMAC-SM3算法可以看作是带密钥的SM3算法,因此在生成Sm3Hmac对象时需要传入一个密钥key作为输入参数。虽然HMAC-SM3在算法和实现上对密钥长度没有限制,但是出于安全性、效率等方面的考虑,HMAC-SM3算法的密钥长度建议采用32字节(等同于SM3哈希值的长度),不应少于16字节,采用比32字节更长的密钥长度会增加计算开销而不会增加安全性。

下面的例子显示了如何用HMAC-SM3生成消息abc的MAC值。

>>> from gmssl import Sm3Hmac, rand_bytes
>>> from gmssl import SM3_HMAC_MIN_KEY_SIZE
>>>
>>> key = rand_bytes(SM3_HMAC_MIN_KEY_SIZE)
>>> sm3_hmac = Sm3Hmac(key)
>>> sm3_hmac.update(b'abc')
>>> mac = sm3_hmac.generate_mac()
>>> print(mac.hex())

Sm3Hmac也通过update方法来提供输入消息,应用可以多次调用update

应用在通过update完成数据输入后,调用generate_mac可以获得消息认证码。

基于口令的密钥导出函数PBKDF2

常用软件如Word、PDF、WinRAR等支持基于口令的文件加密,字符串形式的口令相对于随机的密钥字节序列对用户来说更容易记忆和输入,对用户更加友好。但是由于口令中存在的信息熵远低于随机的二进制密钥,直接将口令字符串作为密钥,甚至无法抵御来自个人计算机的暴力破解攻击。一种典型的错误用法是直接用哈希函数计算口令的哈希值,将看起来随机的哈希值作为密钥使用。但是由于口令的空间相对较小,攻击者仍然可以尝试所有可能口令的哈希值,对于暴力破解来说,破解口令的哈希值和原始口令,在攻击难度上没有太大差别。

安全和规范的做法是采用一个基于口令的密钥导出函数(Password-Based Key Derivation Function, PBKDF)从口令中导出密钥。通过PBKDF导出密钥并不会降低攻击者在暴力破解时尝试的口令数量,但是可以防止攻击者通过查预计算表的方式来加速破解,并且可以大大增加攻击者尝试每一个可能口令的计算时间。PBKDF2是安全的并且使用广泛的PBKDF算法标准之一,算法采用哈希函数作为将口令映射为密钥的主要部件,通过加入随机并且公开的盐值(Salt)来抵御预计算,通过增加多轮的循环计算来增加在线破解的难度,并且支持可变的导出密钥长度。

模块gmssl中包含如下Sm3Pbkdf2的常量

  • SM3_PBKDF2_MIN_ITER
  • SM3_PBKDF2_MAX_ITER
  • SM3_PBKDF2_MAX_SALT_SIZE
  • SM3_PBKDF2_DEFAULT_SALT_SIZE
  • SM3_PBKDF2_MAX_KEY_SIZE

函数Sm3Pbkdf2实现了基于SM3的PBKDF2算法。

sm3_pbkdf2(passwd, salt, iterator, keylen)

其中:

  • passwd用于导出密钥的用户口令。
  • salt是用于抵御与计算的盐值。这个值需要用随机生成(比如通过Random类),并且具有一定的长度。Salt值不需要保密,因此在口令加密数据时,可以直接将这个值附在密文前,传输给接收方。Salt值越长,抵御预计算攻击的效果就更好。例如当Salt为8字节(64比特)长的随机值时,攻击者预计算表就要扩大$2^{64}$倍。Sm3Pbkdf2提供一个推荐的Salt值长度SM3_PBKDF2_DEFAULT_SALT_SIZE常量,并且在实现上不支持超过SM3_PBKDF2_MAX_KEY_SIZE长度的Salt值。
  • iterator参数用于表示在导出密钥时调用SM3算法的循环次数,iterator值越大,暴力破解的难度越大,但是同时用户在调用这个函数时的开销也增大了。一般来说iterator值的应该选择在用户可接收延迟情况下的最大值,比如当iterator = 10000时,用户延迟为100毫秒,但是对于用户来说延迟感受不明显,但是对于暴力攻击者来说iterator = 10000意味着攻击的开销增加了大约1万倍。Sm3Pbkdf2通过SM3_PBKDF2_MIN_ITERSM3_PBKDF2_MAX_ITER两个常量给出了iterator值的范围,用户可以根据当前计算机的性能及用户对延迟的可感知度,在这个范围内选择合适的值。
  • keylen参数表示希望导出的密钥长度,这个长度不可超过常量SM3_PBKDF2_MAX_KEY_SIZE

下面的例子展示了如何从口令字符串导出一个密钥。

>>> from gmssl import sm3_pbkdf2, rand_bytes
>>> from gmssl import SM3_PBKDF2_DEFAULT_SALT_SIZE, SM3_PBKDF2_MIN_ITER
>>>
>>> passwd = "Password"
>>> salt = rand_bytes(SM3_PBKDF2_DEFAULT_SALT_SIZE)
>>> iterator = SM3_PBKDF2_MIN_ITER
>>> keylen = 32
>>> key = sm3_pbkdf2(passwd, salt, iterator, keylen)
>>> print(key.hex())

SM4分组密码

SM4算法是分组密码算法,其密钥长度为128比特(16字节),分组长度为128比特(16字节)。SM4算法每次只能加密或者解密一个固定16字节长度的分组,不支持加解密任意长度的消息。分组密码通常作为更高层密码方案的一个组成部分,不适合普通上层应用调用。如果应用需要保护数据和消息,那么应该优先选择采用SM4-GCM模式,或者为了兼容已有的系统,也可以使用SM4-CBC或SM4-CTR模式。

模块gmssl中包含如下SM4的常量

  • SM4_KEY_SIZE
  • SM4_BLOCK_SIZE

SM4类实现了基本的SM4分组密码算法,类SM4的对象是由构造函数生成的。

gmssl.Sm4(key, encrypt)

对象SM4的方法:

  • Sm4.encrypt(block : int) -> bytes

Sm4对象在创建时需要提供SM4_KEY_SIZE字节长度的密钥,以及一个布尔值DO_ENCRYPT表示是用于加密还是解密。方法encrypt根据创建时的选择进行加密或解密,每次调用encrypt只处理一个分组,即读入SM4_BLOCK_SIZE长度的输入。

下面的例子展示SM4分组加密

>>> from gmssl import Sm4, rand_bytes
>>> from gmssl import SM4_KEY_SIZE, SM4_BLOCK_SIZE, DO_ENCRYPT, DO_DECRYPT
>>>
>>> key = rand_bytes(SM4_KEY_SIZE)
>>> plaintext = rand_bytes(SM4_BLOCK_SIZE)
>>>
>>> sm4_enc = Sm4(key, DO_ENCRYPT)
>>> ciphertext = sm4_enc.encrypt(plaintext)
>>>
>>> sm4_dec = Sm4(key, DO_DECRYPT)
>>> decrypted = sm4_dec.encrypt(ciphertext)
>>>
>>> assert decrypted == plaintext

多次调用Sm4的分组加密解密功能可以实现ECB模式,由于ECB模式在消息加密应用场景中并不安全,因此GmSSL中没有提供ECB模式。如果应用需要开发SM4的其他加密模式,也可以基于Sm4类来开发这些模式。

SM4-CBC加密模式

CBC模式是应用最广泛的分组密码加密模式之一,虽然目前不建议在新的应用中继续使用CBC默认,为了保证兼容性,应用仍然可能需要使用CBC模式。

模块gmssl中包含如下Sm4Cbc的常量:

  • SM4_CBC_IV_SIZE

Sm4Cbc类实现了基本的SM4-CBC分组密码算法,类Sm4Cbc的对象是由构造函数生成的。

gmssl.Sm4Cbc(key, iv, encrypt)

对象Sm4Cbc的方法:

  • Sm4Cbc.update(data : bytes)
  • Sm4Cbc.finish() -> bytes

Sm4Cbc类实现了SM4的带填充CBC模式,可以实现对任意长度数据的加密。由于需要对明文进行填充,因此Sm4Cbc输出的密文长度总是长于明文长度,并且密文的长度是整数个分组长度。

通过Sm4Cbc加密时,keyiv都必须为16字节长度。由于CBC模式中加密和解密的计算过程不同,因此必须通过布尔值DO_ENCRYPT指定是加密还是解密。

由于Sm4Cbc在加解密时维护了内部的缓冲区,因此update的输出长度可能不等于输入长度,应该保证输出缓冲区的长度至少比输入长度长一个SM4_CBC_IV_SIZE长度。

下面的例子显示了采用SM4-CBC加密和解密的过程。

>>> from gmssl import Sm4Cbc, rand_bytes
>>> from gmssl import SM4_KEY_SIZE, SM4_CBC_IV_SIZE, DO_ENCRYPT, DO_DECRYPT
>>>
>>> key = rand_bytes(SM4_KEY_SIZE)
>>> iv = rand_bytes(SM4_CBC_IV_SIZE)
>>> plaintext = b'This is a test message.'
>>>
>>> # Encrypt
>>> sm4_enc = Sm4Cbc(key, iv, DO_ENCRYPT)
>>> ciphertext = sm4_enc.update(plaintext)
>>> ciphertext += sm4_enc.finish()
>>>
>>> # Decrypt
>>> sm4_dec = Sm4Cbc(key, iv, DO_DECRYPT)
>>> decrypted = sm4_dec.update(ciphertext)
>>> decrypted += sm4_dec.finish()
>>>
>>> assert decrypted == plaintext

SM4-CTR加密模式

CTR加密模式可以加密任意长度的消息,和CBC模式不同,并不需要采用填充方案,因此SM4-CTR加密输出的密文长度和输入的明文等长。对于存储或传输带宽有限的应用场景,SM4-CTR相对SM4-CBC模式,密文不会增加额外长度。

模块gmssl中包含如下Sm4Ctr的常量:

  • SM4_CTR_IV_SIZE

Sm4Ctr类实现了基本的SM4-CBC分组密码算法,类Sm4Ctr的对象是由构造函数生成的。

gmssl.Sm4Ctr(key, iv)

对象Sm4Cbc的方法:

  • Sm4Ctr.update(data : bytes)
  • Sm4Ctr.finish() -> bytes

SM4-CTR在加密和解密时计算过程一样,因此在初始化时不需要指定加密或解密,因此没有Sm4Cbc中的DO_ENCRYPT参数。其他过程和SM4-CBC是一样的。

由于Sm4Ctr在加解密时维护了内部的缓冲区,因此update的输出长度可能不等于输入长度,应该保证输出缓冲区的长度至少比输入长度长一个SM4_BLOCK_SIZE长度。

注意 ,SM4-CBC和SM4-CTR模式都不能保证消息的完整性,在使用这两个模式时,应用还需要生成一个独立的HMAC-SM3密钥,并且生成密文的MAC值。

SM4-GCM认证加密模式

Sm4Gcm 提供了两种 API 模式:

推荐用法:无状态 API (线程安全)

对于大多数场景,推荐使用 encryptdecrypt 类方法。它们是无状态的,并且可以安全地在多线程环境中使用。

>>> from gmssl import Sm4Gcm, rand_bytes, NativeError
>>> from gmssl import SM4_KEY_SIZE, SM4_GCM_DEFAULT_IV_SIZE
>>>
>>> key = rand_bytes(SM4_KEY_SIZE)
>>> iv = rand_bytes(SM4_GCM_DEFAULT_IV_SIZE)
>>> aad = b'Additional auth-data'
>>> plaintext = b'This is a test message.'
>>>
>>> # 一行代码完成加密
>>> ciphertext = Sm4Gcm.encrypt(key, iv, aad, plaintext)
>>>
>>> # 一行代码完成解密和验证
>>> try:
...     decrypted = Sm4Gcm.decrypt(key, iv, aad, ciphertext)
...     assert decrypted == plaintext
... except NativeError:
...     print("Authentication failed!")

高级用法:流式 API (非线程安全)

对于需要处理大数据流的场景,可以创建 Sm4Gcm 实例并使用 updatefinish 方法。

⚠️ 重要: 这种流式处理的实例不是线程安全的。如果需要在多线程环境中使用同一个实例,您必须在外部使用 threading.Lock 来保护从 __init__finish 的整个操作序列。

>>> # 伪代码示例
>>> sm4_enc = Sm4Gcm(key, iv, aad, taglen, DO_ENCRYPT)
>>> for chunk in read_large_file_in_chunks():
...     encrypted_chunk = sm4_enc.update(chunk)
...     write_to_output(encrypted_chunk)
>>> last_chunk = sm4_enc.finish()
>>> write_to_output(last_chunk)

通过上面的例子可以看出SM4-GCM加密模式中可以通过指定了一个不需要加密的字段`aad`注意`aad`是不会在`update`中输出的由于GCM模式输出额外的完整性标签因此`update``finish`输出的总密文长度会比总的输入明文长度多`taglen`个字节### Zuc序列密码

祖冲之密码算法(ZU Cipher, ZUC)是一种序列密码密钥和IV长度均为16字节作为序列密码ZUC可以加密可变长度的输入数据并且输出的密文数据长度和输入数据等长因此适合不允许密文膨胀的应用场景在国密算法体系中ZUC算法的设计晚于SM4在32位通用处理器上通常比SM4-CBC明显要快在安全性方面不建议在一组密钥和IV的情况下用ZUC算法加密大量的数据比如GB级或TB级),避免序列密码超长输出时安全性降低另外ZUC算法本身并不支持数据的完整性保护因此在采用ZUC算法加密应用数据时应考虑配合HMAC-SM3提供完整性保护ZUC的标准中还包括针对移动通信底层数据报文加密的128-EEA3方案和用于消息完整性保护的128-EIA3算法目前GmSSL-Python中不支持这两个算法模块`gmssl`中包含如下Sm4Gcm的常量* `ZUC_KEY_SIZE`
* `ZUC_IV_SIZE`

`Zuc`类实现了基本的Zuc序列密码算法`Zuc`的对象是由构造函数生成的

gmssl.Zuc(key, iv)


对象Sm4Cbc的方法:

* `Zuc.update(data : bytes)`
* `Zuc.finish() -> bytes`

`Zuc`类的接口说明如下:

- 序列密码通过生成密钥序列和输入数据进行异或操作的方式来加密或解密,因此序列密码的加密和解密的过程一致,因此创建`Zuc`对象时不需要格外的参数表明加密还是解密。
- 由于CTR模式实际上是以分组密码实现了序列密码的能力,因此可以发现`Zuc`和`Sm4Cbc`的接口是完全一致的。
- ZUC算法内部实现是以32比特字(4字节)为单位进行处理,因此`Zuc`实现加解密过程中也有内部的状态缓冲区,因此`update`的输出长度可能和输入长度不一致,调用方应该保证输出缓冲区长度比输入长度长`BLOCK_SIZE`个字节。注意,`BLOCK_SIZE`的实际值在未来也有可能会变化。

下面的例子展示了`Zuc`的加密和解密过程。

```python
>>> from gmssl import Zuc, rand_bytes
>>> from gmssl import ZUC_KEY_SIZE, ZUC_IV_SIZE
>>>
>>> key = rand_bytes(ZUC_KEY_SIZE)
>>> iv = rand_bytes(ZUC_IV_SIZE)
>>> plaintext = b'This is a test message.'
>>>
>>> # Encrypt
>>> zuc_enc = Zuc(key, iv)
>>> ciphertext = zuc_enc.update(plaintext)
>>> ciphertext += zuc_enc.finish()
>>>
>>> # Decrypt
>>> zuc_dec = Zuc(key, iv)
>>> decrypted = zuc_dec.update(ciphertext)
>>> decrypted += zuc_dec.finish()
>>>
>>> assert decrypted == plaintext

SM2

SM2是国密标准中的椭圆曲线公钥密码,包含数字签名算法和公钥加密算法。SM2相关的功能由类Sm2KeySm2Signature实现,其中Sm2Key实现了SM2密钥对的生成、基础的加密和签名方案,Sm2Signature类实现了对任意长度消息签名的签名方案。

模块gmssl中包含如下Sm2Key的常量:

  • SM2_DEFAULT_ID
  • SM2_MAX_SIGNATURE_SIZE
  • SM2_MIN_PLAINTEXT_SIZE
  • SM2_MAX_PLAINTEXT_SIZE
  • SM2_MIN_CIPHERTEXT_SIZE
  • SM2_MAX_CIPHERTEXT_SIZE

Sm2Key类实现了基本的SM4-CBC分组密码算法,类Sm2Key的对象是由构造函数生成的。

gmssl.Sm2Key()

对象Sm2Key的方法:

  • Sm2Key.generate_key()
  • Sm2Key.compute_z()
  • Sm2Key.export_encrypted_private_key_info_pem()
  • Sm2Key.import_encrypted_private_key_info_pem()
  • Sm2Key.export_public_key_info_pem()
  • Sm2Key.import_public_key_info_pem()
  • Sm2Key.sign()
  • Sm2Key.verify()
  • Sm2Key.encrypt()
  • Sm2Key.decrypt()

需要注意的是,通过构造函数生成的新Sm2Key对象是一个空白的对象,可以通过generate_key方法生成一个新的密钥对,或者通过导入函数从外部导入密钥。Sm2Key一共提供了2个不同的导入方法:

  • import_encrypted_private_key_info_pem 从加密的PEM文件中导入SM2私钥,因此调用时需要提供PEM文件的路径和解密的口令(Password)。
  • import_public_key_info_pem从PEM文件中导入SM2公钥,只需要提供文件的路径,不需要提供口令。

上面2个导入函数也都有对应的导出函数。从PEM文件中导入导出公钥私钥和gmssl命令行工具的默认密钥格式一致,并且在处理私钥时安全性更高。因此建议在默认情况下,在导入导出私钥时默认采用加密的PEM文件格式。

下面的代码片段展示了Sm2Key密钥对和导出为加密的PEM私钥文件:

>>> from gmssl import Sm2Key
>>>
>>> # Generate key pair
>>> sm2_key = Sm2Key()
>>> sm2_key.generate_key()
>>>
>>> # Export and import encrypted private key
>>> sm2_key.export_encrypted_private_key_info_pem('sm2.pem', 'password')
>>> private_key = Sm2Key()
>>> private_key.import_encrypted_private_key_info_pem('sm2.pem', 'password')
>>>
>>> # Export and import public key
>>> sm2_key.export_public_key_info_pem('sm2pub.pem')
>>> public_key = Sm2Key()
>>> public_key.import_public_key_info_pem('sm2pub.pem')

由于公钥文件是不加密的,因此这个公钥可以被支持SM2的第三方工具、库打开和访问。

Sm2Key类除了generate_key方法之外,提供了compute_zsignverifyencryptdecrypt这几个密码计算相关的方法。

其中compute_z是由公钥和用户的字符串ID值计算出一个称为“Z值”的哈希值,用于对消息的签名。由于Sm2Signature类中提供了SM2消息签名的完整功能,因此这个compute_z方法只是用于实验验证。

>>> from gmssl import SM2_DEFAULT_ID
>>>
>>> z = public_key.compute_z(SM2_DEFAULT_ID)
>>> print(z.hex())

Sm2Keysignverify方法实现了SM2签名的底层功能,这两个方法不支持对数据或消息的签名,只能实现对SM3哈希值的签名和验证,并没有实现SM2签名的完整功能。应用需要保证调用时提供的dgst参数的字节序列长度为32。只有密码协议的底层开发者才需要调用compute_zsignverify这几个底层方法。

>>> from gmssl import Sm3
>>>
>>> sm3 = Sm3()
>>> sm3.update(b'This is a test message.')
>>> dgst = sm3.digest()
>>>
>>> sig = private_key.sign(dgst)
>>> ret = public_key.verify(dgst, sig)
>>> assert ret is True

Sm2Keyencryptdecrypt方法实现了SM2加密和解密功能。注意,虽然SM2标准中没有限制加密消息的长度,但是公钥加密应该主要用于加密较短的对称密钥、主密钥等密钥数据,因此GmSSL库中限制了SM2加密消息的最大长度。应用在调用encrypt时,需要保证输入的明文长度不超过SM2_MAX_PLAINTEXT_SIZE 的限制。如果需要加密引用层的消息,应该首先生成对称密钥,用SM4-GCM加密消息,再用SM2加密对称密钥。

>>> from gmssl import rand_bytes
>>>
>>> plaintext = rand_bytes(32)
>>> ciphertext = public_key.encrypt(plaintext)
>>> decrypted = private_key.decrypt(ciphertext)
>>>
>>> assert decrypted == plaintext

Sm2Signatue提供了对任意长消息的签名、验签功能。

模块gmssl中包含如下Sm2Signatue的常量:

  • DO_ENCRYPT = True
  • DO_DECRYPT = False
  • DO_SIGN = True
  • DO_VERIFY = False

Sm2Signatue类实现了基本的SM4-CBC分组密码算法,类Sm2Signatue的对象是由构造函数生成的。

gmssl.Sm2Signatue(sm2_key, signer_id = SM2_DEFAULT_ID, sign = DO_SIGN)

对象Sm2Signatue的方法:

  • Sm2Signatue.update()
  • Sm2Signatue.sign()
  • Sm2Signatue.verify()

在生成Sm2Signature对象时,不仅需要提供Sm2Key,还需要提供签名方的字符串ID,以满足SM2签名的标准。如果提供的Sm2Key来自于导入的公钥,那么这个Sm2Signature对象只能进行签名验证操作,即在构造时DO_SIGN = False,并且只能调用verify方法,不能调用sign方法。

>>> from gmssl import Sm2Signature, Sm2Key, SM2_DEFAULT_ID, DO_SIGN, DO_VERIFY
>>>
>>> # Assume private_key and public_key are loaded Sm2Key objects
>>>
>>> # Sign
>>> signer = Sm2Signature(private_key, SM2_DEFAULT_ID, DO_SIGN)
>>> signer.update(b'This is a test message.')
>>> signature = signer.sign()
>>>
>>> # Verify
>>> verifier = Sm2Signature(public_key, SM2_DEFAULT_ID, DO_VERIFY)
>>> verifier.update(b'This is a test message.')
>>> ret = verifier.verify(signature)
>>>
>>> assert ret is True

不管是Sm2Keysign还是Sm2Signaturesign方法输出的都是DER编码的签名值。这个签名值的第一个字节总是0x30,并且长度是可变的,常见的长度包括70字节、71字节、72字节,也可能短于70字节。一些SM2的实现不能输出DER编码的签名,只能输出固定64字节长度的签名值。可以通过签名值的长度以及首字节的值来判断SM2签名值的格式。

SM2数字证书

Sm2Certificate实现了SM2证书的导入、导出、解析和验证等功能。这里的“SM2证书”含义和“RSA证书”类似,是指证书中的公钥字段是SM2公钥,证书中签名字段是SM2签名,证书格式就是标准的X.509v3证书。由于GmSSL库目前只支持SM2签名算法,不支持ECDSA、RSA、DSA等签名算法,因此Sm2Certificate类无法支持其他公钥类型的证书。注意,有一种不常见的情况,一个证书可以公钥是SM2公钥而数字签名是RSA签名,这种证书可能是采用RSA公钥的CA中心对SM2证书请求签发而产生的,由于目前GmSSL不支持SM2之外的签名算法,因此Sm2Certificate不支持此类证书。

Sm2Certificate只支持SM2证书的解析和验证等功能,不支持SM2证书的签发和生成,如果应用需要实现证书申请(即生成CSR文件)或者自建CA签发证书功能,那么可以通过GmSSL库或者gmssl命令行工具实现,GmSSL-Python目前不考虑支持证书签发、生成的相关功能。

模块gmssl中包含如下Sm2Certificate的常量:

  • ZUC_KEY_SIZE
  • ZUC_IV_SIZE

Sm2Certificate的方法:

  • Sm2Certificate.import_pem()
  • Sm2Certificate.get_raw()
  • Sm2Certificate.export_pem()
  • Sm2Certificate.get_serial_number()
  • Sm2Certificate.get_issuer()
  • Sm2Certificate.get_subject()
  • Sm2Certificate.get_subject_public_key()
  • Sm2Certificate.get_validity()
  • Sm2Certificate.verify_by_ca_certificate()

新生成的Sm2Certificate对象中的证书数据为空,必须通过导入证书数据才能实现真正的初始化。证书有很多种不同格式的编码,如二进制DER编码的crt文件或者文本PEM编码的cer文件或者pem文件,有的证书也会把二进制的证书数据编码为一串连续的十六进制字符串,也有的CA会把多个证书构成的证书链封装在一个PKCS#7格式的密码消息中,而这个密码消息可能是二进制的,也可能是PEM编码的。

在这些格式中最常用的格式是本文的PEM格式,这也是Sm2Certificate类默认支持的证书格式。下面这个例子中就是一个证书的PEM文件内容,可以看到内容是由文本构成的,并且总是以-----BEGIN CERTIFICATE-----一行作为开头,以-----END CERTIFICATE-----一行作为结尾。PEM格式的好处是很容易用文本编辑器打开来,容易作为文本被复制、传输,一个文本文件中可以依次写入多个证书,从而在一个文件中包含多个证书或证书链。因此PEM格式也是CA签发生成证书使用的最主流的格式。由于PEM文件中头尾之间的文本就是证书二进制DER数据的BASE64编码,因此PEM文件也很容易和二进制证书进行手动或自动的互相转换。

-----BEGIN CERTIFICATE-----
MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG
EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw
...
pDoiVhsLwg==
-----END CERTIFICATE-----

通过gmssl certparse命令可以打印这个证书的内容:

$ gmssl certparse -in ROOTCA.pem

可以看到一个证书的主要内容是包含证书持有者信息的tbsCertificate字段,以及权威机构对tbsCertificate字段的签名算法signatureAlgorithm和签名值signatureValue。因为这个证书是SM2证书,因此其中的签名算法是sm2sign-with-sm3,签名值是0x30开头的DER编码的可变长度签名值。

证书中持有者信息包含如下字段:

  • 证书格式的版本号 version,目前版本号应该是第3版,即v3
  • 证书的序列号 serialNumber,早期证书中的序列号是一个递增的整数,但是近年来的证书必须是随机值。、
  • 证书的签名算法 signature,这个字段的值必须和最后的signatureAlgorithm保持一致。
  • 证书签发机构的名字 issuer,通常是一个CA中心,issuer的内容是由多个Key-Value格式的多个字段组合而成,其中的Key包括国家countryName、省stateOrProvinceName、城市localityName、组织organizationName、组织内单位organizationUnitName、常用名commonName等,其中commonName应该是CA机构的名字。
  • 证书的有效期 validity,有效期是由起始时间notBefore和终止时间notAfter两个时间构成的,如果当前时间早于notBefore,说明证书还没有启用,如果当前时间晚于notAfter,说明证书已经过期作废。
  • 证书持有者(证书主体)的名字 subject,这个字段的数据类型和issuer是一样的,一般对于网站服务器证书来说,subject的commonName应该是服务器的域名。
  • 证书持有者的公钥信息subjectPulbicKeyInfo,对于SM2证书来说,公钥算法必须是ecPublicKey并且曲线必须是sm2p256v1,公钥的值是一个编码的椭圆曲线点,这个值总是以0x04开头,后跟总共64字节的点的X、Y坐标。
  • 证书中通常还有多个扩展,其中有的扩展是关键的(critical)扩展,有些则不重要,只是提供了参考信息,这里介绍两个比较重要的扩展:
    • BasicConstraints (2.5.29.19) 扩展,这个扩展标明证书是权威机构的CA证书(比如北京市CA中心)还是普通用户的证书(比如某个网站的证书),如果一个证书中没有包含这个扩展,或者扩展中的cA: true字段不存在,那么这个证书不能作为CA证书使用。
    • KeyUsage (2.5.29.15) 扩展,这个扩展表明证书持有者公钥的用途,类似于驾驶证中的A照、B照、C照等划分大客车、大货车、小客车准驾车型,密钥用途表明证书是否可以签名、加密、签发证书等用途。如果一个数字签名附带的证书中有KeyUsage扩展并且扩展包含的密钥用途只有加密,没有签名,那么这个证书对于这个签名来说就是无效的。

Sm2Certificate类只支持第3版证书的解析,因此没有提供getVersion方法获取证书的版本号。GmSSL支持常用扩展的解析和验证,如果某个证书中有GmSSL不支持的非关键扩展,那么GmSSL会忽略这个扩展,如果存在GmSSL不识别或无法验证的关键性扩展,那么GmSSL在解析证书的时候会返回失败,因此如果Sm2Certificateimport_pem成功,说明证书的格式、内容是可以识别的并且是正确的。

拿他其他人提供的证书还必须验证该证书是否有效,首先需要检查证书的有效期。目前很多CA中心的策略是颁发有效期尽可能短的证书(比如3个月有效期),因此拿到的证书很有可能已经过期了。可以通过get_validity()方法获得有效期时间,判断当前时间点是否在有效期范围内。如果要验证过去某个时间点证书支持者的操作是否合法,那么应该检查那个时间点是否在证书的有效期范围内。

对证书最重要的验证之一是这个证书是否是由权威机构签发的。证书用户需要先通过get_issuer方法获得签发机构的名字,确认这个签发机构是否可信。例如,如果一个北京市政府机构的证书中的签发机构是一个商业性CA中心,那么这个证书的有效性就是存疑的。在确认CA中心名字(即整个issuer字段)无误之后,还需要通过Issuer字段从可信的渠道获得这个CA中心的证书,然后调用verify_by_ca_certificate方法,用获得的CA证书验证当前证书中的签名是否正确。在典型的应用中,开发者和软件发行方应该将所有可信的CA中心的证书硬编码到软件中,或者内置到软件或系统的证书库中,避免应用的用户需要手动添加、导入CA证书。

所有的私钥都有泄露的可能,安全性不佳的自建CA有被攻击者渗透的可能,商业性的小CA甚至有被收购、收买的可能,因此有效期范围内的证书也存在被作废的可能。检查证书是否作废主要是通过证书作废列表CRL文件检查,或者通过证书状态在线检查协议OCSP来在线查询。目前Sm2Certificate类没有支持证书作为查询的功能,开发者暂时可以通过GmSSL库或者gmssl命令行工具进行CRL的检查。

在完成所有证书检查之后,应用可以完全信任从证书中读取的持有者身份信息(subject)和支持有的公钥了,这两个信息分别通过get_subject()get_subject_public_key方法获得。

SM9基于身份的密码

SM9算法属于基于身份的密码。基于身份的密码是一种“高级”的公钥密码方案,在具备常规公钥密码加密、签名等密码功能的同时,基于身份的密码体系不需要CA中心和数字证书体系。SM9方案的基本原理是,可以由用户的唯一身份ID(如对方的电子邮件地址、域名或ID号等),从系统的全局主密钥中导出对应的私钥或公钥,导出密钥的正确性是由算法保证的,因此在进行加密、验签的时候,只需要获得解密方或签名方的ID即可,不再需要对方的数字证书了。因此如果应用面对的是一个内部的封闭环境,所有参与用户都是系统内用户,那么采用SM9方案而不是SM2证书和CA的方案,可以简化系统的开发、设计和使用,并降低后续CA体系的维护成本。

对应数字证书体系中的CA中心,SM9体系中也存在一个权威中心,用于生成全局的主密钥(MasterKey),并且为系统中的每个用户生成、分配用户的私钥。和SM2密钥对一样,SM9的主密钥也包含私钥和公钥,其中主公钥(PublicMasterKey)是可以导出并公开给系统中全体用户的。而SM9中用户的密钥对比较特殊,其中的公钥并不能从私钥中导出,SM9用户密钥需要包含用户的ID起到公钥的作用,在加密和验证签名等密码计算中,真正的用户公钥是在计算中,在运行时通过用户ID从主公钥中导出的。因此从应用的角度看,SM9中用户的公钥就是一个字符串形式的ID。

SM9算法体系中包括SM9加密、SM9签名和SM9密钥交换协议,GmSSL-Java中实现了SM9加密和SM9签名,没有实现SM9密钥交换。其中SM9加密功能包含Sm9EncMasterKey类和Sm9EncKey类,分别实现了SM9加密主密钥和SM9加密用户密钥,SM9签名功能包含Sm9SignMasterKey类、Sm9SignKey类和Sm9Signature类,分别实现了SM9签名主密钥、SM9签名用户密钥和SM9签名功能。

和SM2算法中相同的密钥对既可以用于加密又可以用于签名不同,SM9中加密、签名的主密钥、用户密钥的组成是完全不同的,因此GmSSL中分别实现为不同的类。SM9签名由于需要特殊的哈希过程,因此SM9用户签名私钥不提供直接签哈希值的底层签名功能实现,只能通过Sm9Signature实现对消息的签名、验证。

模块gmssl中包含如下Sm9EncMasterKey的常量:

  • SM9_MAX_ID_SIZE
  • SM9_MAX_PLAINTEXT_SIZE
  • SM9_MAX_CIPHERTEXT_SIZE

SM9加密主密钥由类Sm9EncMasterKey实现。

gmssl.Sm9EncMasterKey()

对象Sm9EncMasterKey的接口包括:

  • Sm9EncMasterKey.generate_master_key() 主密钥的生成
  • Sm9EncMasterKey.extract_key()用户私钥的生成
  • Sm9EncMasterKey.import_encrypted_master_key_info_pem() 主密钥的导入,注意Sm2Key的对应接口类似,这里主密钥都是以口令加密的方式导出到文件上的
  • Sm9EncMasterKey.export_encrypted_master_key_info_pem()主密钥的导出
  • Sm9EncMasterKey.export_public_master_key_pem()主公钥(主密钥的公钥部分)的导入
  • Sm9EncMasterKey.import_public_master_key_pem()主公钥(主密钥的公钥部分)的导出
  • Sm9EncMasterKey.encrypt()数据加密

这个类的用户包括两个不同角色,权威中心和用户。其中权威中心调用主密钥的生成、主密钥的导入导出、主公钥导出和用户私钥生成这几个接口,而用户调用主公钥导入和加密这两个接口。

Sm9EncKey对象是由Sm9SEncMasterKeyextract_key方法生成的。

gmssl.Sm9EncKey()

对象Sm9EncKey的方法:

  • Sm9EncKey.get_id()
  • Sm9EncKey.import_encrypted_private_key_info_pem()
  • Sm9EncKey.export_encrypted_private_key_info_pem()
  • Sm9EncKey.decrypt()

Sm9EncKey提供了解密、导入导出等接口,由于在SM9中用户密钥总是包含私钥的,因此导出的是经过口令加密的密钥。

下面的例子中给出了SM9加密方案的主密钥生成、用户密钥导出、加密、解密的整个过程。

>>> from gmssl import Sm9EncMasterKey, rand_bytes
>>> from gmssl import SM4_KEY_SIZE, SM3_HMAC_MIN_KEY_SIZE
>>>
>>> # Master key generation
>>> master_key = Sm9EncMasterKey()
>>> master_key.generate_master_key()
>>>
>>> master_key.export_encrypted_master_key_info_pem('enc_msk.pem', 'password')
>>> master_key.export_public_master_key_pem('enc_mpk.pem')
>>>
>>> # Encrypt
>>> master_pub = Sm9EncMasterKey()
>>> master_pub.import_public_master_key_pem('enc_mpk.pem')
>>>
>>> plaintext = rand_bytes(SM4_KEY_SIZE + SM3_HMAC_MIN_KEY_SIZE)
>>> receiver_id = 'Alice'
>>> ciphertext = master_pub.encrypt(plaintext, receiver_id)
>>>
>>> # Decrypt
>>> master = Sm9EncMasterKey()
>>> master.import_encrypted_master_key_info_pem('enc_msk.pem', 'password')
>>>
>>> receiver_key = master.extract_key(receiver_id)
>>> decrypted = receiver_key.decrypt(ciphertext)
>>>
>>> assert decrypted == plaintext

SM9签名功能由Sm9SignMasterKeySm9SignKeySm9Signature几个类实现,前两者在接口上和SM9加密非常类似,只是这两个类不直接提供签名、验签的功能。

gmssl.Sm9SignMasterKey()
gmssl.Sm9SignKey(owner_id)

对象Sm9SignMasterKey的方法:

  • Sm9SignMasterKey.generate_master_key()
  • Sm9SignMasterKey.extract_key()
  • Sm9SignMasterKey.import_encrypted_master_key_info_pem()
  • Sm9SignMasterKey.export_encrypted_master_key_info_pem()
  • Sm9SignMasterKey.export_public_master_key_pem()
  • Sm9SignMasterKey.import_public_master_key_pem()

对象Sm9SignKey的方法:

  • Sm9SignKey.get_id()
  • Sm9SignKey.import_encrypted_private_key_info_pem()
  • Sm9SignKey.export_encrypted_private_key_info_pem()

Sm9Signature实现对数据的SM9签名和验证功能。SM9签名时需要提供Sm9SignKey类型的签名方私钥(其中包含签名者的ID),在验证签名时需要提供Sm9SignMasterKey格式的系统主公钥和签名方的ID。Sm9SignatureSm2Signature提供类似的updatesignverify接口,只是在验证的时候需要提供的不是公钥,而是系统的主公钥和签名方的ID。

gmssl.Sm9Signature(sign = DO_SIGN)

模块gmssl中包含如下Sm9Signature的常量:

  • SM9_SIGNATURE_SIZE

对象Sm9Signature的方法:

  • Sm9Signature.reset()
  • Sm9Signature.update()
  • Sm9Signature.sign()
  • Sm9Signature.verify()

下面的例子展示了SM9签名的主密钥生成、用户私钥生成、签名、验证的过程。

>>> from gmssl import Sm9SignMasterKey, Sm9Signature, DO_SIGN, DO_VERIFY
>>>
>>> # Master key generation
>>> master_key = Sm9SignMasterKey()
>>> master_key.generate_master_key()
>>>
>>> master_key.export_encrypted_master_key_info_pem('sign_msk.pem', 'password')
>>> master_key.export_public_master_key_pem('sign_mpk.pem')
>>>
>>> # Sign
>>> master = Sm9SignMasterKey()
>>> master.import_encrypted_master_key_info_pem('sign_msk.pem', 'password')
>>>
>>> signer_id = 'Alice'
>>> key = master.extract_key(signer_id)
>>> message = b"Message to be signed"
>>>
>>> sign = Sm9Signature(DO_SIGN)
>>> sign.update(message)
>>> sig = sign.sign(key)
>>>
>>> # Verify
>>> master_pub = Sm9SignMasterKey()
>>> master_pub.import_public_master_key_pem('sign_mpk.pem')
>>>
>>> verify = Sm9Signature(DO_VERIFY)
>>> verify.update(message)
>>> ret = verify.verify(sig, master_pub, signer_id)
>>>
>>> assert ret is True

许可证

GmSSL-Python

本项目采用 Apache License 2.0 许可证。详见 LICENSE 文件。

打包的 GmSSL 库

从 v2.2.2 开始,gmssl-python 包含了预编译的 GmSSL 动态库(位于 src/gmssl/_libs/)。

GmSSL 库信息:

  • 项目: GmSSL
  • 版本: 3.1.1
  • 许可证: Apache License 2.0
  • 版权: Copyright 2014-2023 The GmSSL Project

根据 Apache License 2.0 的条款,我们被允许重新分发 GmSSL 库的二进制文件。GmSSL 库与本项目采用相同的许可证,因此用户可以自由使用、修改和分发。

重要说明:

  • 打包的 GmSSL 库仅用于便利性,用户可以选择使用系统安装的 GmSSL 库
  • 系统库(如果存在)将优先于打包的库被加载
  • 如需最新版本或自定义编译的 GmSSL,请参考上述"手动安装 GmSSL"部分

About

Python binding to the GmSSL library

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages

  • Python 100.0%
Morty Proxy This is a proxified and sanitized view of the page, visit original site.