0
点赞
收藏
分享

微信扫一扫

8、【java数据安全】国家商用密码介绍及数字签名、密钥交换、密钥编码格式使用示例(二)

java数据安全 系列文章

1、【java数据安全】数据安全之加密解密(base64、MD、SHA、DES、AES、IDEA、PBE、DH、RSA、EIGamal)、数字签名(DSA、ECDSA)和数字证书介绍、应用示例详细介绍 2、【java数据安全】base64与报文摘要MD(md5、sha、mac)简单介绍及应用场景、示例 3、【java数据安全】对称加密的5种(DES/3DES、AES、IDEA、PBE)常见算法的使用示例 4、【java数据安全】非对称加密算法(DH、RSA、EIGamal/DSA)的介绍、应用场景和示例 5、【java数据安全】数字签名的三种算法(RSA、DSA和ECDSA)使用示例 6、【java数据安全】数字信封介绍及实现流程 7、【java数据安全】国家商用密码介绍及对称加密、非对称加密使用示例(一) 8、【java数据安全】国家商用密码介绍及数字签名、密钥交换、密钥编码格式使用示例(二)

(文章目录)

本文介绍了国密的数字签名、密钥交换和密钥编码格式的使用示例,接上一篇文章。

一、maven依赖

本文国密算法实现是基于bouncycastle(版本1.6.0)

		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>6.9.10</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
			<version>1.11</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk15on</artifactId>
			<version>1.60</version>
		</dependency>

二、实现

1、数字签名

数字签名使用的算法是非对称加密算法,即SM2。 本文使用两种方式实现数字签名,其中一个是使用显式构建EC算法构造sm2椭圆曲线参数,一种是直接使用默认的参数构建EC算法构造密钥对。

1)、使用显式构建EC算法构造sm2椭圆曲线参数

1、构建密钥对

  • 构建密钥对
public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);

	public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
		ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
		ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
		keyGen.init(keyGenerationParams);
		return keyGen.generateKeyPair();
	}
  • ECC私钥转换为PKCS8标准的字节流 如果不转换同样可以进行签名,具体参考使用默认通过EC构建密钥对。
public static byte[] convertECPrivateKeyToPKCS8(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
		ECDomainParameters domainParams = priKey.getParameters();
		ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(), domainParams.getN(),
				domainParams.getH());
		BCECPublicKey publicKey = null;
		if (pubKey != null) {
			publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
		}
		BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey, spec,
				BouncyCastleProvider.CONFIGURATION);
		return privateKey.getEncoded();
	}

	public static BCECPrivateKey convertPKCS8ToECPrivateKey(byte[] pkcs8Key)
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
		PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key);
		KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
		return (BCECPrivateKey) kf.generatePrivate(peks);
	}

2、签名

通过密钥进行签名

public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] withId, byte[] srcData)
			throws CryptoException {
		SM2Signer signer = new SM2Signer();
		CipherParameters param = null;
		ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom());
		if (withId != null) {
			param = new ParametersWithID(pwr, withId);
		} else {
			param = pwr;
		}
		signer.init(true, param);
		signer.update(srcData, 0, srcData.length);
		return signer.generateSignature();
	}

3、验证

通过公钥进行验证

public static boolean verify(ECPublicKeyParameters pubKeyParams, byte[] withId, byte[] srcData, byte[] sign) {
		SM2Signer signer = new SM2Signer();
		CipherParameters param;
		if (withId != null) {
			param = new ParametersWithID(pubKeyParams, withId);
		} else {
			param = pubKeyParams;
		}
		signer.init(false, param);
		signer.update(srcData, 0, srcData.length);
		return signer.verifySignature(sign);
	}

4、使用示例

AsymmetricCipherKeyPair keyPair = SM2SignatrueUtil.generateKeyPair();

			ECPrivateKeyParameters priKeyParams = (ECPrivateKeyParameters) keyPair.getPrivate();
			ECPublicKeyParameters pubKeyParams = (ECPublicKeyParameters) keyPair.getPublic();

			byte[] pkcs8Bytes = SM2SignatrueUtil.convertECPrivateKeyToPKCS8(priKeyParams, pubKeyParams);
			BCECPrivateKey priKey = SM2SignatrueUtil.convertPKCS8ToECPrivateKey(pkcs8Bytes);

			byte[] sign = SM2SignatrueUtil.sign(priKey, WITH_ID, SRC_DATA);
			System.out.println("SM2 sign with withId result:\n" + ByteUtils.toHexString(sign));
			boolean flag = SM2SignatrueUtil.verify(pubKeyParams, WITH_ID, SRC_DATA, sign);

			if (!flag) {
				Assert.fail("[withId] verify failed");
			}

2)、直接使用默认的参数构建EC算法构造密钥对

本示例分为两种方式,即使用默认的with_id和指定with_id.

1、构建密钥对

public static KeyPair generateBCECKeyPair()
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
		KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
		SecureRandom random = new SecureRandom();
		ECParameterSpec parameterSpec = new ECParameterSpec(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);
		kpg.initialize(parameterSpec, random);
		return kpg.generateKeyPair();
	}

2、签名

public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] withId, byte[] srcData)
			throws CryptoException {
		SM2Signer signer = new SM2Signer();
		CipherParameters param = null;
		ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom());
		if (withId != null) {
			param = new ParametersWithID(pwr, withId);
		} else {
			param = pwr;
		}
		signer.init(true, param);
		signer.update(srcData, 0, srcData.length);
		return signer.generateSignature();
	}

下面的方法就是调用上面的方法,不同的是将with_id传入为null

public static byte[] sign(ECPrivateKeyParameters priKey, byte[] srcData) throws CryptoException {
	return sign(priKey, null, srcData);
}

3、验证

public static boolean verify(ECPublicKeyParameters pubKeyParams, byte[] withId, byte[] srcData, byte[] sign) {
	SM2Signer signer = new SM2Signer();
	CipherParameters param;
	if (withId != null) {
		param = new ParametersWithID(pubKeyParams, withId);
	} else {
		param = pubKeyParams;
	}
	signer.init(false, param);
	signer.update(srcData, 0, srcData.length);
	return signer.verifySignature(sign);
}

下面的方法就是调用上面的方法,不同的是将with_id传入为null

public static boolean verify(ECPublicKeyParameters pubKey, byte[] srcData, byte[] sign) {
	return verify(pubKey, null, srcData, sign);

}

4、使用示例

