国密介绍-SM2(代码实现)

1 前言

在上个章节介绍了SM2的原理,本章节使用代码实现,考虑到大部分的系统为:B/S框架,实现使用:

1  前端使用:sm-crypto,实现页面的加解密处理

2 后端使用:java,实现与前端加解密互通

3 完整代码见:国密演示: SM算法用法,页面的SM2功能菜单:

 2 代码实现

5.1 JS

  本章节代码实现使用:sm-crypto库文件,以下展示部分的核心代码实现:

//生产密钥对
function createECKeyPair(){
	let keypair = sm2.generateKeyPairHex();
	publicKey = keypair.publicKey; // 公钥
	privateKey = keypair.privateKey; // 私钥
    $("#publicKey").val(publicKey);
    $("#PrivateKey").val(privateKey);
}


//公钥加密
function cipherData() {
	//获取秘钥
	var publicKey =  $("#publicKey").val(); // 公钥
    //加密原文
	const msgString = $("#encode-text").val();
	const cipherMode = 1; // 1 - C1C3C2,0 - C1C2C3,默认为1

	const encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode); // 加密结果
	 $("#encData").val(encryptData);
}

//私钥解密
function decDataQ(){
	var encryptData= $("#encData").val();//密文
	var privateKey = $("#PrivateKey").val(); // 私钥	
	const cipherMode = 1; // 1 - C1C3C2,0 - C1C2C3,默认为1
	const decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode); // 解密结果
	$("#decData").val(decryptData);
}

//签名
function signDataJS() {
	var msg=$("#encode-text").val();
	var privateKey=$("#PrivateKey").val();
	
	// 签名
    let sigValueHex = sm2.doSignature(msg, privateKey, {
        hash:true,
        der:true,
    });
	
	$("#signData").val(sigValueHex);
}

5.2 java实现

基于BouncyCastle库实现:

package org.liuy.pki;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.interfaces.ECPrivateKey;
import org.bouncycastle.jce.interfaces.ECPublicKey;
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.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;

/**
 *  国密工具包,依赖BC包
 *  实现与:sm-crypto.js互通
 *  	
 *  @author liuy
 *
 */
 
public class SM2Tools {
	
	
	static {
		Security.addProvider(new BouncyCastleProvider());
	} 
    //椭圆曲线ECParameters ASN.1 结构
    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    //椭圆曲线公钥或私钥的基本域参数。
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    //默认国密id
    private static String userID="1234567812345678";    
    /**
     * 获取公钥16进制字符串
     * @param publicKey
     * @return
     */
    public static String getPublicKeyHex(PublicKey publicKey) {
    	 String publicKeyHex = null;
         if (publicKey instanceof BCECPublicKey) {
            //获取65字节非压缩缩的十六进制公钥串(0x04)
            publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
        }
		return publicKeyHex;
	}
   
    /**
     * 获取16进制私钥字符串
     * @param privateKey
     * @return
     */
	public static String getPrivateKeyHex(PrivateKey privateKey) {
		String privateKeyHex=null;
	    if (privateKey instanceof BCECPrivateKey) {
            //获取32字节十六进制私钥串
            privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
        }
		return privateKeyHex;
	}


