国产环境为什么要推行国密算法

  • 自主可控,不用依赖于国外的技术,更符合国情
  • 安全性更高,目前来说国密算法不会被破解,重要信息不会存在泄露危险
  • 性能更好

暂且不论上面的论述是否是睁着眼睛说的,至少第一点是不可否认的

定义

国产密码算法(国密算法)是指国家密码局认定的国产商用密码算法,实现商用密码算法的加密、解密和认证等功能

国产商业环境目前主要使用公开的SM2(非对称算法)SM3(哈希算法)SM4(对称算法)

常用国密算法

SM1

  • 分组加密算法,分组长度,密钥长度都是128bit
  • 对称加密算法
  • 算法安全保密程度和AES相当
  • 算法不公开,以IP核形式存在于芯片中,需要通过加密芯片接口调用,采用硬件实现

SM2

  • 非对称加密算法,基于椭圆曲线密码的公钥密码算法标准,其秘钥长度256bit,包含数字签名、密钥交换和公钥加密
  • 用于替换RSA/DH/ECDSA/ECDH等国际算法
  • SM2采用的是ECC 256位的一种,其安全强度比RSA 2048位高,且运算速度快于RSA

SM3

  • 密码杂凑算法,报文摘要算法,不可逆
  • SHA-256基础上改进实现的一种算法,采用Merkle-Damgard结构,消息分组长度为512bit,输出的摘要值长度为256bit
  • 用于替代MD5/SHA-1/SHA-2等国际算法
  • 适用于数字签名和验证、消息认证码的生成与验证以及随机数的生成

SM4

  • 分组对称密码算法
  • AES算法具有相同的密钥长度、分组长度,都是128bit
  • 用于替代DES/AES等国际算法
  • 算法包含几种常见加密解密模式

    • ECB模式

      主要逻辑

      1. 把数据按照一定长度字节分段进行加密或者解密,最后一段不足固定长度的话补0或者补F
      2. 最后把计算出来的数据连在一起就得到明文或者密文

      优点

      1. 简单
      2. 有利于并行计算
      3. 误差不会被传递

      缺点

      1. 不能隐藏明文的模式
      2. 可能对明文进行主动攻击
    • CBC模式

      是一种循环模式

      主要逻辑

      1. 把数据按照固定长度分组,最后一组数据长度不足的话填充指定数据
      2. 第一组数据与初始化向量iv进行异或运算,产生的结果执行加密得到第一组密文
      3. 第二组数据与第一组加密结果进行异或后的结果,执行加密得到第二组密文
      4. 最后把所有分组密文拼接在一起得到加密结果

      优点

      1. 不容易主动攻击
      2. 安全性好于ECB,是SSL、IPSec的标准

      缺点

      1. 不利于并行计算
      2. 误差传递
      3. 需要初始化向量IV

SM9

  • 用椭圆曲线对实现的基于标识的数字签名算法、密钥交换协议、密钥封装机制和公钥加密与解密算法,包括数字签名生成算法和验证算法,并给出了数字签名与验证算法及其相应的流程

python国密算法推荐与应用

开源项目

https://github.com/duanhongyi/gmssl

SM2与SM3算法

由于sm2加密需要生成一个sm2对应的椭圆曲线对应的公钥和私钥,但是gmssl库没有封装一个随机生成公钥私钥的函数,所以需要访问一个第三方的网址去获取生成的公钥私钥,网址如下

https://const.net.cn/tool/sm2/genkey/

把生成的公钥和私钥输入作为参数,填入如下代码

初始化定义,非对称加密需要提供公钥和私钥参数

from gmssl import sm2, func

private_key = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
public_key = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'
sm2_crypt = sm2.CryptSM2(
    public_key=public_key, private_key=private_key, asn1=True)

加密和解密

data = b"test"
for _ in range(5):
    enc_data = sm2_crypt.encrypt(data)
    print(f'encrypted data len {len(enc_data.hex())}: {enc_data.hex()}')
    dec_data = sm2_crypt.decrypt(enc_data)
    print(f'decrypted data len {len(dec_data)}: {dec_data}\n')
    assert dec_data == data

生成的打印输出如下,生成密文的长度都是固定的200,发现每次生成的密文都是不同的,在加密过程中加入了salt,避免了相同明文映射到相同密文造成的不安全性

