python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > SM2加签验签

Python调用Java接口实现互相SM2加签验签

作者:@半良人

这篇文章主要为大家详细介绍了Python如何调用Java接口实现互相SM2加签验签功能,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下

环境极其依赖

python环境

pip3 install gmssl

java环境

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.66</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.18</version>
</dependency>

具体功能

1.Python生成密钥对

from gmssl import sm2 as SM2
from gmssl import func as GMFunc
from random import SystemRandom
from base64 import b64encode, b64decode

class CurveFp:
    def __init__(self, A, B, P, N, Gx, Gy, name):
        self.A = A
        self.B = B
        self.P = P
        self.N = N
        self.Gx = Gx
        self.Gy = Gy
        self.name = name


class SM2Key:
    sm2p256v1 = CurveFp(
        name="sm2p256v1",
        A=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC,
        B=0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93,
        P=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF,
        N=0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123,
        Gx=0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7,
        Gy=0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0
    )

    @staticmethod
    def multiply(a, n, N, A, P):
        return SM2Key.fromJacobian(SM2Key.jacobianMultiply(SM2Key.toJacobian(a), n, N, A, P), P)

    @staticmethod
    def add(a, b, A, P):
        return SM2Key.fromJacobian(SM2Key.jacobianAdd(SM2Key.toJacobian(a), SM2Key.toJacobian(b), A, P), P)

    @staticmethod
    def inv(a, n):
        if a == 0:
            return 0
        lm, hm = 1, 0
        low, high = a % n, n
        while low > 1:
            r = high // low
            nm, new = hm - lm * r, high - low * r
            lm, low, hm, high = nm, new, lm, low
        return lm % n

    @staticmethod
    def toJacobian(Xp_Yp):
        Xp, Yp = Xp_Yp
        return Xp, Yp, 1

    @staticmethod
    def fromJacobian(Xp_Yp_Zp, P):
        Xp, Yp, Zp = Xp_Yp_Zp
        z = SM2Key.inv(Zp, P)
        return (Xp * z ** 2) % P, (Yp * z ** 3) % P

    @staticmethod
    def jacobianDouble(Xp_Yp_Zp, A, P):
        Xp, Yp, Zp = Xp_Yp_Zp
        if not Yp:
            return 0, 0, 0
        ysq = (Yp ** 2) % P
        S = (4 * Xp * ysq) % P
        M = (3 * Xp ** 2 + A * Zp ** 4) % P
        nx = (M ** 2 - 2 * S) % P
        ny = (M * (S - nx) - 8 * ysq ** 2) % P
        nz = (2 * Yp * Zp) % P
        return nx, ny, nz

    @staticmethod
    def jacobianAdd(Xp_Yp_Zp, Xq_Yq_Zq, A, P):
        Xp, Yp, Zp = Xp_Yp_Zp
        Xq, Yq, Zq = Xq_Yq_Zq
        if not Yp:
            return Xq, Yq, Zq
        if not Yq:
            return Xp, Yp, Zp
        U1 = (Xp * Zq ** 2) % P
        U2 = (Xq * Zp ** 2) % P
        S1 = (Yp * Zq ** 3) % P
        S2 = (Yq * Zp ** 3) % P
        if U1 == U2:
            if S1 != S2:
                return 0, 0, 1
            return SM2Key.jacobianDouble((Xp, Yp, Zp), A, P)
        H = U2 - U1
        R = S2 - S1
        H2 = (H * H) % P
        H3 = (H * H2) % P
        U1H2 = (U1 * H2) % P
        nx = (R ** 2 - H3 - 2 * U1H2) % P
        ny = (R * (U1H2 - nx) - S1 * H3) % P
        nz = (H * Zp * Zq) % P
        return nx, ny, nz

    @staticmethod
    def jacobianMultiply(Xp_Yp_Zp, n, N, A, P):
        Xp, Yp, Zp = Xp_Yp_Zp
        if Yp == 0 or n == 0:
            return (0, 0, 1)
        if n == 1:
            return (Xp, Yp, Zp)
        if n < 0 or n >= N:
            return SM2Key.jacobianMultiply((Xp, Yp, Zp), n % N, N, A, P)
        if (n % 2) == 0:
            return SM2Key.jacobianDouble(SM2Key.jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P), A, P)
        if (n % 2) == 1:
            mv = SM2Key.jacobianMultiply((Xp, Yp, Zp), n // 2, N, A, P)
            return SM2Key.jacobianAdd(SM2Key.jacobianDouble(mv, A, P), (Xp, Yp, Zp), A, P)


class PrivateKey:
    def __init__(self, curve=SM2Key.sm2p256v1, secret=None):
        self.curve = curve
        self.secret = secret or SystemRandom().randrange(1, curve.N)

    def PublicKey(self):
        curve = self.curve
        xPublicKey, yPublicKey = SM2Key.multiply((curve.Gx, curve.Gy), self.secret, A=curve.A, P=curve.P, N=curve.N)
        return PublicKey(xPublicKey, yPublicKey, curve)

    def ToString(self):
        return "{}".format(str(hex(self.secret))[2:].zfill(64))


class PublicKey:
    def __init__(self, x, y, curve):
        self.x = x
        self.y = y
        self.curve = curve

    def ToString(self, compressed=True):
        return '04' + {
            True: str(hex(self.x))[2:],
            False: "{}{}".format(str(hex(self.x))[2:].zfill(64), str(hex(self.y))[2:].zfill(64))
        }.get(compressed)


class SM2Util:
    def __init__(self, pub_key=None, pri_key=None):
        self.pub_key = pub_key
        self.pri_key = pri_key
        self.sm2 = SM2.CryptSM2(public_key=self.pub_key, private_key=self.pri_key)

    def Encrypt(self, data):
        info = self.sm2.encrypt(data.encode())
        return b64encode(info).decode()

    def Decrypt(self, data):
        info = b64decode(data.encode())
        return self.sm2.decrypt(info).decode()

    def Sign(self, data):
        random_hex_str = GMFunc.random_hex(self.sm2.para_len)
        sign = self.sm2.sign(data.encode(), random_hex_str)
        return sign

    def Verify(self, data, sign):
        return self.sm2.verify(sign, data.encode())

    @staticmethod
    def GenKeyPair():
        pri = PrivateKey()
        pub = pri.PublicKey()
        return pri.ToString(), pub.ToString(compressed=False)


def main():
    """
    主函数
    :return:
    """
    import random
    vs = '我是笨蛋'
    data = ''.join([vs[random.randint(0, len(vs) - 1)] for i in range(500)])
    print('原数据:{}'.format(data))

    e = SM2Util.GenKeyPair()
    print('私钥1:{} 公钥1:{}', (e[0], e[1]))
    print('私钥:{} 公钥:{}'.format(e[0], e[1]))
    sm2 = SM2Util(pri_key=e[0], pub_key=e[1][2:])

    sign = sm2.Sign(data)
    print('签名:{} 验签:{}'.format(sign, sm2.Verify(data, sign)))

    cipher = sm2.Encrypt(data)
    print('加密:{}\n解密:{}'.format(cipher, sm2.Decrypt(cipher)))


if __name__ == '__main__':
    main()

2.java生成密钥对

package SM;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Base64;

import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;


public class Keys {
    public static void main(String[] args) {
        String text = "我是笨蛋";
        System.out.println("原文:" + text);
        
        SM2 sm2 = new SM2();
        SM2Engine.Mode mode=SM2Engine.Mode.C1C2C3;
        sm2.setMode(mode);
        sm2.setDigest(new SM3Digest());
        String privateKey = HexUtil.encodeHexStr(sm2.getD());
        String publicKey = HexUtil.encodeHexStr(sm2.getQ(false));
        System.out.println("privateKey:" + privateKey);
        System.out.println("publicKey:" + publicKey);
          
        
}}

3.Python加签验签

import gmssl.func as gmssl_func

from baseutils.gmssl import sm2, func

private_key = 'b9069975c3276fab170ce5ea643e635b8f52075e69e6162232ae01555ed12a31'
public_key = '04f9ad444af8e31f993d96a644c6759ae8f9e5056068540eaa0e6d5f6b338d8ac4a7aac58170c1f18a7227c0dd72daee8b4e1e10d3db94aab6ab0fc5cac550f048'

data = 'Hello, SM2!'.encode("utf-8")
hashdata = sm2.sm3.sm3_hash(func.bytes_to_list(data))
hashdata2 = bytes.fromhex(hashdata)
print("hashdata----------", hashdata)
signer = sm2.CryptSM2(private_key=private_key, public_key=public_key)  # 签名
random_hex = gmssl_func.random_hex(signer.para_len)
signature = signer.sign(hashdata2, random_hex)

verifier = sm2.CryptSM2(private_key=private_key, public_key=public_key)  # 验证签名
is_valid = verifier.verify(signature, hashdata2)
print("签名数据:", data)
print("签名:", signature)
print("签名验证结果:", is_valid)

4.java加签验签

package SM;
import cn.hutool.core.lang.func.Func;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.util.Base64;

/**
 * 国密非对称加解密和加签验签算法
 *
 */
public class Sm2Test9 {
    public static void main(String[] args) {
        String text = "狗";
        System.out.println("原文:" + text);

        KeyPair pair = SecureUtil.generateKeyPair("SM2");
        byte[] privateKey = pair.getPrivate().getEncoded();
        byte[] publicKey = pair.getPublic().getEncoded();
        System.out.println("公钥:\n" + bytesToBase64(publicKey));
        System.out.println("私钥:\n" + bytesToBase64(privateKey));

        SM2 sm2 = SmUtil.sm2(privateKey, publicKey);
        //加签
        String sign = sm2.signHex(HexUtil.encodeHexStr(text));
        System.out.println("签名:" + sign);
        //验签
        boolean verify = sm2.verifyHex(HexUtil.encodeHexStr(text), sign);
        System.out.println("验签:" + verify);
         /**
        	 * 验签私钥
        	 */
        String signPrivateKey  = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgvIp+WfwJgOvvyPvfaxikVpRD5V5s2Z0hPo2a+GpfVzygCgYIKoEcz1UBgi2hRANCAARfv1UZ0Au40+P8bMqxCRaRx8VCc76S+UTTW2AaoO+5H+Z/XV96Dby1WulAGfOVoPdVpqb4rNcjKvrIjAujC+px";
        String signPublicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEX79VGdALuNPj/GzKsQkWkcfFQnO+kvlE01tgGqDvuR/mf11feg28tVrpQBnzlaD3Vaam+KzXIyr6yIwLowvqcQ==";
        String hexString1 = bytesToHex(base64ToBytes(signPrivateKey));
        String hexString2 = bytesToHex(base64ToBytes(signPublicKey));
        System.out.println("公钥00:\n" + hexString2);
        System.out.println("私钥00:\n" + hexString1);
        byte[] privateKey1 = base64ToBytes(signPrivateKey);
        byte[] publicKey1 = base64ToBytes(signPublicKey);
        SM2 sm22 = SmUtil.sm2(privateKey1, publicKey1);
        //加签
        String sign2 = sm22.signHex(HexUtil.encodeHexStr(text));
        System.out.println("签名111111:" + sign2);
        //验签
        boolean verify2 = sm22.verifyHex(HexUtil.encodeHexStr(text), sign2);
        System.out.println("验签11111:" + verify2);
 

    }

    public static String bytesToHex(byte[] bytes) {  
        StringBuilder sb = new StringBuilder();  
        for (byte b : bytes) {  
            sb.append(String.format("%02x", b & 0xff));  
        }  
        return sb.toString();  
    }
    /**
     * 字节数组转Base64编码
     *
     * @param bytes 字节数组
     * @return Base64编码
     */
    private static String bytesToBase64(byte[] bytes) {
        byte[] encodedBytes = Base64.getEncoder().encode(bytes);
        return new String(encodedBytes, StandardCharsets.UTF_8);
    }

    /**
     * Base64编码转字节数组
     *
     * @param base64Str Base64编码
     * @return 字节数组
     */
    private static byte[] base64ToBytes(String base64Str) {
        byte[] bytes = base64Str.getBytes(StandardCharsets.UTF_8);
        return Base64.getDecoder().decode(bytes);
    }
    /**
     * 16进制字符串编码转字节数组
     *
     * @param 
     * @return 字节数组
     */
    public static byte[] hexStringToByteArray(String s) {  
        int len = s.length();  
        byte[] data = new byte[len / 2];  
        for (int i = 0; i < len; i += 2) {  
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)  
                                 + Character.digit(s.charAt(i+1), 16));  
        }  
        return data;  
    }
 
}