KeyPair keyPair = SM2SignatrueUtil.generateBCECKeyPair();

			ECPrivateKeyParameters priKey = SM2SignatrueUtil.convertPrivateKey((BCECPrivateKey) keyPair.getPrivate());
			ECPublicKeyParameters pubKey = SM2SignatrueUtil.convertPublicKey((BCECPublicKey) keyPair.getPublic());
			// 指定with_id
			byte[] sign = SM2SignatrueUtil.sign(priKey, WITH_ID, SRC_DATA);
			boolean flag = SM2SignatrueUtil.verify(pubKey, WITH_ID, SRC_DATA, sign);
			if (!flag) {
				Assert.fail("verify failed");
			}
			// 默认的with_id
			sign = SM2SignatrueUtil.sign(priKey, SRC_DATA);
			flag = SM2SignatrueUtil.verify(pubKey, SRC_DATA, sign);
			
			if (!flag) {
				Assert.fail("verify failed");
			}

3)完整示例

1、实现代码

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.params.ParametersWithRandom;
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.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;

import com.win.security.sm.BaseUtil;

/**
 * sm2的签名与验证
 * 
 * @author alan 2018年12月11日
 */
public class SM2SignatrueUtil extends BaseUtil {
	//////////////////////////////////////////////////////////////////////////////////////
	/*
	 * 以下为SM2推荐曲线参数
	 */
	public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
	public final static BigInteger SM2_ECC_N = CURVE.getOrder();
	public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
	public final static BigInteger SM2_ECC_GX = new BigInteger(
			"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
	public final static BigInteger SM2_ECC_GY = new BigInteger(
			"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
	public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
	public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);
	//////////////////////////////////////////////////////////////////////////////////////
	private static final String ALGO_NAME_EC = "EC";
	private static final String PEM_STRING_PUBLIC = "PUBLIC KEY";
	private static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY";

	/**
	 * 生成ECC密钥对
	 *
	 * @return ECC密钥对
	 */
	public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
		ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
		ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
		keyGen.init(keyGenerationParams);
		return keyGen.generateKeyPair();
	}

	/**
	 * 获取密钥对
	 * 
	 * @return
	 */
	public static AsymmetricCipherKeyPair generateKeyPair() {
		SecureRandom random = new SecureRandom();
		return generateKeyPair(DOMAIN_PARAMS, random);
	}

	/**
	 * 签名
	 * 
	 * @param priKey
	 * @param withId
	 * @param srcData
	 * @return
	 * @throws CryptoException
	 */
	public static byte[] sign(BCECPrivateKey priKey, byte[] withId, byte[] srcData) throws CryptoException {
		ECPrivateKeyParameters priKeyParameters = convertPrivateKey(priKey);
		return sign(priKeyParameters, withId, srcData);
	}

	/**
	 * 签名
	 * 
	 * @param priKeyParameters
	 * @param withId
	 *            可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
	 * @param srcData
	 * @return
	 * @throws CryptoException
	 */
	public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] withId, byte[] srcData)
			throws CryptoException {
		SM2Signer signer = new SM2Signer();
		CipherParameters param = null;
		ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom());
		if (withId != null) {
			param = new ParametersWithID(pwr, withId);
		} else {
			param = pwr;
		}
		signer.init(true, param);
		signer.update(srcData, 0, srcData.length);
		return signer.generateSignature();
	}

	/**
	 * 密钥转换
	 * 
	 * @param priKey
	 * @return
	 */
	public static ECPrivateKeyParameters convertPrivateKey(BCECPrivateKey ecPriKey) {
		ECParameterSpec parameterSpec = ecPriKey.getParameters();
		ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
				parameterSpec.getN(), parameterSpec.getH());
		return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters);
	}

	/**
	 * 验证
	 * 
	 * @param pubKeyParams
	 * @param withId
	 *            可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
	 * @param srcData
	 * @param sign
	 * @return
	 */
	public static boolean verify(ECPublicKeyParameters pubKeyParams, byte[] withId, byte[] srcData, byte[] sign) {
		SM2Signer signer = new SM2Signer();
		CipherParameters param;
		if (withId != null) {
			param = new ParametersWithID(pubKeyParams, withId);
		} else {
			param = pubKeyParams;
		}
		signer.init(false, param);
		signer.update(srcData, 0, srcData.length);
		return signer.verifySignature(sign);
	}

	/**
	 * 将ECC私钥转换为PKCS8标准的字节流
	 * 
	 * @param priKeyParams
	 * @param pubKeyParams
	 *            可以为空,但是如果为空的话得到的结果OpenSSL可能解析不了
	 * @return
	 */
	public static byte[] convertECPrivateKeyToPKCS8(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
		ECDomainParameters domainParams = priKey.getParameters();
		ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(), domainParams.getN(),
				domainParams.getH());
		BCECPublicKey publicKey = null;
		if (pubKey != null) {
			publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
		}
		BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey, spec,
				BouncyCastleProvider.CONFIGURATION);
		return privateKey.getEncoded();
	}

	/**
	 * 将PKCS8标准的私钥字节流转换为私钥对象
	 * 
	 * @param pkcs8Bytes
	 * @return
	 * @throws NoSuchProviderException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 */
	public static BCECPrivateKey convertPKCS8ToECPrivateKey(byte[] pkcs8Key)
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
		PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(pkcs8Key);
		KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
		return (BCECPrivateKey) kf.generatePrivate(peks);
	}

	/**
	 * 获取EC密钥对
	 * 
	 * @return
	 * @throws NoSuchProviderException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidAlgorithmParameterException
	 */
	public static KeyPair generateBCECKeyPair()
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
		KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
		SecureRandom random = new SecureRandom();
		ECParameterSpec parameterSpec = new ECParameterSpec(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);
		kpg.initialize(parameterSpec, random);
		return kpg.generateKeyPair();
	}

	/**
	 * 公钥转换
	 * 
	 * @param ecPubKey
	 * @return
	 */
	public static ECPublicKeyParameters convertPublicKey(BCECPublicKey ecPubKey) {
		ECParameterSpec parameterSpec = ecPubKey.getParameters();
		ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
				parameterSpec.getN(), parameterSpec.getH());
		return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters);
	}

	/**
	 * 签名 with_id使用默认的
	 * 
	 * @param priKey
	 * @param srcData
	 * @return
	 * @throws CryptoException
	 */
	public static byte[] sign(ECPrivateKeyParameters priKey, byte[] srcData) throws CryptoException {
		return sign(priKey, null, srcData);
	}

	/**
	 * 验证,with_id使用默认的
	 * 
	 * @param pubKey
	 * @param srcData
	 * @param sign
	 * @return
	 */
	public static boolean verify(ECPublicKeyParameters pubKey, byte[] srcData, byte[] sign) {
		return verify(pubKey, null, srcData, sign);

	}
}

