笔者来介绍一下RSA 和 SM2的原理和实现
a、RSA
原理以及数据格式介绍
RSA2048/3072/4096,非对称签名,hash算法一搬有hash256 、384和512,对称签名有:DES、AES128 和256等
RSA原理是利用大数难分解原理,正向容易,反向极难的核心思想。
- RSA 涉及3个重要参数:n,e,d
- 公钥(n,e),私钥(n,d),公钥公开,私钥保密
- n = p*q,p和q为两个大素数,2048位就是256 B的n,
- e满足如下规则:1< e <φ(n),φ(n) = (p-1) * (q-1),通常会选65537
- d满足如下规则,e*d % φ(n) = 1,
公私钥裸数据格式,der格式如下:

下图可以看到n是256Byte大数,e为 01 00 01 ->65537

- 私钥的格式如下:包括了公钥信息,

完整的结构如下:
- 包括n/e/d/p/q等参数

TLV 格式说明

- 加密原理:消息是m,m^e = C (mod n),C就是加密密文,====》n%(m^e)=KC,消息m是用户数据转成的大数字,然后参与运算。
- 解密原理:c^d = m (mod n),解密出m消息 ====> n%(c^d)=Km
python算法
秘钥生成
python
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
# 生成私钥
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
# 生成公钥
public_key = private_key.public_key()
# 将私钥和公钥序列化为PEM格式
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
)
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
# 打印私钥和公钥
print(private_pem.decode('utf-8'))
print(public_pem.decode('utf-8'))
签名生成
python
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
# 已经有了private_key和要签名的文件1.txt
# 读取文件内容
with open('1.txt', 'rb') as f:
data = f.read()
# 使用私钥对文件进行签名
signature = private_key.sign(
data,
padding.PKCS1v15()
hashes.SHA256()
)
# 将签名写入文件
with open('signature', 'wb') as f:
f.write(signature)
签名校验
python
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
# 使用公钥验证签名
try:
public_key.verify(
signature,
message,
padding.PKCS1v15(),
hashes.SHA256()
)
print("签名验证成功,消息未被篡改。")
except Exception as e:
print(f"签名验证失败: {e}")
只是在知道公钥的情况下,也可以进行签名验证
python
def rsa_verify_sign(public_type, public_key_str, sign_data, file_path):
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization,fronhashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
# load public key
public_key = serialization.load_pem_public_key(public_key_str.encode(), backend=default_backend())# use the public ky verify sign
with open(file_path, "rb") as f:
data = f.read()
public_key.verify(sign_data, data, padding.PKcs1v15(), hashes.SHA256())
OpenSSL
shell
# 生成2048位的RSA私钥
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
# 从私钥中提取公钥
openssl rsa -pubout -in private_key.pem -out public_key.pem
# 使用私钥签名文件,生成签名文件
openssl dgst -sha256 -sign private_key.pem -out signature.bin example.txt
# 验证签名
openssl dgst -sha256 -verify public_key.pem -signature signature.bin example.txt
# 生成私钥的der格式,即二进制格式
openssl rsa -in private_key.pem -outform DER -out private_key.der
# 生成公钥的der格式,即二进制格式
openssl rsa -in public_key.pem -pubin -outform DER -out public_key.der
Openssl 支持的hash 、填充以及加密算法如下

b、SM2 国密算法
原理及数据格式介绍
SM2:非对称签名算法,SM3:hash算法,SM4:对称加密算法
SM2 原理介绍:
-
SM2是利用离散对数问题,建立在基于椭圆曲线的离散对数问题上的密码体制,给定椭圆曲线上的一个点G,并选取一个整数k,求解K=kG很容易(注意根据kG求解出来的K也是椭圆曲线上的一个点);反过来,在椭圆曲线上给定两个点K和G,若使K=kG,求整数k是一个难题。其就是建立在此数学难题之上,这一数学难题称为椭圆曲线离散对数问题。其中椭圆曲线上的点K则为公钥(注意公钥K不是一个整数而是一个椭圆曲线点),整数k则为私钥(实际上是一个大整数)。
-
其公钥是椭圆曲线上面的一个坐标,为256位
-
每个坐标32Byte,总计64Byte,实际中编码方式可能是ASN.1,所以其公钥长度必定大于64位,
-
考虑如下等式:K=kG 其中 K,G为Ep(a,b)上的点,k为小于n(n是点G的阶)的整数,我们把点G称为基点(base point)。
-
密码学中,描述一条Fp上的椭圆曲线,常用到六个参量:T=(p,a,b,n,x,y),(p 、a 、b) 用来确定一条椭圆曲线,p为素数域内点的个数,a和b是其内的两个大数;x,y为G基点的坐标,也是两个大数;n为点G基点的阶;以上六个量就可以描述一条椭圆曲线,有时候我们还会用到h(椭圆曲线上所有点的个数p与n相除的整数部分)
-
SM2默认的参数如下:

SM2秘钥数据格式:
-
SM2私钥是一个大于等于1 且小于n1 的整数(n 为SM2 算法的阶, 其值参见 GM/T 0003) , 简记为k, 长度为256位
-
SM2公钥是SM2曲线上的一个点, 由横坐标和纵坐标两个分量来表示, 记为(x,y) , 简记为 Q, 每个分量的长度为256 位,总长度512位
-
SM2 算法私钥数据格式的 ASN.1定义为:
SM2 Private Key : : = 整数
SM2 算法公钥数据格式的 ASN.1 定义为:
SM2 Public Key : : = bit string
SM2 Public Key 为bit string 类型, 内容为04‖ X‖ Y, 其中,X 和 Y 分别标识公钥的x 分量和y
分量, 其长度各为256 位。
具体例子如下:
- 私钥:5c01d0cf8c243994138438c5965c4af5126592e78ce410bf0100bb4a04f2d032(256 BIT)
- 公钥:04c5a147483701488f316614071ed6868861398aecd05560348d54fbdefbe82538486605039809C839088783609fC8853764024193C5690c14c1850828988 (528 BIT,开始两Byte固定:04)
签名的格式也是一个坐标:
- R:整数第一部分
- S:整数第二部分
还有可能是ASN.1 DER格式,需要解析:
解析流程如下:
python
def verify(self, Sign, data):
# 验签函数,sign签名r||s,E消息hash,public_key公钥
if self.asn1:
unhex_sign = unhexlify(Sign.encode())
seq_der = DerSequence()
origin_sign = seq_der.decode(unhex_sign)
r = origin_sign[0]
s = origin_sign[1]
else:
r = int(Sign[0:self.para_len], 16)
s = int(Sign[self.para_len:2*self.para_len], 16)
签名的时候也对应了相应的模式,
python
def sign(self, data, K):
"""
签名函数, data消息的hash,private_key私钥,K随机数,均为16进制字符串
:param self:
:param data: data消息的hash
:param K: K随机数
:return:
"""
E = data.hex() # 消息转化为16进制字符串
e = int(E, 16)
d = int(self.private_key, 16)
k = int(K, 16)
P1 = self._kg(k, self.ecc_table['g'])
x = int(P1[0:self.para_len], 16)
R = ((e + x) % int(self.ecc_table['n'], base=16))
if R == 0 or R + k == int(self.ecc_table['n'], base=16):
return None
d_1 = pow(
d+1, int(self.ecc_table['n'], base=16) - 2, int(self.ecc_table['n'], base=16))
S = (d_1*(k + R) - R) % int(self.ecc_table['n'], base=16)
if S == 0:
return None
elif self.asn1:
return DerSequence([DerInteger(R), DerInteger(S)]).encode().hex()
else:
return '%064x%064x' % (R, S)
Python算法
-
GMSSL的Python库里面有SM2的签名和验签函数
-
签名公私钥都是pem str格式的
-
里面没有对于SM2 ID的参数,不过可以自己新增接口
python
def sm2_verify_sign(self, public_type, public_key_str, sign_data, file_path):
from gmssl import sm2, sm3, func
with open(file_path, "rb") as f:
data = f.read()
public_key = public_key_str[2:]
# import asnlcrypto
# from asnlcrypto.keys import PublickeyInfo
# public_key = "".join(public_key_str.split("\n")[1:-2])
# public_key_byte = base64.b64decode(public_key)
# public_key_info = PublicKeyInfo.load(public_key_byte)
# public_key_byte = public_key_info['public_key'].native
# public_key = public_key_byte.hex()
sm2_crypt = sm2.CryptSM2(private_key="", public_key=public_key, asnl=True)
if public_type == "customer":
result = sm2_crypt.verify(sign_data, data)
else:
pass
def sm2_sign(self, private_key, public_key, message_bytes):
from gmssl import sm2, sm3, func
sm2_signer = sm2.CryptSM2(private_key=private_key, public_key=public_key, asnl=True)
random_hex_str ='fa0ff179b5c2397262d393365cc3e43aef6646c7e48e31de05af2b2e1fde9a06'
signature = sm2_signer.sign_with_sm3(message_bytes, random_hex_str=random_hex_str)
signature = bytes.fromhex(signature)
新增SM2 ID的签名算法,这是Python GMSSL库的算法
- 默认id 是:31323334353637383132333435363738
- 所以这个可以作为参数
python
def _sm3_z(self, data):
"""
SM3WITHSM2 签名规则: SM2.sign(SM3(Z+MSG),PrivateKey)
其中: z = Hash256(Len(ID) + ID + a + b + xG + yG + xA + yA)
"""
# sm3withsm2 的 z 值
z = '0080'+'31323334353637383132333435363738' + \
self.ecc_table['a'] + self.ecc_table['b'] + self.ecc_table['g'] + \
self.public_key
z = binascii.a2b_hex(z)
Za = sm3.sm3_hash(func.bytes_to_list(z))
M_ = (Za + data.hex()).encode('utf-8')
e = sm3.sm3_hash(func.bytes_to_list(binascii.a2b_hex(M_)))
return e
GMSSL
Linux下使用的更新的GLIBC编译的,所以现在环境部支持,无法运行。需要在linux下面重新编译


linux编译,进入build下面,
cmake ..

接着进行make编译,最后会生成gmssl 可执行程序
make

到这里及生成完成

输入./bin/gmssl ,可以运行国密相关的操作。

输入 make test,可以执行case 验证