encrypted data len 200: 42a29aa33143985e14ae37ccdba89704595008e561af51cb7d88d2c9663468492b0d08a431fa5cd5b92e785ffd9cb858a69d71da82ddb3ddfeaa6e5e765e25cfb68bd4b307c63bc7fe844045159e8cbdbecbe4e3c31359c5f87669abb1dc6e431ad82632
decrypted data len 4: b'test'

encrypted data len 200: a797d8e1a205e1e50d60895b29d056eecd6d8e0f58ececf283680d695a007c24c2f182ce9601a500e3b755f6f530d3490b4efc43c242b9bdfff64253099792e8a7c4fa9692a59961fc7b6ef35a738a27d1c262f7e4a39190c64e1b8c2ff7c9ffa48748a6
decrypted data len 4: b'test'

encrypted data len 200: cd010a36cb272c9fc1f355e9e707554c972ba0ec27fe171a137b7d9165a7fe7f9913633333b1290909ce28f73973aa84fe666a97335c6d12f17fd724c87cc2bd37bc4581118fe049cd08ef963ffce0d84cd779f7c6b875c17a88b864115bca71a43cfbd9
decrypted data len 4: b'test'

encrypted data len 200: 88f5c05e7830c8b70320f2c40673d9aa0fd961535214825765d81e85e9c47e6bdb064086c1b462b2a735aa82ea5ed9668f8611a16c811247ff9dad92f9d572f983d5d2dcc26ae808fdfa4fb7044245bf9066ea11a28fb18208091e060da7c62ea6145266
decrypted data len 4: b'test'

encrypted data len 200: a5ef6804476fe7442c181aa70df95eae31c4ff59fb70002f7dfca1e8d50f987c8917ce0c9754c19ec5696bfcd71ff686847ca5c62f8c49654388ff1b463393aed40d3a53876587c53aa33aee466248d0fceb4f6c97ada7b25194d0990275b97c81fa32d1
decrypted data len 4: b'test'

签名和验证

data = b"test"
for _ in range(5):
    random_hex_str = func.random_hex(sm2_crypt.para_len)
    print(f'random hex str len:{len(random_hex_str)}: {random_hex_str}')
    sign = sm2_crypt.sign(data, random_hex_str)
    print(f'sign len: {len(sign)}: {sign}\n')
    assert sm2_crypt.verify(sign, data)

输出结果

每次签名的时候都会使用一个随机16进制字符串,同时输出的签名长度也不固定

random hex str len:64: ae41ca267f156a5e8bc2b5842138518df2187c4b81185502eaba534cecd473de
sign len: 144: 3046022100b32014d803f37c731d2612e6b79d8af8ab0e9a70bca60bb426e69d816c9a5c6e02210092d08bf619a8b465c40333b0151c38f8ed83e9243778eb16aab158dbbdea146c

random hex str len:64: 5becbd479334bf24177d1cd945d44a6b6a645ab7ff3c7cc13a1ced27400c22dd
sign len: 140: 304402203bca856f3714692050e0450b60199ee84933727c18c86afb966b92a67e10acb802201e13845e809f2f91d893602422fcf227826465f3ff388ec619ecfae09272f49e

random hex str len:64: 80e8a41ec38ed14c48d20fd6d3dba36e8e44f699def324032b920aa0024d79b7
sign len: 142: 304502210094a9cafd30d458467eaf63d560f4a76d20961d73171ab48f7a680c29aa6ec093022075bac7f549ca5cf9aef442d202ea1246ce3513a3c90cc0b5478f8d67879b5a1d

random hex str len:64: 7c7dbbca98378d1669c8884e7a9edab4c33227099487cb534c8f42214d4bcde2
sign len: 142: 3045022100c195ebbf1610c0e73739f702e4f486f40fa71578f8afcdf171fae3019421520702202978040d809d09e8c39dc459e3386b06877276c6129e355a70b16e2cd2d883b6

random hex str len:64: ac3e6789f816a4058eb0e70611ea784570cbba2aa885f7a847e9801ae2445f89
sign len: 142: 3045022100cb2a28cb29e1e6435d38d893e448a0c8aad1cc0bc4c532f09889f4dd70c3f8b5022001652e812c8f4828ddd6f4ce02b221f687f8f8cbe58263043a9e8f17f36793f3

