sm2多端加密解密,java,js,android,ios实战

SM2非对称加密
公钥 = 04xxxxxxxxxxxxxxxxxxxx,

私钥 = 276xxxx

原文:你哦哈1232154 3654 {} ,俺可接受不符点
公钥私钥是我后台自己生成的

java代码实现
pom.xml

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15to18</artifactId>
    <version>1.71</version>
</dependency>
国密sm2算法秘钥对
package com.sm.mscmsm2;
 
/**
 * 描述: 国密sm2算法秘钥对 - 对象 <br>
 * 时间: 2022-07-14 16:01  <br>
 * 作者:IT学习道场
 */
 
public class SM2KeyPairs {
    /**
     *公钥
     */
    private String publicKey;
 
    /**
     * 私钥
     */
    private String privateKey;
 
    public SM2KeyPairs() {
    }
 
    public SM2KeyPairs(String publicKey, String privateKey) {
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }
 
    public String getPublicKey() {
        return publicKey;
    }
 
    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
    }
 
    public String getPrivateKey() {
        return privateKey;
    }
 
    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }
 
    @Override
    public String toString() {
        return "SM2KeyPairs{" +
                "publicKey='" + publicKey + '\'' +
                ", privateKey='" + privateKey + '\'' +
                '}';
    }
}
SM2辅助工具类
采用数字证书类型 X9

package com.sm.mscmsm2;
 
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
 
/**
 * 描述: 美术传媒sm2 <br>
 * 时间: 2022-07-25 17:32  <br>
 * 作者:王林冲
 */
@Slf4j
public class SM2Util {
 
    private static final ECDomainParameters domainParameters;
 
    private static final X9ECParameters sm2ECParameters;
 
    static {
        sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
    }
 
    public static SM2KeyPairs getSM2KeyPairs(){
        //生成密钥对
        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
        try {
            keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
            AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
            //私钥,16进制格式,自己保存,格式如a2081b5b81fbea0b6b973a3ab6dbbbc65b1164488bf22d8ae2ff0b8260f64853
            BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
            String privateKeyHex = privatekey.toString(16);
            //公钥,16进制格式,发给前端,格式如04813d4d97ad31bd9d18d785f337f683233099d5abed09cb397152d50ac28cc0ba43711960e811d90453db5f5a9518d660858a8d0c57e359a8bf83427760ebcbba
            ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
            String publicKeyHex = Hex.toHexString(ecPoint.getEncoded(false));
            SM2KeyPairs pairs = new SM2KeyPairs(publicKeyHex, privateKeyHex);
            return pairs;
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage());
        }
    }
 
    public static String decrypt(String data, String privateKey) {
        byte[] cipherDataByte = Hex.decode(data);
        //刚才的私钥Hex,先还原私钥
        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
        //用私钥解密
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, privateKeyParameters);
        //processBlock得到Base64格式,记得解码
        byte[] arrayOfBytes = new byte[0];
        try {
            arrayOfBytes = Base64.getDecoder().decode(sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length));
        } catch (InvalidCipherTextException e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage());
        }
        //得到明文:SM2 Encryption Test
        return new String(arrayOfBytes);
    }
 
    public static String encrypt(String data, String publicKey){
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        //刚才的私钥Hex,先还原私钥
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
 
        byte[] arrayOfBytes = new byte[0];
        try {
            byte[] in = Base64.getEncoder().encode(data.getBytes("utf-8"));
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage());
        }
        return Hex.toHexString(arrayOfBytes);
 
    }
 
    public static void main(String[] args) throws InvalidCipherTextException, UnsupportedEncodingException {
        //SM2KeyPairs sm2KeyPairs = getSM2KeyPairs();
        //System.out.println(sm2KeyPairs.toString());
        String publicKey = "0419080a9bdb6968f0ef31b2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
        String privateKey = "2766001cc5d2888xxxxxxxxxxxxxxxxxxxxxxxxx";
        String text = "你哦哈1232154 3654 {} ,俺可接受不符点";
        String encrypt = encrypt(text, publicKey);
        System.out.println("encrypt = "+  encrypt);
        //String encrypt = "041fccce91d7a35a429f449aea758364826f688577bad9d3b9c99a1ab5c390fdf63f00232e0af4fcab8fccd70c46b636e42024f260973d73c8d1b6c3d41c2b26238b8a09c994e0a912ba2022b342af049b164979018af08e75cb63a66408fe9dfd553e7a350fb0d12405b984bde185ba38ca693c9c12b06dbe445abc5b9fc754f2424bab2a766d62c12b1832c51b2cab44ba4dc0049b5f3b479fe1cc348bcfd77f65db4267";
        String jm = decrypt(encrypt, privateKey);
        System.out.println(jm);
 
    }
}
JavaScript实现
需要引入crypto-js.js和sm2.js,加老王微信,老王私下发你,文件太大,不方便放到文章上,关注公众号后,直接输入  “ 加好友” ,可以加老王微信精彩不断

