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();
}
}
}