2、测试类

import java.security.KeyPair;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * @author alan 2018年12月11日
 */
public class SM2SignatrueUtilTest {
	public static final byte[] WITH_ID = new byte[] { 1, 2, 3, 4 };
	public static final byte[] SRC_DATA = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

	/**
	 * 使用显式构建EC算法,构造sm2椭圆曲线参数
	 */
	@Test
	public void testECPrivateKeyPKCS8() {
		try {
			AsymmetricCipherKeyPair keyPair = SM2SignatrueUtil.generateKeyPair();

			ECPrivateKeyParameters priKeyParams = (ECPrivateKeyParameters) keyPair.getPrivate();
			ECPublicKeyParameters pubKeyParams = (ECPublicKeyParameters) keyPair.getPublic();

			byte[] pkcs8Bytes = SM2SignatrueUtil.convertECPrivateKeyToPKCS8(priKeyParams, pubKeyParams);
			BCECPrivateKey priKey = SM2SignatrueUtil.convertPKCS8ToECPrivateKey(pkcs8Bytes);

			byte[] sign = SM2SignatrueUtil.sign(priKey, WITH_ID, SRC_DATA);
			System.out.println("SM2 sign with withId result:\n" + ByteUtils.toHexString(sign));
			boolean flag = SM2SignatrueUtil.verify(pubKeyParams, WITH_ID, SRC_DATA, sign);

			if (!flag) {
				Assert.fail("[withId] verify failed");
			}
		} catch (Exception e) {
			e.printStackTrace();
			Assert.fail();
		}
	}

	/**
	 * 直接使用默认的参数构建EC算法构造密钥对
	 */
	@Test
	public void testGenerateBCECKeyPair() {
		try {
			KeyPair keyPair = SM2SignatrueUtil.generateBCECKeyPair();

			ECPrivateKeyParameters priKey = SM2SignatrueUtil.convertPrivateKey((BCECPrivateKey) keyPair.getPrivate());
			ECPublicKeyParameters pubKey = SM2SignatrueUtil.convertPublicKey((BCECPublicKey) keyPair.getPublic());
			// 指定with_id
			byte[] sign = SM2SignatrueUtil.sign(priKey, WITH_ID, SRC_DATA);
			boolean flag = SM2SignatrueUtil.verify(pubKey, WITH_ID, SRC_DATA, sign);
			if (!flag) {
				Assert.fail("verify failed");
			}
			// 默认的with_id
			sign = SM2SignatrueUtil.sign(priKey, SRC_DATA);
			flag = SM2SignatrueUtil.verify(pubKey, SRC_DATA, sign);
			
			if (!flag) {
				Assert.fail("verify failed");
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			Assert.fail();
		}
	}
}

2、密钥交换

密钥交换和一般使用国际算法中的DH类似。本实现了两种密钥交换方式,即经过确认的密钥骄交换和没有经过确认的密钥交换。SM2的密钥交换需要有密钥长度、己方固定私钥和临时私钥,对方的固定公钥和临时私钥。密钥交换即是使用己方的私钥和对方的公钥构建本地加密的密钥,然后进行加密;解密的构建密钥的过程则相反。

1)、没有经过确认的密钥

1、构建密钥

//构建密钥
	public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
		ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
		ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
		keyGen.init(keyGenerationParams);
		return keyGen.generateKeyPair();
	}
//计算密钥
	public static byte[] calculateKey(boolean initiator, int keyBits, ECPrivateKeyParameters selfStaticPriv,
			ECPrivateKeyParameters selfEphemeralPriv, byte[] selfId, ECPublicKeyParameters otherStaticPub,
			ECPublicKeyParameters otherEphemeralPub, byte[] otherId) {
		SM2KeyExchange exch = new SM2KeyExchange();
		exch.init(new ParametersWithID(
				new SM2KeyExchangePrivateParameters(initiator, selfStaticPriv, selfEphemeralPriv), selfId));
		return exch.calculateKey(keyBits,
				new ParametersWithID(new SM2KeyExchangePublicParameters(otherStaticPub, otherEphemeralPub), otherId));
	}

2、使用示例

// 己方固定私钥
			AsymmetricCipherKeyPair initiatorStaticKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters initiatorStaticPriv = (ECPrivateKeyParameters) initiatorStaticKp.getPrivate();
			ECPublicKeyParameters initiatorStaticPub = (ECPublicKeyParameters) initiatorStaticKp.getPublic();
			
			// 己方临时私钥
			AsymmetricCipherKeyPair initiatorEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters initiatorEphemeralPriv = (ECPrivateKeyParameters) initiatorEphemeralKp.getPrivate();
			ECPublicKeyParameters initiatorSEphemeralPub = (ECPublicKeyParameters) initiatorEphemeralKp.getPublic();
			
			// 对方固定公钥
			AsymmetricCipherKeyPair responderStaticKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters responderStaticPriv = (ECPrivateKeyParameters) responderStaticKp.getPrivate();
			ECPublicKeyParameters responderStaticPub = (ECPublicKeyParameters) responderStaticKp.getPublic();
			
			// 对方临时公钥
			AsymmetricCipherKeyPair responderEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters responderEphemeralPriv = (ECPrivateKeyParameters) responderEphemeralKp.getPrivate();
			ECPublicKeyParameters responderSEphemeralPub = (ECPublicKeyParameters) responderEphemeralKp.getPublic();

// 实际应用中应该是通过网络交换临时公钥,具体参考DH的密钥交换示例
			byte[] k1 = SM2KeyExchangeUtil.calculateKey(true, KEY_BITS, initiatorStaticPriv, initiatorEphemeralPriv,
					INITIATOR_ID, responderStaticPub, responderSEphemeralPub, RESPONDER_ID);
			byte[] k2 = SM2KeyExchangeUtil.calculateKey(false, KEY_BITS, responderStaticPriv, responderEphemeralPriv,
					RESPONDER_ID, initiatorStaticPub, initiatorSEphemeralPub, INITIATOR_ID);

			if (!Arrays.equals(k1, k2)) {
				Assert.fail();
			}

2)、经过确认的密钥

1、构建密钥