html使用demo

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>sm2加密</title>
    <script src="./lib/crypto-js.js"></script>
    <script src="./lib/sm2.js"></script>
</head>
 
<body>
 
<script type="text/javascript">
    //私钥:2766001cc5d2888553efe566781d8fb25557aecd6435e731d21ad362af8a4eaf
    //公钥:前缀04+x坐标+y坐标
    var pubkeyHex = "04190xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    var msg='你哦哈1232154 3654 {} ,俺可接受不符点';
    //加密格式0: C1C2C3、1: C1C3C2
    var encryptData = sm2Encrypt(msg, pubkeyHex, 0);
    document.write(encryptData);
</script>
</body>
 
</html>

Android端和java端一样,jar包采用 bcprov-jdk15to18-1.71.jar






java端基于hutool实现,上面不是hutool,因为个人喜欢hutool,必须要基于hutool实现一个

pom.xml

<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.7.4</version>
  </dependency>
<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcpkix-jdk15on</artifactId>
  <version>1.57</version>
</dependency>
SM2KeyPairs
package com.sm.hutoolsm2;
 
/**
 * 描述: 国密sm2算法秘钥对 - 对象 <br>
 * 时间: 2022-07-14 16:01  <br>
 * 作者:IT学习道场
 */
 
public class SM2KeyPairs {
    /**
     *公钥
     */
    private String publicKey;
 
    /**
     * 私钥
     */
    private String privateKey;
 
    public SM2KeyPairs() {
    }
 
    public SM2KeyPairs(String publicKey, String privateKey) {
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }
 
    public String getPublicKey() {
        return publicKey;
    }
 
    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
    }
 
    public String getPrivateKey() {
        return privateKey;
    }
 
    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }
 
    @Override
    public String toString() {
        return "SM2KeyPairs{" +
                "publicKey='" + publicKey + '\'' +
                ", privateKey='" + privateKey + '\'' +
                '}';
    }
}
SM2Util
package com.sm.hutoolsm2;
 
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import com.sm.sm2.SM2KeyPairs;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import java.nio.charset.Charset;
import java.util.Base64;
 
/**
 * 描述: todo <br>
 * 时间: 2022-07-25 17:32  <br>
 * 作者:IT学习道场
 */
