使用 Python 脚本执行国密 sm2 加解密

一、场景

工作中的一个场景:Go 需要对信息加解密,但是研究了 GmSSL Go API 文档之后,发现是依赖于 CGO 的,同事配了半天环境没配成功。于是换了一个方法,选择 Go 调 Python 脚本执行加解密。之前我是写过 Python 对信息使用国密 sm2 算法进行加解密的,因此比较方便。

二、代码

import sys
from gmssl import sm2
from base64 import b64encode, b64decode
# sm2的公私钥
SM2_PRIVATE_KEY = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
SM2_PUBLIC_KEY = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'

sm2_crypt = sm2.CryptSM2(public_key=SM2_PUBLIC_KEY, private_key=SM2_PRIVATE_KEY)

# 加密
def encrypt(info):
    encode_info = sm2_crypt.encrypt(info.encode(encoding="utf-8"))
    encode_info = b64encode(encode_info).decode()  # 将二进制bytes通过base64编码
    return encode_info


# 解密
def decrypt(info):
    decode_info = b64decode(info.encode())  # 通过base64解码成二进制bytes
    decode_info = sm2_crypt.decrypt(info).decode(encoding="utf-8")
    return decode_info


if __name__ == "__main__":
    action = sys.argv[1]  # 取命令中的加解密动作
    contact_info = sys.argv[2]  # 取命令中需要加解密的内容
    if action == "encrypt":
        encrypted_contact_info = encrypt(contact_info)
        print(encrypted_contact_info)
    if action == "decrypt":
        decrypted_contact_info = decrypt(contact_info)
        print(decrypted_contact_info)

在 VSCode 的 Terminal 执行命令:

$ C:/Users/admin/anaconda3/envs/xxx/python.exe d:/Code/python/sm2_test.py encrypt 123456

输出加密后的内容:

H24OlVZgSTtevCW138O+C5PlZp8OiD920JnpVr7r9ndkGBWFZUVDD48iIVrZRnamgosV5910m9k0438WpIyi0guEt8F5inG7Y5A51whRfdPZ+qdvWVQxI857CBEzkb3h1bMp1ETQ

再执行解密:

$ C:/Users/admin/anaconda3/envs/xxx/python.exe d:/Code/python/sm2_test.py decrypt H24OlVZgSTtevCW138O+C5PlZp8OiD920JnpVr7r9ndkGBWFZUVDD48iIVrZRnamgosV5910m9k0438WpIyi0guEt8F5inG7Y5A51whRfdPZ+qdvWVQxI857CBEzkb3h1bMp1ETQ

输出解密后的内容:

123456

可以正确加密解密。

解释一下执行脚本的参数,可以参考这篇教程

  • sys.argv[0] 表示脚本名;
  • sys.argv[1] 表示要调用的加解密动作;
  • sys.argv[2] 表示要加解密的内容。

如上图所示,命令行参数分别对应:

  • sys.argv[0]: d:/Code/python/sm2_test.py
  • sys.argv[1]: encrypt
  • sys.argv[2]: 123456

三、补充

展开讲讲为什么里面需要转 base64。因为经过实际测试,发现 sm2 加解密的是 bytes 类型,直接进行加密没问题,但是单独解密并不成功:

按住 Ctrl,再鼠标点击 decrypt 函数,直接跳到源码查看源码:

发现源码里会转一下 hex 类型,但是报错提示:str 对象没有 hex 属性。

但是如果把解密写在加密之后(即加完密立马解密),发现就没问题了。

from gmssl import sm2
# sm2的公私钥
SM2_PRIVATE_KEY = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
SM2_PUBLIC_KEY = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'

sm2_crypt = sm2.CryptSM2(public_key=SM2_PUBLIC_KEY, private_key=SM2_PRIVATE_KEY)

# 加密
def encrypt(info):
    encode_info = sm2_crypt.encrypt(info.encode(encoding="utf-8"))
    return encode_info


# 解密
def decrypt(info):
    decode_info = sm2_crypt.decrypt(info).decode(encoding="utf-8")
    return decode_info


if __name__ == "__main__":
    info = "123456"
    encode_info = encrypt(info)
    print(encode_info)
    decode_info = decrypt(encode_info)
    print(decode_info)

输出:

b'\x9b\x19\xa8\xc6\xfb\x0b\xf9\xfc\xf15,E*\x9f\x12\xfd\xd6\xd3@\x82N.\x82|\xb2"Y\x86V\xce\x1d\x99\x0e\x8e\xa1\xf6\xfcP\x81x\xbf(:[\xb2UYq\xbc\x84\xe8\x93[\x01\t\xc1\xcf1(E\xcc\xf9{\xe2\x88\xbb(\x90t\xb2\xfa\xd7\xe9\r\x8b\x81\x98\xf9\x85z/\xd5;\x88U\x89Sc\xcfx\xa8\x84\xee,A\xb3\x9de\xa9\xe3\x8e\xd4'
123456

可以成功进行加解密。

但是现在需要的是通过 Go 调 Python 脚本来执行加解密,需要加解密可以分开(根据命令行传入的参数判断是加密还是解密),因此这个方法不行。经过查阅一些资料,才有了最开始的那段代码,通过 base64 来编码解码进行加密解密。

还考虑到一个问题是:直接以加密后的二进制存数据库,有可能会影响到 Go 那边从数据库读数据。因为以二进制 bytes 存数据库,字段采用的是 BinaryField,但是 Go 不确定有这种类型的字段,可能需要自定义。考虑到项目比较赶,马上就要提测了,没有时间研究 Go 怎么自定义类型了,于是选择了换成 CharField 类型存储到数据库。因此就是加密后的二进制 bytes 通过 base64 编码之后再存数据库。解密就是从数据库读出数据再通过 base64 解码,转成二进制 bytes 进行解密。

还有一个问题就是:看到上述加密后的二进制 bytes,发现有很多反斜杠 \,但是反斜杠是有转义功能的,比如两个反斜杠 \\ 表示的就是一个反斜杠 \(这里可以参考 C 语言基础语法)。考虑到存储读取可能也会出现这种问题,因此最终采用 base64 进行编解码再存储。

四、sm2 公私钥生成代码

from gmssl.utils import PrivateKey
priKey = PrivateKey()
pubKey = priKey.publicKey()
print(priKey.toString())
print(pubKey.toString(compressed = False))

每次生成的公私钥都不一样:

036e29c4ce1f17b5fd35c88e81793bc9de53f46b3766b779b297e062af958405
8d22779ef058a61365d8a427dfd4df5f661535cb515a05567436ab0bd7ff7803a9e31c90630d9221960e614a4228a0a5162bde4072ef3aa9a1ba6fe74d240577

五、参考

Python 命令行参数

Byte[ ]和Base64之间的转换(加密)

支持国密SM2/SM3/SM4/SM9/ZUC/SSL的OpenSSL分支

GmSSL Go API 文档

全部评论

相关推荐

12-06 01:10
已编辑
哈尔滨工程大学 Java
一面问的真细,二面不知为啥变双机位。9.29快手主站平时怎么学习 AI 的,国内外知名大模型,实习公司都用的什么大模型,怎么评估效果的java池化思想,线程池构造方法的核心参数,线程池中阻塞队列注意事项,submit方法参数和执行逻辑,shutdown和shutdownnow,核心线程允许过期吗threadlocal底层,为什么key是弱引用,key回收了再get或者set这个value会怎样aqs,如何保证公平性java代理java堆划分,新生代还有别的晋升老年代的情况吗,什么时候触发gc,gc失败抛什么异常,如何排查oom,导出dump命令redis数据结构,哪个底层是跳表,和其他数据结构对比布隆过滤器会出现大key问题吗,你咋实现的布隆过滤器你怎么实现redis分布式锁,可重入,续期聚簇索引非聚簇索引select语句会加锁吗,怎么实现的不加锁undolog redolog binlog怎么能让select加锁,update这个范围加的什么锁,update一条呢手撕简单01背包,接雨水10.10快手主站意图识别用的哪个大模型,走到意图和rag的比例,faq是点击的吗自然语言怎么识别的gap一年干啥了,转正怎么样没跟组里提意向吗,研究生研究方向是传统算法吗,会大模型微调吗注册场景为什么用布隆过滤器,原理分布式锁底层的key怎么拼的,value里是什么redis持久化zset底层mysql索引结构,一个表三个字段有主键唯一索引和没索引的字段会有几个b+树,聚簇索引非聚簇索引存的啥无手撕
点赞 评论 收藏
分享
孙艹肘:校招不给三方直接让实习我都去了,,主打一个在学校呆着也是闲着,不如出来实习一下
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务