//构建密钥
	public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
		ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
		ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
		keyGen.init(keyGenerationParams);
		return keyGen.generateKeyPair();
	}
//计算密钥并确认
	public static ExchangeResult calculateKeyWithConfirmation(boolean initiator, int keyBits, byte[] confirmationTag,
			ECPrivateKeyParameters selfStaticPriv, ECPrivateKeyParameters selfEphemeralPriv, byte[] selfId,
			ECPublicKeyParameters otherStaticPub, ECPublicKeyParameters otherEphemeralPub, byte[] otherId) {
		SM2KeyExchange exch = new SM2KeyExchange();
		exch.init(new ParametersWithID(
				new SM2KeyExchangePrivateParameters(initiator, selfStaticPriv, selfEphemeralPriv), selfId));
		byte[][] result = exch.calculateKeyWithConfirmation(keyBits, confirmationTag,
				new ParametersWithID(new SM2KeyExchangePublicParameters(otherStaticPub, otherEphemeralPub), otherId));
		ExchangeResult confirmResult = new ExchangeResult();
		confirmResult.setKey(result[0]);
		if (initiator) {
			confirmResult.setS2(result[1]);
		} else {
			confirmResult.setS1(result[1]);
			confirmResult.setS2(result[2]);
		}
		return confirmResult;
	}
//
	public static boolean responderConfirm(byte[] s2, byte[] confirmationTag) {
		return Arrays.equals(s2, confirmationTag);
	}

2、使用示例

AsymmetricCipherKeyPair initiatorStaticKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters initiatorStaticPriv = (ECPrivateKeyParameters) initiatorStaticKp.getPrivate();
			ECPublicKeyParameters initiatorStaticPub = (ECPublicKeyParameters) initiatorStaticKp.getPublic();

			AsymmetricCipherKeyPair initiatorEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters initiatorEphemeralPriv = (ECPrivateKeyParameters) initiatorEphemeralKp.getPrivate();
			ECPublicKeyParameters initiatorSEphemeralPub = (ECPublicKeyParameters) initiatorEphemeralKp.getPublic();

			AsymmetricCipherKeyPair responderStaticKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters responderStaticPriv = (ECPrivateKeyParameters) responderStaticKp.getPrivate();
			ECPublicKeyParameters responderStaticPub = (ECPublicKeyParameters) responderStaticKp.getPublic();

			AsymmetricCipherKeyPair responderEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters responderEphemeralPriv = (ECPrivateKeyParameters) responderEphemeralKp.getPrivate();
			ECPublicKeyParameters responderSEphemeralPub = (ECPublicKeyParameters) responderEphemeralKp.getPublic();

			// 第一步应该是交换临时公钥等信息

			// 第二步响应方生成密钥和验证信息
			SM2KeyExchangeUtil.ExchangeResult responderResult = SM2KeyExchangeUtil.calculateKeyWithConfirmation(false,
					KEY_BITS, null, responderStaticPriv, responderEphemeralPriv, RESPONDER_ID, initiatorStaticPub,
					initiatorSEphemeralPub, INITIATOR_ID);

			// 第三步发起方生成密钥和验证消息,并验证响应方的验证消息
			SM2KeyExchangeUtil.ExchangeResult initiatorResult = SM2KeyExchangeUtil.calculateKeyWithConfirmation(true,
					KEY_BITS, responderResult.getS1(), initiatorStaticPriv, initiatorEphemeralPriv, INITIATOR_ID,
					responderStaticPub, responderSEphemeralPub, RESPONDER_ID);

			// 第四步响应方验证发起方的验证消息
			if (!SM2KeyExchangeUtil.responderConfirm(responderResult.getS2(), initiatorResult.getS2())) {
				Assert.fail();
			}

3)、完整示例

1、实现源码

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.agreement.SM2KeyExchange;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.params.SM2KeyExchangePrivateParameters;
import org.bouncycastle.crypto.params.SM2KeyExchangePublicParameters;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;

import lombok.Data;

/**
 * @author alan 2018年12月11日
 */
public class SM2KeyExchangeUtil {
	//////////////////////////////////////////////////////////////////////////////////////
	/*
	 * 以下为SM2推荐曲线参数
	 */
	public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
	public final static BigInteger SM2_ECC_N = CURVE.getOrder();
	public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
	public final static BigInteger SM2_ECC_GX = new BigInteger(
			"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
	public final static BigInteger SM2_ECC_GY = new BigInteger(
			"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
	public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
	public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);

	//////////////////////////////////////////////////////////////////////////////////////
	/**
	 * 生成ECC密钥对
	 *
	 * @return ECC密钥对
	 */
	public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
		ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
		ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
		keyGen.init(keyGenerationParams);
		return keyGen.generateKeyPair();
	}

	/**
	 * 生成ECC密钥对
	 *
	 * @return ECC密钥对
	 */
	public static AsymmetricCipherKeyPair generateKeyPair() {
		SecureRandom random = new SecureRandom();
		return generateKeyPair(DOMAIN_PARAMS, random);
	}

	/**
	 * @param initiator
	 *            true表示发起方,false表示响应方
	 * @param keyBits
	 *            生成的密钥长度
	 * @param selfStaticPriv
	 *            己方固定私钥
	 * @param selfEphemeralPriv
	 *            己方临时私钥
	 * @param selfId
	 *            己方ID
	 * @param otherStaticPub
	 *            对方固定公钥
	 * @param otherEphemeralPub
	 *            对方临时公钥
	 * @param otherId
	 *            对方ID
	 * @return 返回协商出的密钥,但是这个密钥是没有经过确认的
	 */
	public static byte[] calculateKey(boolean initiator, int keyBits, ECPrivateKeyParameters selfStaticPriv,
			ECPrivateKeyParameters selfEphemeralPriv, byte[] selfId, ECPublicKeyParameters otherStaticPub,
			ECPublicKeyParameters otherEphemeralPub, byte[] otherId) {
		SM2KeyExchange exch = new SM2KeyExchange();
		exch.init(new ParametersWithID(
				new SM2KeyExchangePrivateParameters(initiator, selfStaticPriv, selfEphemeralPriv), selfId));
		return exch.calculateKey(keyBits,
				new ParametersWithID(new SM2KeyExchangePublicParameters(otherStaticPub, otherEphemeralPub), otherId));
	}