public class SM2Util {
 
 
    /**
     * 获取公钥私钥
     * @return SM2KeyPairs 公私钥对象
     */
    public static SM2KeyPairs getKeyPairs(){
        SM2 sm2 = SmUtil.sm2();
        // sm2的加解密时有两种方式即 C1C2C3、 C1C3C2,
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        // 生成私钥
        String privateKey = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey()));
        // 生成公钥
        String publicKey = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false));
 
        SM2KeyPairs keyPairs = new SM2KeyPairs(publicKey, privateKey);
        return keyPairs;
    }
    /**
     * 公钥加密
     * @param publicKey 公钥
     * @param text 预加密文本
     * @return 加密后文本
     */
    public static String encrypt(String publicKey, String text){
        // 通过密钥解密
        SM2 sm2 = SmUtil.sm2(null, publicKey);
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        //1 先把明文转成base64
        text = Base64.getEncoder().encodeToString(text.getBytes());
        //2 把base64的文本用公钥加密后在转成密文为16进制,否则前端解密前需要先转换格式
        String encryptStr = sm2.encryptHex(text, Charset.forName("utf-8"), KeyType.PublicKey);
        return encryptStr;
 
    }
 
    /**
     * 私钥解密
     * @param privateKey 私钥
     * @param text 加密文本
     * @return 解密后文本
     */
    public static String decrypt(String privateKey, String text){
        //创建sm2 对象
        SM2 sm2 = SmUtil.sm2(privateKey, null);
        sm2.setMode(SM2Engine.Mode.C1C2C3);
        // 私钥解密
        String decrypt = sm2.decryptStr(text, KeyType.PrivateKey);
        byte[] decode = Base64.getDecoder().decode(decrypt);
        String decryptStr = new String(decode);
        return decryptStr;
 
    }
 
    public static void main(String[] args) {
        //SM2KeyPairs keyPairs = getKeyPairs();
        //System.out.println(keyPairs.toString());
        String publicKey = "0407125a6dc1d73e41dc1b57xxxxxxxxxxxxxxxxxxxxxx";
        String privateKey = "0f3c459c2090eb5c35108xxxxxxxxxxxxx";
 
        String text = "你哦哈1232154 3654 {} ,俺可接受不符点";
 
        String encrypt = encrypt(publicKey, text);
        System.out.println("encrypt = " + encrypt);
        //String encrypt ="04ddb0f3622c51af847bbd528d9fb8cd264aa9341c817cb3077bb1464766b3b7e3a5ff523004d4f2259676267a080f66c4682844adef2e4b612e604071af16c6285fc48795e4ae1277e7d79b4bf420584dea2345eae0d5f2e12293013d25af09eaff4fabf5109a18fed74d07ed30a34b1623f161f7a70e2746accf9334b96cfc33f2aa6aae13c08b131604a9caa7d2c5453b8a021a6354ba27cd7d3f4ebcf93376efa9cf5d";
        String decrypt = decrypt(privateKey, encrypt);
        System.out.println("decrypt = " + decrypt);
 
    }
}
hutool实现和上面的Android可用的版本一样,当然,后端也可以直接使用Android和java通用的版本

ios端必须和js和java和Android端一致,采用 C1C2C3模式

下面是ios的实现。采用国密 国密的 Objective-C 封装

查看具体实现过程,请至开源项目地址GitHub - muzipiao/GMObjC: SM2/SM3/SM4/ECDH library based on OpenSSL.。

在终端运行以下命令:

git clone https://github.com/muzipiao/GMObjC.git
 
cd GMObjC
 
pod install
 
open GMObjC.xcworkspace
向项目中引入GMObjC即可

使用代码如下

SM2 加解密
SM2 加解密都很简单,加密传入待加密明文和公钥,解密传入密文和私钥即可,加密逻辑代码:

NSString *pubKey = @"0408E3FFF9505BCFAF9307E665E9229F4E1B3936437xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
NSString *priKey = @"90F3A42B9FE24AB196305FD92EC82E647616C3A369xxxxxxxxxx";
NSString *plaintext = @"你哦哈1232154 3654 {} ,俺可接受不符点";
//先对明文进行base64加密,再用GMSm2Utils 对其进行sm2加密,返回asn1编码格式的密文
NSString *encode = [GMSm2Utils encryptText:[self base64:plaintext] publicKey:pubKey];
//把asn1编码格式的密文的 encode 解码成C1C3C2的密文字符串 = c1c3c2
NSString *c1c3c2 = [GMSm2Utils asn1DecodeToC1C3C2:encode];
//再把c1c3c2这个字符串转成 C1C2C3 模式的密文字符串 = c1c2c3 ,这个可以直接传给java端,用上面的java端实现的sm2Util进行解密
NSString *c1c2c3 = [GMSm2Utils convertC1C3C2ToC1C2C3:c1c3c2 hasPrefix:NO];
NSLog(@"c1c2c3 : %@",c1c2c3);
注意这里的密文要拼上 "04" 这个前缀,后端才能进行解密

这是base64加密方法

- (NSString *)base64:(NSString *)string{
    NSString *target = string;
    NSData *data = [target dataUsingEncoding:NSUTF8StringEncoding];
    NSString *base64Str = [data base64EncodedStringWithOptions:nil];
    //        NSString *base64DecodeStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return base64Str;
}
我们前后端的逻辑是 先对明文进行base64加密,再sm2加密,再转16进制。

解密的逻辑,是先对密文进行16进制解密,再sm2解密,再base64解密

你们具体可根据自己的情况来处理,但是最好搞清楚逻辑,不然很容易出错,或者直接按照我们的略记处理,直接copy,省事儿

作者:IT学习道场


欢迎关注微信公众号 : IT学习道场