国密算法 SM3 消息摘要 杂凑算法 哈希函数 散列函数 python实现完整代码

11 篇文章 7 订阅
9 篇文章 5 订阅

依次分享完SM2和SM4的python实现代码,今天分享SM3。前两次见链接:

SM2:国密算法 SM2 公钥加密 非对称加密 数字签名 密钥协商 python实现完整代码_qq_43339242的博客-CSDN博客_sm2 密钥长度SM4:国密算法 SM4 对称加密 分组密码 python实现完整代码_qq_43339242的博客-CSDN博客 

SM3是一种密码杂凑算法,输出的杂凑值是256位(MD5是128位,SHA-1是160位),用于替换MD5/SHA-1等国际算法。实现SM3的python库主要是python-gmssl库和snowland-smxpysmx)库,二者都对SM2(仅公钥加解密和数字签名)、SM3、SM4进行了细致而优雅的实现。在耗时测试中,还引入了国际算法MD5和SHA-256作为对比,采用的是成熟高效的Crypto(PyCryptodome)库。 它们的来源如下:

1.GMSSL. https://github.com/duanhongyi/gmssl
2.snowland-smx. https://gitee.com/snowlandltd/ snowland-smx-python
3.PyCryptodome. https://www.pycryptodome.org

话不多说,老套路,先上能完整运行的SM3实现代码(运行不了是没装够依赖库):

from array import array
Tj_rl = array('L', ((0x79cc4519 << j | 0x79cc4519 >> 32-j) & 0xffffffff for j in range(16)))
Tj_rl.extend((0x7a879d8a << (j & 31) | 0x7a879d8a >> (32 - j & 31)) & 0xffffffff for j in range(16, 64))
V0 = array('L', [0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e])


def CF(V, B):
    W = array('L', B)
    for j in range(16, 68):
        X = W[j-16] ^ W[j-9] ^ (W[j-3] << 15 | W[j-3] >> 17) & 0xffffffff
        W.append((X ^ (X << 15 | X >> 17) ^ (X << 23 | X >> 9) ^ (W[j-13] << 7 | W[j-13] >> 25) ^ W[j-6]) & 0xffffffff)
    W_ = array('L', (W[j] ^ W[j+4] for j in range(64)))
    A, B, C, D, E, F, G, H = V
    for j in range(64):
        A_rl12 = A << 12 | A >> 20
        tmp = (A_rl12 + E + Tj_rl[j]) & 0xffffffff
        SS1 = (tmp << 7 | tmp >> 25)
        SS2 = SS1 ^ A_rl12
        if j & 0x30:  # 16 <= j
            FF, GG = A & B | A & C | B & C, E & F | ~E & G
        else:
            FF, GG = A ^ B ^ C, E ^ F ^ G
        TT1, TT2 = (FF + D + SS2 + W_[j]) & 0xffffffff, (GG + H + SS1 + W[j]) & 0xffffffff
        C, D, G, H = (B << 9 | B >> 23) & 0xffffffff, C, (F << 19 | F >> 13) & 0xffffffff, G
        A, B, E, F = TT1, A, (TT2 ^ (TT2 << 9 | TT2 >> 23) ^ (TT2 << 17 | TT2 >> 15)) & 0xffffffff, E
    return A ^ V[0], B ^ V[1], C ^ V[2], D ^ V[3], E ^ V[4], F ^ V[5], G ^ V[6], H ^ V[7]


def digest(data):
    # 填充
    pad_num = 64 - (len(data) + 1 & 0x3f)
    data += b'\x80' + (len(data) << 3).to_bytes(pad_num if pad_num >= 8 else pad_num + 64, 'big')
    V, B = V0, array('L', data)
    B.byteswap()
    # 迭代压缩
    for i in range(0, len(B), 16):
        V = CF(V, B[i:i+16])
    V = array('L', V)
    V.byteswap()
    return V.tobytes()


from Crypto.Hash import MD5, SHA3_256
from pysmx.SM3 import digest as SM3_pysmx
from gmssl.sm3 import sm3_hash
import time, os


def SM3_gmssl(data: bytes) -> bytes:
    return bytes.fromhex(sm3_hash([i for i in data]))


def SM3_my(data: bytes) -> bytes:
    return digest(data)