	/**
	 * @param initiator
	 *            true表示发起方,false表示响应方
	 * @param keyBits
	 *            生成的密钥长度
	 * @param confirmationTag
	 *            确认信息,如果是响应方可以为null;如果是发起方则应为响应方的s1
	 * @param selfStaticPriv
	 *            己方固定私钥
	 * @param selfEphemeralPriv
	 *            己方临时私钥
	 * @param selfId
	 *            己方ID
	 * @param otherStaticPub
	 *            对方固定公钥
	 * @param otherEphemeralPub
	 *            对方临时公钥
	 * @param otherId
	 *            对方ID
	 * @return
	 */
	public static ExchangeResult calculateKeyWithConfirmation(boolean initiator, int keyBits, byte[] confirmationTag,
			ECPrivateKeyParameters selfStaticPriv, ECPrivateKeyParameters selfEphemeralPriv, byte[] selfId,
			ECPublicKeyParameters otherStaticPub, ECPublicKeyParameters otherEphemeralPub, byte[] otherId) {
		SM2KeyExchange exch = new SM2KeyExchange();
		exch.init(new ParametersWithID(
				new SM2KeyExchangePrivateParameters(initiator, selfStaticPriv, selfEphemeralPriv), selfId));
		byte[][] result = exch.calculateKeyWithConfirmation(keyBits, confirmationTag,
				new ParametersWithID(new SM2KeyExchangePublicParameters(otherStaticPub, otherEphemeralPub), otherId));
		ExchangeResult confirmResult = new ExchangeResult();
		confirmResult.setKey(result[0]);
		if (initiator) {
			confirmResult.setS2(result[1]);
		} else {
			confirmResult.setS1(result[1]);
			confirmResult.setS2(result[2]);
		}
		return confirmResult;
	}

	/**
	 * @param s2
	 * @param confirmationTag
	 *            实际上是发起方的s2
	 * @return
	 */
	public static boolean responderConfirm(byte[] s2, byte[] confirmationTag) {
		return Arrays.equals(s2, confirmationTag);
	}

	@Data
	public static class ExchangeResult {
		private byte[] key;
		// 发起方没有s1
		private byte[] s1;
		private byte[] s2;

	}
}

2、测试类

import java.util.Arrays;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * @author alan 2018年12月11日
 */
public class SM2KeyExchangeUtilTest {
	private static final byte[] INITIATOR_ID = "ABCDEFG1234".getBytes();
	private static final byte[] RESPONDER_ID = "1234567ABCD".getBytes();
	private static final int KEY_BITS = 128;

	@Test
	public void testCaculateKey() {
		try {
			// 己方固定私钥
			AsymmetricCipherKeyPair initiatorStaticKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters initiatorStaticPriv = (ECPrivateKeyParameters) initiatorStaticKp.getPrivate();
			ECPublicKeyParameters initiatorStaticPub = (ECPublicKeyParameters) initiatorStaticKp.getPublic();
			
			// 己方临时私钥
			AsymmetricCipherKeyPair initiatorEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters initiatorEphemeralPriv = (ECPrivateKeyParameters) initiatorEphemeralKp.getPrivate();
			ECPublicKeyParameters initiatorSEphemeralPub = (ECPublicKeyParameters) initiatorEphemeralKp.getPublic();
			
			// 对方固定公钥
			AsymmetricCipherKeyPair responderStaticKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters responderStaticPriv = (ECPrivateKeyParameters) responderStaticKp.getPrivate();
			ECPublicKeyParameters responderStaticPub = (ECPublicKeyParameters) responderStaticKp.getPublic();
			
			// 对方临时公钥
			AsymmetricCipherKeyPair responderEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters responderEphemeralPriv = (ECPrivateKeyParameters) responderEphemeralKp.getPrivate();
			ECPublicKeyParameters responderSEphemeralPub = (ECPublicKeyParameters) responderEphemeralKp.getPublic();

			// 实际应用中应该是通过网络交换临时公钥,具体参考DH的密钥交换示例
			byte[] k1 = SM2KeyExchangeUtil.calculateKey(true, KEY_BITS, initiatorStaticPriv, initiatorEphemeralPriv,
					INITIATOR_ID, responderStaticPub, responderSEphemeralPub, RESPONDER_ID);
			byte[] k2 = SM2KeyExchangeUtil.calculateKey(false, KEY_BITS, responderStaticPriv, responderEphemeralPriv,
					RESPONDER_ID, initiatorStaticPub, initiatorSEphemeralPub, INITIATOR_ID);

			if (!Arrays.equals(k1, k2)) {
				Assert.fail();
			}

		} catch (Exception ex) {
			Assert.fail();
		}
	}

	@Test
	public void testCalculateKeyWithConfirmation() {
		try {
			AsymmetricCipherKeyPair initiatorStaticKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters initiatorStaticPriv = (ECPrivateKeyParameters) initiatorStaticKp.getPrivate();
			ECPublicKeyParameters initiatorStaticPub = (ECPublicKeyParameters) initiatorStaticKp.getPublic();

			AsymmetricCipherKeyPair initiatorEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters initiatorEphemeralPriv = (ECPrivateKeyParameters) initiatorEphemeralKp.getPrivate();
			ECPublicKeyParameters initiatorSEphemeralPub = (ECPublicKeyParameters) initiatorEphemeralKp.getPublic();

			AsymmetricCipherKeyPair responderStaticKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters responderStaticPriv = (ECPrivateKeyParameters) responderStaticKp.getPrivate();
			ECPublicKeyParameters responderStaticPub = (ECPublicKeyParameters) responderStaticKp.getPublic();

			AsymmetricCipherKeyPair responderEphemeralKp = SM2KeyExchangeUtil.generateKeyPair();
			ECPrivateKeyParameters responderEphemeralPriv = (ECPrivateKeyParameters) responderEphemeralKp.getPrivate();
			ECPublicKeyParameters responderSEphemeralPub = (ECPublicKeyParameters) responderEphemeralKp.getPublic();

			// 第一步应该是交换临时公钥等信息

			// 第二步响应方生成密钥和验证信息
			SM2KeyExchangeUtil.ExchangeResult responderResult = SM2KeyExchangeUtil.calculateKeyWithConfirmation(false,
					KEY_BITS, null, responderStaticPriv, responderEphemeralPriv, RESPONDER_ID, initiatorStaticPub,
					initiatorSEphemeralPub, INITIATOR_ID);

			// 第三步发起方生成密钥和验证消息,并验证响应方的验证消息
			SM2KeyExchangeUtil.ExchangeResult initiatorResult = SM2KeyExchangeUtil.calculateKeyWithConfirmation(true,
					KEY_BITS, responderResult.getS1(), initiatorStaticPriv, initiatorEphemeralPriv, INITIATOR_ID,
					responderStaticPub, responderSEphemeralPub, RESPONDER_ID);

			// 第四步响应方验证发起方的验证消息
			if (!SM2KeyExchangeUtil.responderConfirm(responderResult.getS2(), initiatorResult.getS2())) {
				Assert.fail();
			}

		} catch (Exception ex) {
			Assert.fail();
		}
	}
}

