SM2加密的跨语言问题

最近在使用python对接一个java实现的SM2加密。
我尝试了最流行的python库:gmssl(吐槽一下,bug真多),但两边的加解密对不上。
我尝试找一下在线加解密网站,例如:https://the-x.cn/cryptography/Sm2.aspx
测试后我发现它和java算的结果是对得上的,但是和python计算的结果有出入,我有理由怀疑python这边的问题,但是我尝试了其它类似的库,发现都不行(一个能打的都没有) :sweat_smile:
有大佬研究过python中的sm2吗?实在不行的话只能造轮子了 :sweat_smile:

3 Likes

话说sm这一系列东西属于国产自研算法吗 :sweat_smile:

国密的底层加密库算法,每个语言都不太一样,甚至是同语言的依赖版本不一致的,也有可能双曲线的点位选择都不一致 :tieba_089:

都叫国密了,你说呢 :tieba_089:

这个东西,轮子自己造吧,好看不好看无所谓,好用就行,实用主义 :tieba_089:

难蚌,难怪没人造轮子,shit

Java的用过,SM2 SM3 SM4,自己生成的两对密钥,有时候自己都解不了,JDK1.7和1.8还有JDK11,JDK17,依赖包版本不一致,同一对密钥可能解密不成功 :tieba_089:

之前也试过SM2的加密对接,python和网页的加密结果怎么都对不上。最后无奈只能调JS实现了

终于找到一个能用的python包了
https://github.com/ww-rm/gmalg

我以前想用 Java 改一个 C 的项目,结果加密之后咋也不一致,然后就放弃了

我php调用时也是各种没有,各种不一致,索性用go写一个,给php调用 :rofl:

JDK 版本不同,确实会造成加解密不成功问题

不是,你挖坟挖到这个时候了。。。

??? 没懂

From 快问快答 to 开发调优

1 Like

老哥,我现在也遇到了这个java加密,python解密的问题,有轮子借鉴一下么,这个 gmalg也不好使

gmalg和java的结果是一致的啊

目前 已经解决,这个问题差距不是很大,具体原理,让各位大佬解释吧,我直接贴个代码.
java 是这个 ,主要引用了

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.70</version> <!-- 也可以确保最新 -->
        </dependency>
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;

/**
 * @author Enyuan Zhao
 * @Description SM2实现工具类
 * @date 2021/11/24 16:10
 */
public class Sm2Util {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    private static final Provider BC = new BouncyCastleProvider();

    /**
     * 生成国密公私钥对
     *
     * @return
     * @throws Exception
     */
    public static KeyPair createECKeyPair() throws Exception {
        /*
         * 上面过程为原始构造过程参考,BC已经为我们封装好了
         * 只需要下面三行代码即可
         */
        // 获取SM2椭圆曲线的参数
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", BC);
        // 使用SM2参数初始化生成器
        kpg.initialize(sm2Spec, new SecureRandom());
        return kpg.generateKeyPair();
    }