def sm3_compare_test():
    print('—————————————————————首次Hash测试—————————————————————')
    # 随机生成消息
    long_data = os.urandom(128)

    print('消息长度:%dB  单位:μs' % (len(long_data)))
    print('算法库名\t\t\t首次Hash\t\t再次Hash')
    # Crypto - MD5
    time_1 = time.perf_counter()
    MD5.new(long_data).digest()
    time_2 = time.perf_counter()
    MD5.new(long_data).digest()
    time_3 = time.perf_counter()
    print('Crypto-MD5\t\t%.1f\t\t%.1f' % ((time_2 - time_1) * 1000000, (time_3 - time_2) * 1000000))
    # Crypto - SHA256
    time_1 = time.perf_counter()
    SHA3_256.new(long_data).digest()
    time_2 = time.perf_counter()
    SHA3_256.new(long_data).digest()
    time_3 = time.perf_counter()
    print('Crypto-SHA256\t%.1f\t\t%.1f' % ((time_2 - time_1) * 1000000, (time_3 - time_2) * 1000000))
    # gmssl - SM3
    time_1 = time.perf_counter()
    SM3_gmssl(long_data)
    time_2 = time.perf_counter()
    hash2 = SM3_gmssl(long_data)
    time_3 = time.perf_counter()
    print('gmssl-SM3\t\t%.1f\t\t%.1f' % ((time_2 - time_1) * 1000000, (time_3 - time_2) * 1000000))
    # pysmx - SM3
    time_1 = time.perf_counter()
    SM3_pysmx(long_data)
    time_2 = time.perf_counter()
    hash1 = SM3_pysmx(long_data)
    time_3 = time.perf_counter()
    print('pysmx-SM3\t\t%.1f\t\t%.1f' % ((time_2 - time_1) * 1000000, (time_3 - time_2) * 1000000))
    assert hash1 == hash2
    # my - SM3
    time_1 = time.perf_counter()
    SM3_my(long_data)
    time_2 = time.perf_counter()
    hash2 = SM3_my(long_data)
    time_3 = time.perf_counter()
    print('my-SM3\t\t\t%.1f\t\t%.1f' % ((time_2 - time_1) * 1000000, (time_3 - time_2) * 1000000))
    assert hash1 == hash2

    print('\n—————————————————————连续Hash测试—————————————————————')
    test_num = 100  # 测试次数
    # 随机生成消息
    short_data = [os.urandom(28) for i in range(test_num)]  # 短消息列表
    long_data = [os.urandom(1128) for i in range(test_num)]  # 长消息列表
    hash_data = [b''] * test_num
    hash_data1 = [b''] * test_num
    hash_data2 = [b''] * test_num
    hash_data3 = [b''] * test_num
    hash_data4 = [b''] * test_num

    print('短消息长度:%dB  长消息长度:%dB  测试次数:%d  单位:ms' % (len(short_data[0]), len(long_data[0]), test_num))
    print('算法库名\t\t\t短消息Hash\t长消息Hash')

    # Crypto - MD5
    time_1 = time.perf_counter()
    for i in range(test_num):
        hash_data[i] = MD5.new(short_data[i]).digest()  # 短消息Hash
    time_2 = time.perf_counter()
    for i in range(test_num):
        hash_data[i] = MD5.new(long_data[i]).digest()  # 长消息Hash
    time_3 = time.perf_counter()
    print('Crypto-MD5\t\t%.2f\t\t%.2f' % ((time_2 - time_1) * 1000, (time_3 - time_2) * 1000))

    # Crypto - SHA256
    time_1 = time.perf_counter()
    for i in range(test_num):
        hash_data[i] = SHA3_256.new(short_data[i]).digest()  # 短消息Hash
    time_2 = time.perf_counter()
    for i in range(test_num):
        hash_data[i] = SHA3_256.new(long_data[i]).digest()  # 长消息Hash
    time_3 = time.perf_counter()
    print('Crypto-SHA256\t%.2f\t\t%.2f' % ((time_2 - time_1) * 1000, (time_3 - time_2) * 1000))

    # gmssl - SM3
    time_1 = time.perf_counter()
    for i in range(test_num):
        hash_data1[i] = SM3_gmssl(short_data[i])  # 短消息Hash
    time_2 = time.perf_counter()
    for i in range(test_num):
        hash_data2[i] = SM3_gmssl(long_data[i])  # 长消息Hash
    time_3 = time.perf_counter()
    print('gmssl-SM3\t\t%.2f\t\t%.2f' % ((time_2 - time_1) * 1000, (time_3 - time_2) * 1000))
    time_aim1 = time_3 - time_1

    # pysmx - SM3
    time_1 = time.perf_counter()
    for i in range(test_num):
        hash_data3[i] = SM3_pysmx(short_data[i])  # 短消息Hash
    time_2 = time.perf_counter()
    for i in range(test_num):
        hash_data4[i] = SM3_pysmx(long_data[i])  # 长消息Hash
    time_3 = time.perf_counter()
    print('pysmx-SM3\t\t%.2f\t\t%.2f' % ((time_2 - time_1) * 1000, (time_3 - time_2) * 1000))
    time_aim2 = time_3 - time_1
    assert hash_data1 == hash_data3 and hash_data2 == hash_data4

    # my - SM3
    time_1 = time.perf_counter()
    for i in range(test_num):
        hash_data1[i] = SM3_my(short_data[i])  # 短消息Hash
    time_2 = time.perf_counter()
    for i in range(test_num):
        hash_data2[i] = SM3_my(long_data[i])  # 长消息Hash
    time_3 = time.perf_counter()
    print('my-SM3\t\t\t%.2f\t\t%.2f' % ((time_2 - time_1) * 1000, (time_3 - time_2) * 1000))
    time_my = time_3 - time_1
    print('总耗时为pysmx的%.2f%%、gmssl的%.2f%%' % (time_my / time_aim2 * 100, time_my / time_aim1 * 100))
    assert hash_data1 == hash_data3 and hash_data2 == hash_data4