3、密钥编码多种格式

针对密钥进行不同的编码方式。

1)、构建密钥

public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
		ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
		ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
		keyGen.init(keyGenerationParams);
		return keyGen.generateKeyPair();
	}

2)、DER

	/**
	 * 将ECC私钥转换为PKCS8标准的字节流
	 *
	 * @param priKey
	 * @param pubKey
	 *            可以为空,但是如果为空的话得到的结果OpenSSL可能解析不了
	 * @return
	 */
	public static byte[] convertECPrivateKeyToPKCS8(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey) {
		ECDomainParameters domainParams = priKey.getParameters();
		ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(), domainParams.getN(),
				domainParams.getH());
		BCECPublicKey publicKey = null;
		if (pubKey != null) {
			publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
		}
		BCECPrivateKey privateKey = new BCECPrivateKey(ALGO_NAME_EC, priKey, publicKey, spec,
				BouncyCastleProvider.CONFIGURATION);
		return privateKey.getEncoded();
	}

3)、PEM

private static String convertEncodedDataToPEM(String type, byte[] encodedData) throws IOException {
		ByteArrayOutputStream bOut = new ByteArrayOutputStream();
		PemWriter pWrt = new PemWriter(new OutputStreamWriter(bOut));
		try {
			PemObject pemObj = new PemObject(type, encodedData);
			pWrt.writeObject(pemObj);
		} finally {
			pWrt.close();
		}
		return new String(bOut.toByteArray());
	}

4)、SEC1

	/**
	 * 将ECC私钥转换为SEC1标准的字节流 openssl d2i_ECPrivateKey函数要求的DER编码的私钥也是SEC1标准的,
	 * 这个工具函数的主要目的就是为了能生成一个openssl可以直接“识别”的ECC私钥. 相对RSA私钥的PKCS1标准,ECC私钥的标准为SEC1
	 *
	 * @param priKey
	 * @param pubKey
	 * @return
	 * @throws IOException
	 */
	public static byte[] convertECPrivateKeyToSEC1(ECPrivateKeyParameters priKey, ECPublicKeyParameters pubKey)
			throws IOException {
		byte[] pkcs8Bytes = convertECPrivateKeyToPKCS8(priKey, pubKey);
		PrivateKeyInfo pki = PrivateKeyInfo.getInstance(pkcs8Bytes);
		ASN1Encodable encodable = pki.parsePrivateKey();
		ASN1Primitive primitive = encodable.toASN1Primitive();
		byte[] sec1Bytes = primitive.getEncoded();
		return sec1Bytes;
	}

5)、使用示例

AsymmetricCipherKeyPair keyPair = KeyPairEncoding.generateKeyPair();
			ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate();
			ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic();

			byte[] priKeyPkcs8Der = KeyPairEncoding.convertECPrivateKeyToPKCS8(priKey, pubKey);
			System.out.println("private key pkcs8 der length:" + priKeyPkcs8Der.length);
			System.out.println("private key pkcs8 der:" + ByteUtils.toHexString(priKeyPkcs8Der));
			FileUtil.writeFile("ec.pkcs8.pri.der", priKeyPkcs8Der);

			String priKeyPkcs8Pem = KeyPairEncoding.convertECPrivateKeyPKCS8ToPEM(priKeyPkcs8Der);
			FileUtil.writeFile("ec.pkcs8.pri.pem", priKeyPkcs8Pem.getBytes("UTF-8"));
			byte[] priKeyFromPem = KeyPairEncoding.convertECPrivateKeyPEMToPKCS8(priKeyPkcs8Pem);
			if (!Arrays.equals(priKeyFromPem, priKeyPkcs8Der)) {
				throw new Exception("priKeyFromPem != priKeyPkcs8Der");
			}

			BCECPrivateKey newPriKey = KeyPairEncoding.convertPKCS8ToECPrivateKey(priKeyPkcs8Der);

			byte[] priKeyPkcs1Der = KeyPairEncoding.convertECPrivateKeyToSEC1(priKey, pubKey);
			System.out.println("private key pkcs1 der length:" + priKeyPkcs1Der.length);
			System.out.println("private key pkcs1 der:" + ByteUtils.toHexString(priKeyPkcs1Der));
			FileUtil.writeFile("ec.pkcs1.pri", priKeyPkcs1Der);

			byte[] pubKeyX509Der = KeyPairEncoding.convertECPublicKeyToX509(pubKey);
			System.out.println("public key der length:" + pubKeyX509Der.length);
			System.out.println("public key der:" + ByteUtils.toHexString(pubKeyX509Der));
			FileUtil.writeFile("ec.x509.pub.der", pubKeyX509Der);

			String pubKeyX509Pem = KeyPairEncoding.convertECPublicKeyX509ToPEM(pubKeyX509Der);
			FileUtil.writeFile("ec.x509.pub.pem", pubKeyX509Pem.getBytes("UTF-8"));
			byte[] pubKeyFromPem = KeyPairEncoding.convertECPublicKeyPEMToX509(pubKeyX509Pem);
			if (!Arrays.equals(pubKeyFromPem, pubKeyX509Der)) {
				throw new Exception("pubKeyFromPem != pubKeyX509Der");
			}

6)、完整示例

1、实现源码

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
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.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;

/**
 * DER – 辨别编码规则 (DER) 可包含所有私钥、公钥和证书。它是大多数浏览器的缺省格式,并按 ASN1 DER 格式存储。<br>
 * 它是无报头的 - PEM是用文本报头包围的 DER
 * 
 * @author alan 2018年12月11日
 */