遇到的问题

python与java各自生成的密钥对加签验签没有问题,但是互相验签失败

python gmssl版本有影响

java hutool 生成的密钥对的方法有很大区别,hutool本身也提供了几种生成不同密钥对的方法需要注意(30开头的ASN1格式的密钥好像目前python兼容不了)

解决方案

1.java兼容python(目前我提供的这个代码是java生成的密钥对python能用,python生成的密钥对java也能用)

2.python兼容java(把java类打包成可执行jar文件,在python中执行)

import subprocess

java_program = "java -jar D:\SM2Python\python_java\JARS\SM2.jar"
result = subprocess.run(java_program.split(), stdout=subprocess.PIPE, universal_newlines=True)
print(result.stdout)

3.搭建一个java项目可接口访问

4.Java Hutool与python兼容的SM2签名

import base64
import json
from collections import OrderedDict
from gmalg import SM2, SM3


class SignUtil:
    """SM2签名工具类,严格遵循Java Hutool库的签名逻辑"""

    # Java Hutool SM2默认使用的UID
    DEFAULT_UID = b"1234567812345678"

    @staticmethod
    def _parse_private_key(der_data: bytes) -> bytes:
        """解析DER格式的SM2私钥"""
        hex_data = der_data.hex()

        # 尝试从PKCS#8格式解析私钥 (120字节格式)
        key_pos = hex_data.find("0420") + 4
        if key_pos > 3 and key_pos + 64 <= len(hex_data):
            return bytes.fromhex(hex_data[key_pos:key_pos + 64])

        # 尝试从另一种PKCS#8格式解析私钥 (118字节格式)
        key_pos = hex_data.find("30740201010420") + 12
        if key_pos > 12 and key_pos + 64 <= len(hex_data):
            return bytes.fromhex(hex_data[key_pos:key_pos + 64])

        # 尝试直接提取32字节私钥
        if len(hex_data) >= 64:
            return bytes.fromhex(hex_data[:64])

        return b""

    @staticmethod
    def _parse_public_key(der_data: bytes) -> bytes:
        """解析DER格式的SM2公钥"""
        hex_data = der_data.hex()

        # 尝试从X.509格式解析公钥 (91字节格式)
        key_pos = hex_data.find("3059301306072a8648ce3d020106082a811ccf5501822d03420004") + 58
        if key_pos > 58 and key_pos + 128 <= len(hex_data):
            return bytes.fromhex("04" + hex_data[key_pos:key_pos + 128])

        # 尝试从另一种X.509格式解析公钥 (89字节格式)
        key_pos = hex_data.find("03420004") + 8
        if key_pos > 8 and key_pos + 128 <= len(hex_data):
            return bytes.fromhex("04" + hex_data[key_pos:key_pos + 128])

        # 尝试直接提取65字节公钥
        if len(hex_data) >= 130 and hex_data.startswith("04"):
            return bytes.fromhex(hex_data[:130])

        return b""

    @staticmethod
    def sign(data: str, private_key_str: str) -> str:
        """
        生成与Java Hutool兼容的SM2签名
        :param data: 待签名的原始数据
        :param private_key_str: Base64编码的私钥
        :return: Base64编码的签名
        """
        try:
            print("=== Python 签名调试信息 ===")
            print(f"data: {data}")

            # 解析私钥
            private_key_der = base64.b64decode(private_key_str)
            private_key_bytes = SignUtil._parse_private_key(private_key_der)
            if not private_key_bytes:
                raise ValueError("私钥解析失败")

            # 使用默认UID创建SM2实例
            sm2 = SM2(sk=private_key_bytes, uid=SignUtil.DEFAULT_UID)

            # 计算SM3哈希(与Java Hutool一致,直接对数据进行SM3哈希,不包含Z值)
            data_bytes = data.encode('utf-8')
            sm3 = SM3()
            sm3.update(data_bytes)
            digest = sm3.value()
            print(f"SM3(data) (digest): {digest.hex()}")

            # 关键: Java签名的是SM3哈希的hex字符串的字节,而不是二进制digest
            # hashcode = SmUtil.sm3(data) -> 返回的是hex字符串
            # hashcode.getBytes() -> 是这个hex字符串的UTF-8字节
            hashcode_hex_str = digest.hex()
            message_bytes = hashcode_hex_str.encode('utf-8')
            print(f"hashcode (hex string): {hashcode_hex_str}")
            print(f"hashcode.getBytes() (message_bytes): {message_bytes} (hex: {message_bytes.hex()})")

            # 生成签名
            r_bytes, s_bytes = sm2.sign(message_bytes)
            r_int = int.from_bytes(r_bytes, 'big')
            s_int = int.from_bytes(s_bytes, 'big')
            print(f"r (hex): {r_int.to_bytes((r_int.bit_length() + 7) // 8, 'big').hex()}")
            print(f"s (hex): {s_int.to_bytes((s_int.bit_length() + 7) // 8, 'big').hex()}")

            # 构建DER编码签名
            from Cryptodome.Util.asn1 import DerSequence, DerInteger
            # 确保r和s是正数
            der_seq = DerSequence([
                DerInteger(r_int),
                DerInteger(s_int)
            ])
            signature_der = der_seq.encode()

            signature_b64 = base64.b64encode(signature_der).decode('utf-8')
            print(f"最终签名 (Base64): {signature_b64}")
            print("=== 结束 Python 签名调试信息 ===")

            return signature_b64

        except Exception as e:
            print(f"签名失败: {e}")
            import traceback
            traceback.print_exc()
            return ""

    @staticmethod
    def verify(data: str, sign: str, public_key_str: str) -> bool:
        """
        验证Java生成的SM2签名
        :param data: 原始数据
        :param sign: Base64编码的签名
        :param public_key_str: Base64编码的公钥
        :return: 验签结果
        """
        try:
            print("=== Python 验证调试信息 ===")
            print(f"data: {data}")

            # 解析公钥
            public_key_der = base64.b64decode(public_key_str)
            public_key_bytes = SignUtil._parse_public_key(public_key_der)
            if not public_key_bytes:
                print("公钥解析失败")
                return False

            # 使用默认UID创建SM2实例
            sm2 = SM2(pk=public_key_bytes, uid=SignUtil.DEFAULT_UID)

            # 计算SM3哈希
            data_bytes = data.encode('utf-8')
            sm3 = SM3()
            sm3.update(data_bytes)
            digest = sm3.value()
            print(f"SM3(data) (digest): {digest.hex()}")

            # 关键: Java签名的是SM3哈希的hex字符串的字节
            hashcode_hex_str = digest.hex()
            message_bytes = hashcode_hex_str.encode('utf-8')
            print(f"hashcode (hex string): {hashcode_hex_str}")
            print(f"hashcode.getBytes() (message_bytes): {message_bytes} (hex: {message_bytes.hex()})")

            # 解析签名
            sign_bytes = base64.b64decode(sign)
            print(f"签名 (Base64): {sign}")
            print(f"签名 (DER hex): {sign_bytes.hex()}")

            from Cryptodome.Util.asn1 import DerSequence
            seq = DerSequence()
            seq.decode(sign_bytes)
            r_int = seq[0] if isinstance(seq[0], int) else int.from_bytes(seq[0], 'big')
            s_int = seq[1] if isinstance(seq[1], int) else int.from_bytes(seq[1], 'big')
            r_bytes = r_int.to_bytes((r_int.bit_length() + 7) // 8, 'big')
            s_bytes = s_int.to_bytes((s_int.bit_length() + 7) // 8, 'big')

            print(f"r (hex): {r_bytes.hex()}")
            print(f"s (hex): {s_bytes.hex()}")

            # 验证签名
            result = sm2.verify(message_bytes, r_bytes, s_bytes)
            print(f"验签结果: {result}")
            print("=== 结束 Python 验证调试信息 ===")
            return result

        except Exception as e:
            print(f"验签失败: {e}")
            import traceback
            traceback.print_exc()
            return False


# 测试代码
if __name__ == "__main__":
    # Java生成的密钥对
    java_private_key = "java私钥(base64后的)"
    java_public_key = "java公钥(base64后的)"
    java_signature = "java生成的签名"

    # 创建要签名的数据(与Java代码完全一致)
    sign_data = OrderedDict()
    sign_data["data"] = "TEST"
    sign_data["version"] = "V1.0.0"

    data_to_sign = json.dumps(sign_data, separators=(',', ':'))
    print("待签名数据:", data_to_sign)
    print("数据长度:", len(data_to_sign))
    print("数据HEX:", data_to_sign.encode('utf-8').hex())

    # 验证SM3哈希值
    sm3 = SM3()
    sm3.update(data_to_sign.encode('utf-8'))
    digest = sm3.value()
    print("SM3哈希:", digest.hex())

    # 使用Java私钥在Python中生成签名
    print("\n=== Python生成签名 ===")
    signature = SignUtil.sign(data_to_sign, java_private_key)
    print("Python生成的签名:", signature)

    # 在Python中验证Python生成的签名
    print("\n=== Python验证Python签名 ===")
    verify_result = SignUtil.verify(data_to_sign, signature, java_public_key)
    print("Python验证Python签名结果:", verify_result)

    # 在Python中验证Java生成的签名
    print("\n=== Python验证Java签名 ===")
    java_verify_result = SignUtil.verify(data_to_sign, java_signature, java_public_key)
    print("Python验证Java签名结果:", java_verify_result)

    # 额外测试:使用Java公钥验证Java签名(在Java中执行)
    print("\n=== 请在Java中验证以下数据 ===")
    print("数据:", data_to_sign)
    print("签名:", signature)

到此这篇关于Python调用Java接口实现互相SM2加签验签的文章就介绍到这了,更多相关SM2加签验签内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文