if __name__ == "__main__":
    sm3_compare_test()

SM3的核心代码只有前40行,后面是测试用的。核心函数是digest,输入输出均为bytes类型。不像其他库提供以字符串和16进制串作为输入的实现,我的代码只处理bytes,我认为调用者才最清楚如何将各种类型的源数据高效转换成bytes类型。

虽然代码是最简短的一次(相比SM2和SM4),可过程真是一言难尽。。。还是想吐槽一下/(ㄒoㄒ)/~~我用的密码学教材描述SM3算法的部分出了三个错误,搞得我不得不在一大堆运算和循环中,通过将中间值一步一步输出,与正确程序逐一对比,才发现可能是教材错了,费劲!好在调通了(๑•̀ㅂ•́)و✧

运行上面的代码,看一下性能对比测试结果:

连续Hash测试中,结果数值为总耗时,不是平均耗时。不同的计算机配置下结果可能有些许差别,我的计算机配置:

可见,调用机器代码的Crypto库依旧吊打我们这些倔强的纯python,和SM4的测试结果类似,头一次加载链接库比较慢,但从第二次开始就起飞了。我的实现比另两个国密算法库快了一点,主要是做了以下改进:

1.统一用array数组作为中间值,最大限度减少bytes、list、int之间的类型转换。

2.运算中经常用到"& 0xffffffff"确保值是32位,因为我们的计算机是64位,有时让中间值超过32位也无妨,仅在最后赋值时再进行"& 0xffffffff"运算,这样可减少运算量。但连续两次循环移位之间不能省略"& 0xffffffff",否则会得到错误结果。

3.将循环移位运算从pysmx的“一次模除、一次乘法、一次加法”调整为“两次移位、一次按位或”,位运算比乘除法快。

当然,尽管优化了一点,还是被机器代码轰成渣。下一步还是要用numba对代码进行改写,在分享SM4文章的末尾已经见识到了numba的威力,效率已跟Crypto库不相上下,不知道这一次能达到什么效果,拭目以待吧!写完了会分享。

自从去年阴差阳错地改进了SM2,到之前用python做基于国密算法的加密系统发现咱自己的SM4效率比国际成熟库差了数量级,又改进SM4,再到现在写完SM3,惊喜地发现自己实现的SM2、SM3、SM4已经构成了一个完整的体系,包括非对称密码、杂凑函数和分组密码,下步考虑把它们都做到跟国际成熟库(类似Crypto库)对等的效率,封装成库,供大家pip安装。毕竟用python的开发者有那么多而且越来越多,要是能解决python平台国密算法耗时太高的痛点,也算是功德一件!

现有的国密python库或多或少存在效率、易用性和实现不完整等方面的问题。当然,没有前辈的工作,就没有我的努力方向,更没有目前的成果。中学数学老师说过的话让我印象深刻:“先谈会不会,再谈好不好”。gmssl库和pysmx库首先解决了有没有的问题,然后再通过我们共同努力让它变得更好。再次向国密算法的设计者、国密算法库的开发者、国密算法的广大使用者以及高瞻远瞩的各级领导致以崇高的敬意!

正是大家的坚持,我们才能将网络信息安全一步一步掌握在自己手中。

  • 7
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值