public class DerSM2Signatrue {
	//////////////////////////////////////////////////////////////////////////////////////
	/*
	 * 以下为SM2推荐曲线参数
	 */
	public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
	public final static BigInteger SM2_ECC_N = CURVE.getOrder();
	public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
	public final static BigInteger SM2_ECC_GX = new BigInteger(
			"32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
	public final static BigInteger SM2_ECC_GY = new BigInteger(
			"BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
	public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
	public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, SM2_ECC_N, SM2_ECC_H);
	//////////////////////////////////////////////////////////////////////////////////////
	public static final int SM3_DIGEST_LENGTH = 32;

	/**
	 * 生成ECC密钥对
	 *
	 * @return ECC密钥对
	 */
	public static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random) {
		ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters, random);
		ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
		keyGen.init(keyGenerationParams);
		return keyGen.generateKeyPair();
	}

	/**
	 * 生成ECC密钥对
	 *
	 * @return ECC密钥对
	 */
	public static AsymmetricCipherKeyPair generateKeyPair() {
		SecureRandom random = new SecureRandom();
		return generateKeyPair(DOMAIN_PARAMS, random);
	}

	/**
	 * @param priKey
	 * @param withId
	 * @param srcData
	 * @return
	 * @throws CryptoException
	 */
	public static byte[] sign(ECPrivateKeyParameters priKey, byte[] withId, byte[] srcData) throws CryptoException {
		SM2Signer signer = new SM2Signer();
		CipherParameters param = null;
		ParametersWithRandom pwr = new ParametersWithRandom(priKey, new SecureRandom());
		if (withId != null) {
			param = new ParametersWithID(pwr, withId);
		} else {
			param = pwr;
		}
		signer.init(true, param);
		signer.update(srcData, 0, srcData.length);
		return signer.generateSignature();
	}

	/**
	 * 将DER编码的SM2签名解析成64字节的纯R+S字节流
	 * 
	 * @param sign
	 * @return
	 */
	public static byte[] decodeDERSM2Sign(byte[] sign) {
		ASN1Sequence as = DERSequence.getInstance(sign);
		byte[] rBytes = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
		byte[] sBytes = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
		// 由于大数的补0规则,所以可能会出现33个字节的情况,要修正回32个字节
		rBytes = fixTo32Bytes(rBytes);
		sBytes = fixTo32Bytes(sBytes);
		byte[] rawSign = new byte[rBytes.length + sBytes.length];
		System.arraycopy(rBytes, 0, rawSign, 0, rBytes.length);
		System.arraycopy(sBytes, 0, rawSign, rBytes.length, sBytes.length);
		return rawSign;
	}

	/**
	 * @param rBytes
	 * @return
	 */
	private static byte[] fixTo32Bytes(byte[] src) {
		final int fixLen = 32;
		if (src.length == fixLen) {
			return src;
		}

		byte[] result = new byte[fixLen];
		if (src.length > fixLen) {
			System.arraycopy(src, src.length - result.length, result, 0, result.length);
		} else {
			System.arraycopy(src, result.length - src.length, result, 0, src.length);
		}
		return result;
	}

	/**
	 * 把64字节的纯R+S字节流转换成DER编码字节流
	 * 
	 * @param rawSign
	 * @return
	 * @throws IOException
	 */
	public static byte[] encodeSM2SignToDER(byte[] rawSign) throws IOException {
		// 要保证大数是正数
		BigInteger r = new BigInteger(1, extractBytes(rawSign, 0, 32));
		BigInteger s = new BigInteger(1, extractBytes(rawSign, 32, 32));
		ASN1EncodableVector v = new ASN1EncodableVector();
		v.add(new ASN1Integer(r));
		v.add(new ASN1Integer(s));
		return new DERSequence(v).getEncoded(ASN1Encoding.DER);
	}

	/**
	 * @param rawSign
	 * @param i
	 * @param j
	 * @return
	 */
	private static byte[] extractBytes(byte[] rawSign, int offset, int length) {
		byte[] result = new byte[length];
		System.arraycopy(rawSign, offset, result, 0, result.length);
		return result;
	}

	/**
	 * @param pubKey
	 * @param withId
	 * @param srcData
	 * @param sign
	 * @return
	 */
	public static boolean verify(ECPublicKeyParameters pubKey, byte[] withId, byte[] srcData, byte[] sign) {
		SM2Signer signer = new SM2Signer();
		CipherParameters param;
		if (withId != null) {
			param = new ParametersWithID(pubKey, withId);
		} else {
			param = pubKey;
		}
		signer.init(false, param);
		signer.update(srcData, 0, srcData.length);
		return signer.verifySignature(sign);
	}

	/**
	 * @param priKey
	 * @param srcData
	 * @return
	 * @throws CryptoException
	 */
	public static byte[] sign(ECPrivateKeyParameters priKey, byte[] srcData) throws CryptoException {
		return sign(priKey, null, srcData);
	}

	/**
	 * @param pubKey
	 * @param srcData
	 * @param sign
	 * @return
	 */
	public static boolean verify(ECPublicKeyParameters pubKey, byte[] srcData, byte[] sign) {
		return verify(pubKey, null, srcData, sign);

	}

	/**
	 * ECC公钥加密
	 *
	 * @param pubKeyParameters
	 *            ECC公钥
	 * @param srcData
	 *            源数据
	 * @return SM2密文,实际包含三部分:ECC公钥、真正的密文、公钥和原文的SM3-HASH值
	 * @throws InvalidCipherTextException
	 */
	public static byte[] encrypt(ECPublicKeyParameters pubKeyParameters, byte[] srcData)
			throws InvalidCipherTextException {
		SM2Engine engine = new SM2Engine();
		ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
		engine.init(true, pwr);
		return engine.processBlock(srcData, 0, srcData.length);
	}

	/**
	 * DER编码C1C2C3密文(根据《SM2密码算法使用规范》 GM/T 0009-2012)
	 *
	 * @param cipher
	 * @return
	 * @throws IOException
	 */
	public static byte[] encodeSM2CipherToDER(byte[] cipher) throws IOException {
		int curveLength = getCurveLength(DOMAIN_PARAMS);
		return encodeSM2CipherToDER(curveLength, SM3_DIGEST_LENGTH, cipher);
	}

	public static int getCurveLength(ECDomainParameters domainParams) {
		return (domainParams.getCurve().getFieldSize() + 7) / 8;
	}

	/**
	 * DER编码C1C2C3密文(根据《SM2密码算法使用规范》 GM/T 0009-2012)
	 *
	 * @param curveLength
	 * @param digestLength
	 * @param cipher
	 * @return
	 * @throws IOException
	 */
	public static byte[] encodeSM2CipherToDER(int curveLength, int digestLength, byte[] cipher) throws IOException {
		int startPos = 1;

		byte[] c1x = new byte[curveLength];
		System.arraycopy(cipher, startPos, c1x, 0, c1x.length);
		startPos += c1x.length;

		byte[] c1y = new byte[curveLength];
		System.arraycopy(cipher, startPos, c1y, 0, c1y.length);
		startPos += c1y.length;

		byte[] c2 = new byte[cipher.length - c1x.length - c1y.length - 1 - digestLength];
		System.arraycopy(cipher, startPos, c2, 0, c2.length);
		startPos += c2.length;

		byte[] c3 = new byte[digestLength];
		System.arraycopy(cipher, startPos, c3, 0, c3.length);

		ASN1Encodable[] arr = new ASN1Encodable[4];
		arr[0] = new ASN1Integer(c1x);
		arr[1] = new ASN1Integer(c1y);
		arr[2] = new DEROctetString(c3);
		arr[3] = new DEROctetString(c2);
		DERSequence ds = new DERSequence(arr);
		return ds.getEncoded(ASN1Encoding.DER);
	}

	/**
	 * 解DER编码密文(根据《SM2密码算法使用规范》 GM/T 0009-2012)
	 *
	 * @param derCipher
	 * @return
	 */
	public static byte[] decodeDERSM2Cipher(byte[] derCipher) {
		ASN1Sequence as = DERSequence.getInstance(derCipher);
		byte[] c1x = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
		byte[] c1y = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
		byte[] c3 = ((DEROctetString) as.getObjectAt(2)).getOctets();
		byte[] c2 = ((DEROctetString) as.getObjectAt(3)).getOctets();

		int pos = 0;
		byte[] cipherText = new byte[1 + c1x.length + c1y.length + c2.length + c3.length];

		final byte uncompressedFlag = 0x04;
		cipherText[0] = uncompressedFlag;
		pos += 1;

		System.arraycopy(c1x, 0, cipherText, pos, c1x.length);
		pos += c1x.length;

		System.arraycopy(c1y, 0, cipherText, pos, c1y.length);
		pos += c1y.length;

		System.arraycopy(c2, 0, cipherText, pos, c2.length);
		pos += c2.length;

		System.arraycopy(c3, 0, cipherText, pos, c3.length);

		return cipherText;
	}

	/**
	 * ECC私钥解密
	 *
	 * @param priKeyParameters
	 *            ECC私钥
	 * @param sm2Cipher
	 *            SM2密文,实际包含三部分:ECC公钥、真正的密文、公钥和原文的SM3-HASH值
	 * @return 原文
	 * @throws InvalidCipherTextException
	 */
	public static byte[] decrypt(ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
			throws InvalidCipherTextException {
		SM2Engine engine = new SM2Engine();
		engine.init(false, priKeyParameters);
		return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
	}
}

