使用 Python 腳本執(zhí)行國(guó)密 sm2 加解密

一、場(chǎng)景

工作中的一個(gè)場(chǎng)景:Go 需要對(duì)信息加解密,但是研究了 GmSSL Go API 文檔之后,發(fā)現(xiàn)是依賴于 CGO 的,同事配了半天環(huán)境沒配成功。于是換了一個(gè)方法,選擇 Go 調(diào) Python 腳本執(zhí)行加解密。之前我是寫過 Python 對(duì)信息使用國(guó)密 sm2 算法進(jìn)行加解密的,因此比較方便。

二、代碼

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()  # 將二進(jìn)制bytes通過base64編碼
    return encode_info


# 解密
def decrypt(info):
    decode_info = b64decode(info.encode())  # 通過base64解碼成二進(jìn)制bytes
    decode_info = sm2_crypt.decrypt(info).decode(encoding="utf-8")
    return decode_info


if __name__ == "__main__":
    action = sys.argv[1]  # 取命令中的加解密動(dòng)作
    contact_info = sys.argv[2]  # 取命令中需要加解密的內(nèi)容
    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 執(zhí)行命令:

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

輸出加密后的內(nèi)容:

H24OlVZgSTtevCW138O+C5PlZp8OiD920JnpVr7r9ndkGBWFZUVDD48iIVrZRnamgosV5910m9k0438WpIyi0guEt8F5inG7Y5A51whRfdPZ+qdvWVQxI857CBEzkb3h1bMp1ETQ

再執(zhí)行解密:

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

輸出解密后的內(nèi)容:

123456

可以正確加密解密。

image

解釋一下執(zhí)行腳本的參數(shù),可以參考這篇教程

  • sys.argv[0] 表示腳本名;
  • sys.argv[1] 表示要調(diào)用的加解密動(dòng)作;
  • sys.argv[2] 表示要加解密的內(nèi)容。

如上圖所示,命令行參數(shù)分別對(duì)應(yīng):

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

三、補(bǔ)充

展開講講為什么里面需要轉(zhuǎn) base64。因?yàn)榻?jīng)過實(shí)際測(cè)試,發(fā)現(xiàn) sm2 加解密的是 bytes 類型,直接進(jìn)行加密沒問題,但是單獨(dú)解密并不成功:

image

按住 Ctrl,再鼠標(biāo)點(diǎn)擊 decrypt 函數(shù),直接跳到源碼查看源碼:

image

發(fā)現(xiàn)源碼里會(huì)轉(zhuǎn)一下 hex 類型,但是報(bào)錯(cuò)提示:str 對(duì)象沒有 hex 屬性。

但是如果把解密寫在加密之后(即加完密立馬解密),發(fā)現(xiàn)就沒問題了。

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

可以成功進(jìn)行加解密。

但是現(xiàn)在需要的是通過 Go 調(diào) Python 腳本來執(zhí)行加解密,需要加解密可以分開(根據(jù)命令行傳入的參數(shù)判斷是加密還是解密),因此這個(gè)方法不行。經(jīng)過查閱一些資料,才有了最開始的那段代碼,通過 base64 來編碼解碼進(jìn)行加密解密。

還考慮到一個(gè)問題是:直接以加密后的二進(jìn)制存數(shù)據(jù)庫(kù),有可能會(huì)影響到 Go 那邊從數(shù)據(jù)庫(kù)讀數(shù)據(jù)。因?yàn)橐远M(jìn)制 bytes 存數(shù)據(jù)庫(kù),字段采用的是 BinaryField,但是 Go 不確定有這種類型的字段,可能需要自定義。考慮到項(xiàng)目比較趕,馬上就要提測(cè)了,沒有時(shí)間研究 Go 怎么自定義類型了,于是選擇了換成 CharField 類型存儲(chǔ)到數(shù)據(jù)庫(kù)。因此就是加密后的二進(jìn)制 bytes 通過 base64 編碼之后再存數(shù)據(jù)庫(kù)。解密就是從數(shù)據(jù)庫(kù)讀出數(shù)據(jù)再通過 base64 解碼,轉(zhuǎn)成二進(jìn)制 bytes 進(jìn)行解密。

還有一個(gè)問題就是:看到上述加密后的二進(jìn)制 bytes,發(fā)現(xiàn)有很多反斜杠 \,但是反斜杠是有轉(zhuǎn)義功能的,比如兩個(gè)反斜杠 \\ 表示的就是一個(gè)反斜杠 \(這里可以參考 C 語(yǔ)言基礎(chǔ)語(yǔ)法)。考慮到存儲(chǔ)讀取可能也會(huì)出現(xiàn)這種問題,因此最終采用 base64 進(jìn)行編解碼再存儲(chǔ)。

四、sm2 公私鑰生成代碼

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

每次生成的公私鑰都不一樣:

036e29c4ce1f17b5fd35c88e81793bc9de53f46b3766b779b297e062af958405
8d22779ef058a61365d8a427dfd4df5f661535cb515a05567436ab0bd7ff7803a9e31c90630d9221960e614a4228a0a5162bde4072ef3aa9a1ba6fe74d240577

五、參考

Python 命令行參數(shù)

Byte[ ]和Base64之間的轉(zhuǎn)換(加密)

支持國(guó)密SM2/SM3/SM4/SM9/ZUC/SSL的OpenSSL分支

GmSSL Go API 文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容