    /**
     * 根据publicKey对原始数据data,使用SM2加密
     *
     * @param data
     * @param publicKey
     * @return byte[] encrypt
     */
    public static byte[] encrypt(byte[] data, PublicKey publicKey) {
        ECPublicKeyParameters localECPublicKeyParameters = null;

        if (publicKey instanceof BCECPublicKey) {
            BCECPublicKey localECPublicKey = (BCECPublicKey) publicKey;
            ECParameterSpec localECParameterSpec = localECPublicKey.getParameters();
            ECDomainParameters localECDomainParameters = new ECDomainParameters(localECParameterSpec.getCurve(), localECParameterSpec.getG(), localECParameterSpec.getN());
            localECPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), localECDomainParameters);
        }
        SM2Engine localSM2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        localSM2Engine.init(true, new ParametersWithRandom(localECPublicKeyParameters, new SecureRandom()));
        byte[] arrayOfByte2;
        try {
            arrayOfByte2 = localSM2Engine.processBlock(data, 0, data.length);
            return arrayOfByte2;
        } catch (InvalidCipherTextException e) {

            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据privateKey对加密数据encodedata,使用SM2解密
     *
     * @param encodedata
     * @param privateKey
     * @return
     */
    public static byte[] decrypt(byte[] encodedata, PrivateKey privateKey) {
//        SM2Engine localSM2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        SM2Engine localSM2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        BCECPrivateKey sm2PriK = (BCECPrivateKey) privateKey;
        ECParameterSpec localECParameterSpec = sm2PriK.getParameters();
        ECDomainParameters localECDomainParameters = new ECDomainParameters(localECParameterSpec.getCurve(), localECParameterSpec.getG(), localECParameterSpec.getN());
        ECPrivateKeyParameters localECPrivateKeyParameters = new ECPrivateKeyParameters(sm2PriK.getD(), localECDomainParameters);
        localSM2Engine.init(false, localECPrivateKeyParameters);
        try {
            return localSM2Engine.processBlock(encodedata, 0, encodedata.length);
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 私钥签名
     *
     * @param data
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static byte[] signByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        sig.initSign(privateKey);
        sig.update(data);
        byte[] ret = sig.sign();
        return ret;
    }

    /**
     * 公钥验签
     *
     * @param data
     * @param publicKey
     * @param signature
     * @return
     * @throws Exception
     */
    public static boolean verifyByPublicKey(byte[] data, PublicKey publicKey, byte[] signature) throws Exception {
        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
        sig.initVerify(publicKey);
        sig.update(data);
        boolean ret = sig.verify(signature);
        return ret;
    }

    public static PublicKey getPublicKeyFromHexString(String pubKeyInHex) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // decode hex string into bytes
        byte[] publicKeyBytes = Hex.decode(pubKeyInHex);
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BC);
        ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
        // Convert the byte array to an ECPoint
        ECPoint point = ecSpec.getCurve().decodePoint(publicKeyBytes);

        // Create the ECPublicKeySpec using the ECPoint and EC parameters
        ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, ecSpec);

        return keyFactory.generatePublic(pubSpec);
    }

    public static PrivateKey getPrivateKeyFromHexString(String privateKeyHex) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
        // Decode the hex string to bytes
        byte[] privateKeyBytes = Hex.decode(privateKeyHex);

        // Convert bytes to BigInteger
        BigInteger privateKeyInt = new BigInteger(1, privateKeyBytes);

        // Specify the EC curve
        ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("sm2p256v1");

        // Create the ECPrivateKeySpec
        ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKeyInt, ecSpec);

        // Generate the private key from the key specification
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BC);
        return keyFactory.generatePrivate(privateKeySpec);
    }


    private static String privateKeyHex = "密钥";
    private static String publicKeyHex = "密钥";

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
        PublicKey publicKey = Sm2Util.getPublicKeyFromHexString(publicKeyHex);
        PrivateKey privateKey = Sm2Util.getPrivateKeyFromHexString(privateKeyHex);
        String encodedStr = Base64.getEncoder().encodeToString(Sm2Util.encrypt("Hello".getBytes(StandardCharsets.UTF_8), publicKey));
        System.out.println("加密后" + encodedStr);
        byte[] decode = Base64.getDecoder().decode(encodedStr);
        byte[] decrypt = Sm2Util.decrypt(decode, privateKey);
        System.out.println(new String(decrypt, StandardCharsets.UTF_8));
    }
    public static void main2(String[] args) throws Exception {
//        //生成公私钥对
//        KeyPair ecKeyPair = Sm2Util.createECKeyPair();
//        System.out.println("原始字符串:" + testStr);
//
//        PublicKey publicKey = ecKeyPair.getPublic();
//        if (publicKey instanceof BCECPublicKey) {
//            //获取64字节非压缩缩的十六进制公钥串(0x04)
//            String publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
//            System.out.println("SM2公钥:" + publicKeyHex);
//        }
//
//        PrivateKey privateKey = ecKeyPair.getPrivate();
//        if (privateKey instanceof BCECPrivateKey) {
//            //获取32字节十六进制私钥串
//            String privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
//            System.out.println("SM2私钥:" + privateKeyHex);
//        }
        PublicKey publicKey = getPublicKeyFromHexString(publicKeyHex);
        PrivateKey privateKey = getPrivateKeyFromHexString(privateKeyHex);
//        System.out.println("当前公钥:" + publicKey);
//        System.out.println("公钥hex:" + publicKeyHex);
//        System.out.println("当前私钥:" + privateKey);
//        System.out.println("私钥hex:" + privateKeyHex);

        if (args.length < 2) {
            System.err.println("参数错误,使用方法应为 java -jar Sm2Util.jar [decrypt/encrypt] [decrypt-string/encrypt-string]");
            return;
        }
        switch (args[0]) {
            case "decrypt" : {
                //System.out.println("decrypting");
                byte[] decode = Base64.getDecoder().decode(args[1]);
                byte[] decrypt = Sm2Util.decrypt(decode, privateKey);
                System.out.println(new String(decrypt, StandardCharsets.UTF_8));
                break;
            }
            case "encrypt" : {
                byte[] encrypt = Sm2Util.encrypt(args[1].getBytes(StandardCharsets.UTF_8), publicKey);
                String encryptBase64Str = Base64.getEncoder().encodeToString(encrypt);
                System.out.println(encryptBase64Str);
                break;
            }
            default:
                System.err.println("参数错误,使用方法应为 java -jar Sm2Util.jar [decrypt/encrypt] [decrypt-string/encrypt-string]");

        }

    }

}

python的就很少了.

from gmssl import sm2, func
import base64
import chardet
import binascii

public_key = '密钥'
private_key = '密钥'


sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key, mode=1)


base_str = '密文'
enc_data = binascii.a2b_base64(base_str)

enc_data = enc_data[1:]
dec_data = sm2_crypt.decrypt(enc_data)
print(dec_data.decode('utf-8'))

python 代码 是直接下载的 gmssl ,直接pip 不要去下哪个c的工具包

Name: gmssl
Version: 3.2.2
Summary: Pure-Python SM2/SM3/SM4 implementation
Home-page: https://github.com/duanhongyi/gmssl
Author: duanhongyi
Author-email: [email protected]
License: BSD