2、帮助类

import java.io.IOException;
import java.io.RandomAccessFile;

/**
 * @author alan 2018年12月11日
 */
public class FileUtil {
	public static void writeFile(String filePath, byte[] data) throws IOException {
		RandomAccessFile raf = null;
		try {
			raf = new RandomAccessFile(filePath, "rw");
			raf.write(data);
		} finally {
			if (raf != null) {
				raf.close();
			}
		}
	}

	public static byte[] readFile(String filePath) throws IOException {
		RandomAccessFile raf = null;
		byte[] data;
		try {
			raf = new RandomAccessFile(filePath, "r");
			data = new byte[(int) raf.length()];
			raf.read(data);
			return data;
		} finally {
			if (raf != null) {
				raf.close();
			}
		}
	}
}

3、测试类

import java.util.Arrays;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * @author alan 2018年12月11日
 */
public class DerSM2SignatrueTest {
	public static final byte[] WITH_ID = new byte[] { 1, 2, 3, 4 };
	public static final byte[] SRC_DATA = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };

	@Test
	public void testSignAndVerify() {
		try {
			AsymmetricCipherKeyPair keyPair = DerSM2Signatrue.generateKeyPair();
			ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate();
			ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic();

			
			System.out.println("Pri Hex:" + ByteUtils.toHexString(priKey.getD().toByteArray()).toUpperCase());
			System.out.println(
					"Pub X Hex:" + ByteUtils.toHexString(pubKey.getQ().getAffineXCoord().getEncoded()).toUpperCase());
			System.out.println(
					"Pub X Hex:" + ByteUtils.toHexString(pubKey.getQ().getAffineYCoord().getEncoded()).toUpperCase());
			System.out.println("Pub Point Hex:" + ByteUtils.toHexString(pubKey.getQ().getEncoded(false)).toUpperCase());

			
			
			byte[] sign = DerSM2Signatrue.sign(priKey, WITH_ID, SRC_DATA);
			System.out.println("SM2 sign with withId result:\n" + ByteUtils.toHexString(sign));
			byte[] rawSign = DerSM2Signatrue.decodeDERSM2Sign(sign);
			sign = DerSM2Signatrue.encodeSM2SignToDER(rawSign);
			System.out.println("SM2 sign with withId result:\n" + ByteUtils.toHexString(sign));
			boolean flag = DerSM2Signatrue.verify(pubKey, WITH_ID, SRC_DATA, sign);
			if (!flag) {
				Assert.fail("verify failed");
			}

			
			
			sign = DerSM2Signatrue.sign(priKey, SRC_DATA);
			System.out.println("SM2 sign without withId result:\n" + ByteUtils.toHexString(sign));
			flag = DerSM2Signatrue.verify(pubKey, SRC_DATA, sign);
			if (!flag) {
				Assert.fail("verify failed");
			}
			
			
		} catch (Exception ex) {
			ex.printStackTrace();
			Assert.fail();
		}
	}
	
	@Test
	public void testEncodeSM2CipherToDER() {
		try {
			AsymmetricCipherKeyPair keyPair = DerSM2Signatrue.generateKeyPair();
			ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate();
			ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic();

			byte[] encryptedData = DerSM2Signatrue.encrypt(pubKey, SRC_DATA);

			byte[] derCipher = DerSM2Signatrue.encodeSM2CipherToDER(encryptedData);
			FileUtil.writeFile("derCipher.dat", derCipher);

			byte[] decryptedData = DerSM2Signatrue.decrypt(priKey, DerSM2Signatrue.decodeDERSM2Cipher(derCipher));
			if (!Arrays.equals(decryptedData, SRC_DATA)) {
				Assert.fail();
			}

			Assert.assertTrue(true);
		} catch (Exception ex) {
			ex.printStackTrace();
			Assert.fail();
		}
	}
	
	
}
举报

相关推荐

0 条评论