sm3算法是一种hash算法

from gmssl import sm3, func

for _ in range(3):
    value = sm3.sm3_hash(func.bytes_to_list(b"abc"))
    print(f'len: {len(value)}: {value}')

运行结果是三个相同的64位hash

len: 64: 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0
len: 64: 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0
len: 64: 66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0

如果有类似于签名和校验的需要,还是推荐配合sm2进行

data = b"test"
for _ in range(5):
    sign = sm2_crypt.sign_with_sm3(data)
    print(f'len: {len(sign)} {sign}\n')
    assert sm2_crypt.verify_with_sm3(sign, data)

输出结果如下,每次生成的签名长度不固定,值也不同

len: 144 3046022100e1c3aa1b1de8be08a6c727e58d577fb7632adc08b49455b88aaef405cc80ae6c022100abbf44d84c5eeed6764b9aaeda044dc80f3e997f9c8d04dd75f7a73c6d21a353

len: 142 30450220026c1c8b3e56eacab664d34e33d90e95235fc16eec701df4de2eca699ba7d1cb022100e3c4158fd387e93c9a79a5e2d3d4d92a0aabcd8f72a0bc79fcd3e20a549599b9

len: 142 3045022100e0d049d45893be28cea5221fbe560fa2d519e191ea46aa8fa0dbaf181ae10afe02201ef02258c2f867de34c2ecc16184bada7e35fb515e1fed2e7bbfa11f52a3a8d0

len: 142 30450221009d68b397f288e246e647aa288bb0e8fa038011d6381dd30130849c0756a1defb02200c0d172deab8765159f78e0de8a9d5d3bfc0df9e366902da470f05a4c253f377

len: 140 3044022061c7bef3bcb7a2fd85ea1d8995e0bf05e9ec9b5ba5ef882728d73402876258ac02202d73fe00c8a552d45d8934104c5857d8e23b046e1f98ded8ebef26f8bcf30d92

SM4算法

初始化定义如下,由于是分组加密算法,需要使用一个密钥,密钥长度为128bit

下面定义key的组成,全是ascii字符,每个字符一个字节,所以需要16位字符,超过的字符会被忽略

测试加密明文数据是test-value

from gmssl.sm4 import CryptSM4, SM4_ENCRYPT, SM4_DECRYPT

key = b'1234567812345678'
value = b'test-value'
crypt_sm4 = CryptSM4()

测试ecb模式加密解密

for _ in range(5):
    crypt_sm4.set_key(key, SM4_ENCRYPT)
    encrypt_value = crypt_sm4.crypt_ecb(value)
    print(f'original len: {len(value)} {value}, encrypt len: {len(encrypt_value)} {encrypt_value}')
    crypt_sm4.set_key(key, SM4_DECRYPT)
    decrypt_value = crypt_sm4.crypt_ecb(encrypt_value)
    assert value == decrypt_value

输出结果如下,可以获取到结果如下

每次加密计算的密文结果是一样的,所以如果密钥泄露的话,数据就不安全了

生成的密文长度取决于明文长度,密文长度为16 * (明文长度//16 + 1)

original len: 10 b'test-value', encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'
original len: 10 b'test-value', encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'
original len: 10 b'test-value', encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'
original len: 10 b'test-value', encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'
original len: 10 b'test-value', encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'

测试cbc模式加密解密,需要定义一个初始化向量

iv = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
for _ in range(5):
    crypt_sm4.set_key(key, SM4_ENCRYPT)
    encrypt_value = crypt_sm4.crypt_cbc(iv, value)
    print(f'encrypt len: {len(encrypt_value)} {encrypt_value}')
    crypt_sm4.set_key(key, SM4_DECRYPT)
    decrypt_value = crypt_sm4.crypt_cbc(iv, encrypt_value)
    assert value == decrypt_value

得到结果如下,每次加密结果都一样

encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'
encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'
encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'
encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'
encrypt len: 16 b'\xaaa\xe5"YiT\x1d\x84\xa3e2\xec9\x80\xd8'

golang国密算法实现推荐

https://github.com/tjfoc/gmsm

参考阅读

国密算法介绍-知乎

GMSSL项目readme


龚正阳
29 声望5 粉丝

粗犷型程序员