	/**
     * @return KeyPair
     * @Description 生成秘钥对
     * @Author msx
     */
    public static KeyPair createECKeyPair() {
        //使用标准名称创建EC参数生成的参数规范
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg;
        try {
            kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            // 使用SM2算法域参数集初始化密钥生成器(默认使用以最高优先级安装的提供者的 SecureRandom 的实现作为随机源)
            // kpg.initialize(sm2Spec);
 
            // 使用SM2的算法域参数集和指定的随机源初始化密钥生成器
            kpg.initialize(sm2Spec, new SecureRandom());
            KeyPair  kp=kpg.generateKeyPair();
 
            // 通过密钥生成器生成密钥对
            return kp;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
 
    /**
     * 公钥加密
     * @Author msx
     * @param plaintext         明文数据
     * @param publicKeyHex      16进制SM2公钥
     * @param cipherMode         1 - C1C3C2,0 - C1C2C3,默认为1
     * @return String           16进制密文
     * @throws InvalidCipherTextException 
     * @throws UnsupportedEncodingException 
     */
    public static String encrypt(String plaintext,String publicKeyHex,int cipherMode) {
    	
    	 // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKeyHex));
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        SM2Engine sm2Engine = new SM2Engine();
         // 设置sm2为加密模式
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
        byte[] arrayOfBytes = null;
        byte[] in = plaintext.getBytes();
        try {
			arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
		} catch (InvalidCipherTextException e) {
			e.printStackTrace();
		}
    	//C1C3C2模式
    	if(cipherMode==1)
    	{
            arrayOfBytes=changeC1C2C3ToC1C3C2(arrayOfBytes);
            return Hex.toHexString(arrayOfBytes);
    	}
    	else
    	{
    		C1C2C3模式
    		return Hex.toHexString(arrayOfBytes);
    	}
    }
    
    
    /**
     * 公钥加密
     * @Author msx
     * @param contentArry         明文数组
     * @param publicKey         SM2公钥对象
     * @param cipherMode         1 - C1C3C2,0 - C1C2C3,默认为1
     * @return byte[]            密文数组
     * @throws InvalidCipherTextException 
     * @throws UnsupportedEncodingException 
     */
    public static byte[] encrypt(byte[] contentArry,PublicKey publicKey,int cipherMode) {
    	
		BCECPublicKey localECPublicKey = (BCECPublicKey) publicKey;
	    ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
         // 设置sm2为加密模式
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
        byte[] arrayOfBytes = null;
        try {
			arrayOfBytes = sm2Engine.processBlock(contentArry, 0, contentArry.length);
		} catch (InvalidCipherTextException e) {
			e.printStackTrace();
		}
        if(cipherMode==1)
        {
        	//C1C3C2模式
        	arrayOfBytes=changeC1C2C3ToC1C3C2(arrayOfBytes);
        }
    	//C1C2C3模式
    	return arrayOfBytes;
    }
    
    
    /**
     * bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密
     * @param c1c3c2
     * @return
     */
    public static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; //new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c3c2.length];
        System.arraycopy(c1c3c2, 0, result, 0, c1Len); //c1: 0->65
        System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len); //c2
        System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); //c3
        return result;
    }
    
    /**
     * bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2
     * @param c1c2c3
     * @return
     */
    public static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; //new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c2c3.length];
        System.arraycopy(c1c2c3, 0, result, 0, c1Len); //c1
        System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len); //c3
        System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len); //c2
        return result;
    }
 
    
    /**
     * @Description 私钥解密  
     * @Author msx
     * @param privateKeyHex 16进制私钥
     * @param cipherData    16进制密文数据
     * @param cipherMode         1 - C1C3C2,0 - C1C2C3,默认为1
     * @return String       原文Hex
     * @throws InvalidCipherTextException 
     * @throws UnsupportedEncodingException 
     */
    public static String decrypt(String cipherDataHex,String privateKeyHex,int cipherMode) {
	   	//兼容smcrypto.js,加密头部增加04
	   	if(!cipherDataHex.startsWith("04"))
	   	{
	   		cipherDataHex="04"+cipherDataHex;
	   	}
	   	
    	byte[] dataByte=Hex.decode(cipherDataHex);
		PrivateKey privateKey = getBCECPrivateKeyByPrivateKeyHex(privateKeyHex);
		byte[] dataArry= decrypt(dataByte,privateKey,cipherMode);
		return Hex.toHexString(dataArry);
    }
        
    
    /**
     * @Description 私钥解密  
     * @Author msx
     * @param privateKeyHex 16进制私钥
     * @param cipherData    16进制密文数据
     * @param cipherMode         1 - C1C3C2,0 - C1C2C3,默认为1
     * @return String       原文
     * @throws InvalidCipherTextException 
     * @throws UnsupportedEncodingException 
     */
    public static byte[] decrypt(byte[] cipherDataArry,PrivateKey privateKey,int cipherMode) {
    	 if(cipherMode==1)
    	 {
    		 cipherDataArry=changeC1C3C2ToC1C2C3(cipherDataArry);
    	 }
    	 BCECPrivateKey localECPrivateKey = (BCECPrivateKey) privateKey;	 
         ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(localECPrivateKey.getD(), ecDomainParameters);
         SM2Engine sm2Engine = new SM2Engine();
         sm2Engine.init(false, ecPrivateKeyParameters);
         try {
             return sm2Engine.processBlock(cipherDataArry, 0, cipherDataArry.length);
         } catch (Exception e) {
//             throw new RuntimeException(e);
 			return null;
         } 
    }
 
    
    
 
    /**
     * @Description 公钥字符串转换为 BCECPublicKey 公钥对象
     * @Author msx
     * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除)
     * @return BCECPublicKey SM2公钥对象
     */
    public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
        //截取64字节有效的SM2公钥(如果公钥首个字节为0x04)
        if (pubKeyHex.length() > 128) {
            pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
        }
        //将公钥拆分为x,y分量(各32字节)
        String stringX = pubKeyHex.substring(0, 64);
        String stringY = pubKeyHex.substring(stringX.length());
        //将公钥x、y分量转换为BigInteger类型
        BigInteger x = new BigInteger(stringX, 16);
        BigInteger y = new BigInteger(stringY, 16);
        //通过公钥x、y分量创建椭圆曲线公钥规范
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecParameterSpec);
        //通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }
 
    /**
     * @Description 私钥字符串转换为 BCECPrivateKey 私钥对象
     * @Author msx
     * @param privateKeyHex 32字节十六进制私钥字符串
     * @return BCECPrivateKey SM2私钥对象
     */
    public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
        //将十六进制私钥字符串转换为BigInteger对象
        BigInteger d = new BigInteger(privateKeyHex, 16);
        //通过私钥和私钥域参数集创建椭圆曲线私钥规范
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec);
        //通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }
    
    /**
     *  Base64 PKCS8 privateKey.getEncoded() 转为BCECPrivateKey
     * 
     * @param  pkcs8B64    privateKey.getEncoded()  BASE64字符串
     * @return BCECPrivateKey SM2私钥对象
     */
    public static BCECPrivateKey getBCECPrivateKeyByPKCS8(String pkcs8B64) {
    	byte[] bytes = Base64.decode(pkcs8B64);
        KeyFactory keyFactory;
		try {
			keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
	        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
	        BCECPrivateKey privateKey = (BCECPrivateKey) keyFactory.generatePrivate(keySpec);
	        return privateKey;
		} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) {
			e.printStackTrace();
		}
		return null;
    }
    
 
    /**
     * 私钥签名
     * @param privateKeyHex    私钥(16进制)
     * @param contentHex      待签名内容(16进制)
     * @param userID          国密用户ID数据原文,默认:1234567812345678,参与Z值计算
     * @return                16进制签名信息
     */
    public static String sign(String privateKeyHex, String contentHex,String userID) throws CryptoException {
        //待签名内容转为字节数组
        byte[] message = Hex.decode(contentHex);
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
                sm2ECParameters.getG(), sm2ECParameters.getN());
        BigInteger privateKeyD = new BigInteger(privateKeyHex, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();
        //初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678
        try {
            sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray(userID)));
        } catch (NoSuchAlgorithmException e) {
            System.out.println("签名时出现异常:"+e);
        }
        sm2Signer.update(message, 0, message.length);
        //生成签名,签名分为两部分r和s,分别对应索引0和1的数组
        byte[] signBytes = sm2Signer.generateSignature();
        String sign = Hex.toHexString(signBytes);
        return sign;
    }
    
    private static ECPrivateKeyParameters getPrivateKeyParameters(ECPrivateKey ecPrivateKey) {
        ECParameterSpec parameterSpec = ecPrivateKey.getParameters();
        ECCurve curve = parameterSpec.getCurve();
        ECPoint g = parameterSpec.getG();
        BigInteger n = parameterSpec.getN();
        BigInteger h = parameterSpec.getH();
        ECDomainParameters domainParams = new ECDomainParameters(curve, g, n, h);
        return new ECPrivateKeyParameters(ecPrivateKey.getD(), domainParams);
    }

    
    public static byte[] sign(PrivateKey privateKey,byte[] contentArry,String userID) throws CryptoException {
    	ECPrivateKeyParameters privateKeyParameters = getPrivateKeyParameters((ECPrivateKey) privateKey);	
    	SM2Signer signer = new SM2Signer();
        ParametersWithRandom pwr = new ParametersWithRandom(privateKeyParameters, new SecureRandom());
        CipherParameters param=new ParametersWithID(pwr, userID.getBytes());
        signer.init(true, param);
        signer.update(contentArry, 0, contentArry.length);
        return signer.generateSignature();
    }
       
    /**
          *  私钥签名,userID为国密局默认参数
     * @param privateKey   私钥
     * @param contentArry  签名内容
     * @return
     * @throws CryptoException
     */
    public static byte[] sign(PrivateKey privateKey,byte[] contentArry) throws CryptoException {
        return sign(privateKey,contentArry,userID);
    }
    
    
    /**
     * 国密签名验证
     * @param privateKeyHex    私钥(16进制)
     * @param contentHex       待签名内容(16进制)
     * @return     16进制签名信息
     * @throws CryptoException
     */
    public static String sign(String privateKeyHex, String contentHex) throws CryptoException {
    	return sign(privateKeyHex,contentHex,userID);
    }
    
    
    private static ECPublicKeyParameters getPublicKeyParameters(ECPublicKey ecPublicKey) {
        ECParameterSpec parameterSpec = ecPublicKey.getParameters();
        ECCurve curve = parameterSpec.getCurve();
        ECPoint g = parameterSpec.getG();
        BigInteger n = parameterSpec.getN();
        BigInteger h = parameterSpec.getH();
        ECDomainParameters domainParams = new ECDomainParameters(curve, g, n, h);
        return new ECPublicKeyParameters(ecPublicKey.getQ(), domainParams);
    }
    
    /**
     * 
     * @param publicKeyHex  公钥(16进制)
     * @param contentHex    待签名原文(16进制)
     * @param signHex       签名值(16进制)
     * @param userID        国密用户ID数据原文,默认:1234567812345678,参与Z值计算
     * @return
     */
    public static boolean verify(String publicKeyHex, String contentHex, String signHex,String userID) {
    	//待签名内容
        byte[] message = Hex.decode(contentHex);
        byte[] signData = Hex.decode(signHex);
 
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
                sm2ECParameters.getG(),
                sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKeyHex));
        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();
        ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters, Strings.toByteArray(userID));
        sm2Signer.init(false, parametersWithID);
        sm2Signer.update(message, 0, message.length);
        
        //验证签名结果
        boolean verify = sm2Signer.verifySignature(signData);
        return verify;
    }
    
    
    public static boolean verify(X509Certificate x509Cert, byte[] contentArry, byte[] signArry,String userID) { 	
    	PublicKey  publicKey=x509Cert.getPublicKey();
    	return verify(publicKey, contentArry, signArry, userID);
    }
    
    public static boolean verify(X509Certificate x509Cert, byte[] contentArry, byte[] signArry) { 	
    	PublicKey  publicKey=x509Cert.getPublicKey();
    	return verify(publicKey, contentArry, signArry, userID);
    }
    
    public static boolean verify(PublicKey publicKey, byte[] contentArry, byte[] signArry,String userID) { 	
    	ECPublicKeyParameters ecPublicKeyParameters = getPublicKeyParameters((ECPublicKey) publicKey);
    	SM2Signer signer = new SM2Signer();
        CipherParameters param=new ParametersWithID(ecPublicKeyParameters, userID.getBytes());
        signer.init(false, param);
        signer.update(contentArry, 0, contentArry.length);
        return signer.verifySignature(signArry);
    }
    
    
    /**
     * 验证签名
     * @param publicKey        公钥(16进制)
     * @param contentHex       待签名原文(16进制)
     * @param signHex          签名值(16进制)
     * @return
     */
    public static boolean verify(String publicKeyHex, String contentHex, String signHex) {
        return verify(publicKeyHex,contentHex,signHex,userID);
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值