共享API对接加签验签文档
1. 设计概述
1.1 编写目的
当我们在设计共享API时,需要对用户的请求进行校验,这时候可以采用加签验签的方式。以下文档为对接示例;
1.2 使用范围
文档使用面向的对象与项目范围
1.3 流程说明
私钥:
- 获取私钥:
请求方接入之前需要生成对应的公私钥,私钥请求方保存,公钥提供给平台;
- 调用接口
调用接口,传入appId,nonce,timestamp,appSign,param参数,获取数据。
2.接口说明
2.1 调用接口
请求方调用接口
**请求方式:**POST
请求地址:
请求参数:
{
"appId":"your_app_id",
"appSign":"your_appSign",
"nonce":"your_nonce",
"timestamp":"timestamp",
"param":"{\"paramKey1\":\"paramVaule1\"}"
}
参数说明:
参数 | 类型 | 必须 | 说明 |
---|---|---|---|
appId | String | 是 | 应用id |
nonce | String | 是 | 随机字符串,具体规则见下文 |
timestamp | String | 是 | 时间戳 |
appSign | String | 是 | 应用签名,具体生成方法见下文appSign参数说明 |
param | String | 是 | 业务参数json,“param”:“{“paramKey1”:“paramVaule1”}” |
返回结果:
参数说明:
参数 | 说明 |
---|---|
code | 状态码 |
message | 请求信息 |
success | 请求状态 |
time | 请求时间 |
data | 不同类型的数据对象 |
data.Value | 返回数据详情 |
3.数据参数说明
3.1 API接口请求体必传信息结构(这里是将这些参数作为body体传入,也可以放在header里传入)
参数 | 类型 | 必须 | 说明 |
---|---|---|---|
appId | String | 是 | 应用id |
nonce | String | 是 | 随机字符,具体规则见下文 |
timestamp | String | 是 | 时间戳 |
appSign | String | 是 | 应用签名,具体签名方式见下文 |
3.2 各参数生成规则
3.2.1 nonce参数
13位随机字符串,包含数字、英文字符、区分大小写
3.2.2 timestamp参数
13位时间戳
3.2.3 sign参数
对原始请求体中param的json参数使用SM3摘要算法计算摘要值生成sign。
示例:
String param = "{\"paramKey1\":\"paramValue1\"}";
SM3 sm3 = new SM3();
String sign = sm3.digestHex(param);
3.2.4 appSign参数
-
首先,将appId,sign,nonce,timestamp,appSecret等参数以key=value的方式拼接(注意排序),并以&符号拼接各参数,示例如下:
Map<String, String> data = Maps.newHashMap(); data.put("appId", appId); data.put("nonce", nonce); data.put("sign", sign); data.put("timestamp", timestamp); Set<String> keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); Arrays.sort(keyArray); //排序 StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 sb.append(k).append("=").append(data.get(k).trim()).append("&"); } //拼接appSecret sb.append("appSecret=").append(appSecret); 示例: appId=6&nonce=2207411320574&sign=205ebe156dd668926602b78d71453ada08c802230c4895066d35fc7e15ac6ded×tamp=1701346054187&appSecret=24ab9d305bd04f88aafc059b2be950fe
-
然后,对拼接的参数进行sm2私钥加签,得到签名appSign。
下方为java使用sm2进行私钥加签的代码,可以进行同步修改
/** * 签名 * * @param priKeyParameters 私钥 * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() * @param srcData 源数据 * @return DER编码后的签名值 * @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(); }
注意:appSecret和sm2私钥应保存在服务端,避免暴露在前端代码中
4. 加签验签工具类提供
4.1 SM2Util
package com.zx.test.openapi.utils.gm;
import org.bouncycastle.asn1.*;
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.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECFieldFp;
import java.security.spec.EllipticCurve;
public class SM2Util extends GMBaseUtil {
//
/*
* 以下为SM2推荐曲线参数
*/
public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
public final static BigInteger SM2_ECC_P = CURVE.getQ();
public final static BigInteger SM2_ECC_A = CURVE.getA().toBigInteger();
public final static BigInteger SM2_ECC_B = CURVE.getB().toBigInteger();
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 CURVE_LEN = BCECUtil.getCurveLength(DOMAIN_PARAMS);
//
public static final EllipticCurve JDK_CURVE = new EllipticCurve(new ECFieldFp(SM2_ECC_P), SM2_ECC_A, SM2_ECC_B);
public static final java.security.spec.ECPoint JDK_G_POINT = new java.security.spec.ECPoint(
G_POINT.getAffineXCoord().toBigInteger(), G_POINT.getAffineYCoord().toBigInteger());
public static final java.security.spec.ECParameterSpec JDK_EC_SPEC = new java.security.spec.ECParameterSpec(
JDK_CURVE, JDK_G_POINT, SM2_ECC_N, SM2_ECC_H.intValue());
//
public static final int SM3_DIGEST_LENGTH = 32;
/**
* 生成ECC密钥对
*
* @return ECC密钥对
*/
public static AsymmetricCipherKeyPair generateKeyPairParameter() {
SecureRandom random = new SecureRandom();
return BCECUtil.generateKeyPairParameter(DOMAIN_PARAMS, random);
}
/**
* 生成ECC密钥对
*
* @return
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws InvalidAlgorithmParameterException
*/
public static KeyPair generateKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException {
SecureRandom random = new SecureRandom();
return BCECUtil.generateKeyPair(DOMAIN_PARAMS, random);
}
/**
* 只获取私钥里的d值,32字节
*
* @param privateKey
* @return
*/
public static byte[] getRawPrivateKey(BCECPrivateKey privateKey) {
return fixToCurveLengthBytes(privateKey.getD().toByteArray());
}
/**
* 只获取公钥里的XY分量,64字节
*
* @param publicKey
* @return 64字节数组
*/
public static byte[] getRawPublicKey(BCECPublicKey publicKey) {
byte[] src65 = publicKey.getQ().getEncoded(false);
byte[] rawXY = new byte[CURVE_LEN * 2];//SM2的话这里应该是64字节
System.arraycopy(src65, 1, rawXY, 0, rawXY.length);
return rawXY;
}
/**
* @param pubKey 公钥
* @param srcData 原文
* @return 默认输出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @throws InvalidCipherTextException
*/
public static byte[] encrypt(BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
return encrypt(Mode.C1C3C2, pubKeyParameters, srcData);
}
/**
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param pubKey 公钥
* @param srcData 原文
* @return 根据mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @throws InvalidCipherTextException
*/
public static byte[] encrypt(Mode mode, BCECPublicKey pubKey, byte[] srcData) throws InvalidCipherTextException {
ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
return encrypt(mode, pubKeyParameters, srcData);
}
/**
* @param pubKeyParameters 公钥
* @param srcData 原文
* @return 默认输出C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @throws InvalidCipherTextException
*/
public static byte[] encrypt(ECPublicKeyParameters pubKeyParameters, byte[] srcData)
throws InvalidCipherTextException {
return encrypt(Mode.C1C3C2, pubKeyParameters, srcData);
}
/**
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param pubKeyParameters 公钥
* @param srcData 原文
* @return 根据mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @throws InvalidCipherTextException
*/
public static byte[] encrypt(Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
throws InvalidCipherTextException {
SM2Engine engine = new SM2Engine(mode);
ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
engine.init(true, pwr);
return engine.processBlock(srcData, 0, srcData.length);
}
/**
* @param priKey 私钥
* @param sm2Cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return 原文。SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。
* @throws InvalidCipherTextException
*/
public static byte[] decrypt(BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher);
}
/**
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param priKey 私钥
* @param sm2Cipher 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return 原文。SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。
* @throws InvalidCipherTextException
*/
public static byte[] decrypt(Mode mode, BCECPrivateKey priKey, byte[] sm2Cipher) throws InvalidCipherTextException {
ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
return decrypt(mode, priKeyParameters, sm2Cipher);
}
/**
* @param priKeyParameters 私钥
* @param sm2Cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return 原文。SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。
* @throws InvalidCipherTextException
*/
public static byte[] decrypt(ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
throws InvalidCipherTextException {
return decrypt(Mode.C1C3C2, priKeyParameters, sm2Cipher);
}
/**
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param priKeyParameters 私钥
* @param sm2Cipher 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return 原文。SM2解密返回了数据则一定是原文,因为SM2自带校验,如果密文被篡改或者密钥对不上,都是会直接报异常的。
* @throws InvalidCipherTextException
*/
public static byte[] decrypt(Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
throws InvalidCipherTextException {
SM2Engine engine = new SM2Engine(mode);
engine.init(false, priKeyParameters);
return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
}
/**
* 分解SM2密文
*
* @param cipherText 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return
* @throws Exception
*/
public static SM2Cipher parseSM2Cipher(byte[] cipherText) throws Exception {
int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
return parseSM2Cipher(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipherText);
}
/**
* 分解SM2密文
*
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param cipherText 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return
*/
public static SM2Cipher parseSM2Cipher(Mode mode, byte[] cipherText) throws Exception {
int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
return parseSM2Cipher(mode, curveLength, SM3_DIGEST_LENGTH, cipherText);
}
/**
* @param curveLength 曲线长度,SM2的话就是256位。
* @param digestLength 摘要长度,如果是SM2的话因为默认使用SM3摘要,SM3摘要长度为32字节。
* @param cipherText 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return
* @throws Exception
*/
public static SM2Cipher parseSM2Cipher(
int curveLength, int digestLength, byte[] cipherText) throws Exception {
return parseSM2Cipher(Mode.C1C3C2, curveLength, digestLength, cipherText);
}
/**
* 分解SM2密文
*
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param curveLength 曲线长度,SM2的话就是256位。
* @param digestLength 摘要长度,如果是SM2的话因为默认使用SM3摘要,SM3摘要长度为32字节。
* @param cipherText 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return
*/
public static SM2Cipher parseSM2Cipher(Mode mode, int curveLength, int digestLength,
byte[] cipherText) throws Exception {
byte[] c1 = new byte[curveLength * 2 + 1];
byte[] c2 = new byte[cipherText.length - c1.length - digestLength];
byte[] c3 = new byte[digestLength];
System.arraycopy(cipherText, 0, c1, 0, c1.length);
if (mode == Mode.C1C2C3) {
System.arraycopy(cipherText, c1.length, c2, 0, c2.length);
System.arraycopy(cipherText, c1.length + c2.length, c3, 0, c3.length);
} else if (mode == Mode.C1C3C2) {
System.arraycopy(cipherText, c1.length, c3, 0, c3.length);
System.arraycopy(cipherText, c1.length + c3.length, c2, 0, c2.length);
} else {
throw new Exception("Unsupported mode:" + mode);
}
SM2Cipher result = new SM2Cipher();
result.setC1(c1);
result.setC2(c2);
result.setC3(c3);
result.setCipherText(cipherText);
return result;
}
/**
* DER编码密文
*
* @param cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return DER编码后的密文
* @throws IOException
*/
public static byte[] encodeSM2CipherToDER(byte[] cipher) throws Exception {
int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
return encodeSM2CipherToDER(Mode.C1C3C2, curveLength, SM3_DIGEST_LENGTH, cipher);
}
/**
* DER编码密文
*
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param cipher 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return 按指定mode DER编码后的密文
* @throws Exception
*/
public static byte[] encodeSM2CipherToDER(Mode mode, byte[] cipher) throws Exception {
int curveLength = BCECUtil.getCurveLength(DOMAIN_PARAMS);
return encodeSM2CipherToDER(mode, curveLength, SM3_DIGEST_LENGTH, cipher);
}
/**
* DER编码密文
*
* @param curveLength 曲线长度,SM2的话就是256位。
* @param digestLength 摘要长度,如果是SM2的话因为默认使用SM3摘要,SM3摘要长度为32字节。
* @param cipher 默认输入C1C3C2顺序的密文。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return 默认输出按C1C3C2编码的结果
* @throws IOException
*/
public static byte[] encodeSM2CipherToDER(int curveLength, int digestLength, byte[] cipher)
throws Exception {
return encodeSM2CipherToDER(Mode.C1C3C2, curveLength, digestLength, cipher);
}
/**
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param curveLength 曲线长度,SM2的话就是256位。
* @param digestLength 摘要长度,如果是SM2的话因为默认使用SM3摘要,SM3摘要长度为32字节。
* @param cipher 根据mode不同,需要输入的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @return 按指定mode DER编码后的密文
* @throws Exception
*/
public static byte[] encodeSM2CipherToDER(Mode mode, int curveLength, int digestLength, byte[] cipher)
throws Exception {
byte[] c1x = new byte[curveLength];
byte[] c1y = new byte[curveLength];
byte[] c2 = new byte[cipher.length - c1x.length - c1y.length - 1 - digestLength];
byte[] c3 = new byte[digestLength];
int startPos = 1;
System.arraycopy(cipher, startPos, c1x, 0, c1x.length);
startPos += c1x.length;
System.arraycopy(cipher, startPos, c1y, 0, c1y.length);
startPos += c1y.length;
if (mode == Mode.C1C2C3) {
System.arraycopy(cipher, startPos, c2, 0, c2.length);
startPos += c2.length;
System.arraycopy(cipher, startPos, c3, 0, c3.length);
} else if (mode == Mode.C1C3C2) {
System.arraycopy(cipher, startPos, c3, 0, c3.length);
startPos += c3.length;
System.arraycopy(cipher, startPos, c2, 0, c2.length);
} else {
throw new Exception("Unsupported mode:" + mode);
}
ASN1Encodable[] arr = new ASN1Encodable[4];
// c1x,c1y的第一个bit可能为1,这个时候要确保他们表示的大数一定是正数,所以new BigInteger符号强制设为正。
arr[0] = new ASN1Integer(new BigInteger(1, c1x));
arr[1] = new ASN1Integer(new BigInteger(1, c1y));
if (mode == Mode.C1C2C3) {
arr[2] = new DEROctetString(c2);
arr[3] = new DEROctetString(c3);
} else if (mode == Mode.C1C3C2) {
arr[2] = new DEROctetString(c3);
arr[3] = new DEROctetString(c2);
}
DERSequence ds = new DERSequence(arr);
return ds.getEncoded(ASN1Encoding.DER);
}
/**
* 解码DER密文
*
* @param derCipher 默认输入按C1C3C2顺序DER编码的密文
* @return 输出按C1C3C2排列的字节数组,C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
*/
public static byte[] decodeDERSM2Cipher(byte[] derCipher) throws Exception {
return decodeDERSM2Cipher(Mode.C1C3C2, derCipher);
}
/**
* @param mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2
* @param derCipher 根据mode输入C1C2C3或C1C3C2顺序DER编码后的密文
* @return 根据mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。
* @throws Exception
*/
public static byte[] decodeDERSM2Cipher(Mode mode, byte[] derCipher) throws Exception {
ASN1Sequence as = DERSequence.getInstance(derCipher);
byte[] c1x = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
byte[] c1y = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
// c1x,c1y可能因为大正数的补0规则在第一个有效字节前面插了一个(byte)0,变成33个字节,在这里要修正回32个字节去
c1x = fixToCurveLengthBytes(c1x);
c1y = fixToCurveLengthBytes(c1y);
byte[] c3;
byte[] c2;
if (mode == Mode.C1C2C3) {
c2 = ((DEROctetString) as.getObjectAt(2)).getOctets();
c3 = ((DEROctetString) as.getObjectAt(3)).getOctets();
} else if (mode == Mode.C1C3C2) {
c3 = ((DEROctetString) as.getObjectAt(2)).getOctets();
c2 = ((DEROctetString) as.getObjectAt(3)).getOctets();
} else {
throw new Exception("Unsupported mode:" + mode);
}
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;
if (mode == Mode.C1C2C3) {
System.arraycopy(c2, 0, cipherText, pos, c2.length);
pos += c2.length;
System.arraycopy(c3, 0, cipherText, pos, c3.length);
} else if (mode == Mode.C1C3C2) {
System.arraycopy(c3, 0, cipherText, pos, c3.length);
pos += c3.length;
System.arraycopy(c2, 0, cipherText, pos, c2.length);
}
return cipherText;
}
/**
* 签名
*
* @param priKey 私钥
* @param srcData 原文
* @return DER编码后的签名值
* @throws CryptoException
*/
public static byte[] sign(BCECPrivateKey priKey, byte[] srcData) throws CryptoException {
ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
return sign(priKeyParameters, null, srcData);
}
/**
* 签名
* 不指定withId,则默认withId为字节数组:"1234567812345678".getBytes()
*
* @param priKeyParameters 私钥
* @param srcData 原文
* @return DER编码后的签名值
* @throws CryptoException
*/
public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData) throws CryptoException {
return sign(priKeyParameters, null, srcData);
}
/**
* 私钥签名
*
* @param priKey 私钥
* @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
* @param srcData 原文
* @return DER编码后的签名值
* @throws CryptoException
*/
public static byte[] sign(BCECPrivateKey priKey, byte[] withId, byte[] srcData) throws CryptoException {
ECPrivateKeyParameters priKeyParameters = BCECUtil.convertPrivateKeyToParameters(priKey);
return sign(priKeyParameters, withId, srcData);
}
/**
* 签名
*
* @param priKeyParameters 私钥
* @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
* @param srcData 源数据
* @return DER编码后的签名值
* @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();
}
/**
* 将DER编码的SM2签名解码成64字节的纯R+S字节流
*
* @param derSign
* @return 64字节数组,前32字节为R,后32字节为S
*/
public static byte[] decodeDERSM2Sign(byte[] derSign) {
ASN1Sequence as = DERSequence.getInstance(derSign);
byte[] rBytes = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray();
byte[] sBytes = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray();
//由于大数的补0规则,所以可能会出现33个字节的情况,要修正回32个字节
rBytes = fixToCurveLengthBytes(rBytes);
sBytes = fixToCurveLengthBytes(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;
}
/**
* 把64字节的纯R+S字节数组编码成DER编码
*
* @param rawSign 64字节数组形式的SM2签名值,前32字节为R,后32字节为S
* @return DER编码后的SM2签名值
* @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 pubKey 公钥
* @param srcData 原文
* @param sign DER编码的签名值
* @return
*/
public static boolean verify(BCECPublicKey pubKey, byte[] srcData, byte[] sign) {
ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
return verify(pubKeyParameters, null, srcData, sign);
}
/**
* 验签
* 不指定withId,则默认withId为字节数组:"1234567812345678".getBytes()
*
* @param pubKeyParameters 公钥
* @param srcData 原文
* @param sign DER编码的签名值
* @return 验签成功返回true,失败返回false
*/
public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) {
return verify(pubKeyParameters, null, srcData, sign);
}
/**
* 验签
*
* @param pubKey 公钥
* @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
* @param srcData 原文
* @param sign DER编码的签名值
* @return
*/
public static boolean verify(BCECPublicKey pubKey, byte[] withId, byte[] srcData, byte[] sign) {
ECPublicKeyParameters pubKeyParameters = BCECUtil.convertPublicKeyToParameters(pubKey);
return verify(pubKeyParameters, withId, srcData, sign);
}
/**
* 验签
*
* @param pubKeyParameters 公钥
* @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes()
* @param srcData 原文
* @param sign DER编码的签名值
* @return 验签成功返回true,失败返回false
*/
public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] withId, byte[] srcData, byte[] sign) {
SM2Signer signer = new SM2Signer();
CipherParameters param;
if (withId != null) {
param = new ParametersWithID(pubKeyParameters, withId);
} else {
param = pubKeyParameters;
}
signer.init(false, param);
signer.update(srcData, 0, srcData.length);
return signer.verifySignature(sign);
}
private static byte[] extractBytes(byte[] src, int offset, int length) {
byte[] result = new byte[length];
System.arraycopy(src, offset, result, 0, result.length);
return result;
}
private static byte[] fixToCurveLengthBytes(byte[] src) {
if (src.length == CURVE_LEN) {
return src;
}
byte[] result = new byte[CURVE_LEN];
if (src.length > CURVE_LEN) {
System.arraycopy(src, src.length - result.length, result, 0, result.length);
} else {
System.arraycopy(src, 0, result, result.length - src.length, src.length);
}
return result;
}
}
4.2 SM3Util
package com.zx.test.openapi.utils.gm;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class SM3Util extends GMBaseUtil {
/**
* 计算SM3摘要值
*
* @param srcData 原文
* @return 摘要值,对于SM3算法来说是32字节
*/
public static byte[] hash(byte[] srcData) {
SM3Digest digest = new SM3Digest();
digest.update(srcData, 0, srcData.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return hash;
}
/**
* 验证摘要
*
* @param srcData 原文
* @param sm3Hash 摘要值
* @return 返回true标识验证成功,false标识验证失败
*/
public static boolean verify(byte[] srcData, byte[] sm3Hash) {
byte[] newHash = hash(srcData);
if (Arrays.equals(newHash, sm3Hash)) {
return true;
} else {
return false;
}
}
/**
* 计算SM3 Mac值
*
* @param key key值,可以是任意长度的字节数组
* @param srcData 原文
* @return Mac值,对于HMac-SM3来说是32字节
*/
public static byte[] hmac(byte[] key, byte[] srcData) {
KeyParameter keyParameter = new KeyParameter(key);
SM3Digest digest = new SM3Digest();
HMac mac = new HMac(digest);
mac.init(keyParameter);
mac.update(srcData, 0, srcData.length);
byte[] result = new byte[mac.getMacSize()];
mac.doFinal(result, 0);
return result;
}
/**
* md5加密
* @param data 原始数据
* @return 加密数据(16进制字符串)
*/
public static String md5Digest(String data) {
byte[] bytes = DigestUtils.md5Digest(data.getBytes(StandardCharsets.UTF_8));
return byte2hex(bytes);
}
/**
* byte[]转换为16进制字符串
* @param bytes 字节数组
* @return 16进制字符串
*/
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
}
4.3 SM2Cipher
package com.zx.test.openapi.utils.gm;
public class SM2Cipher {
/**
* ECC密钥
*/
private byte[] c1;
/**
* 真正的密文
*/
private byte[] c2;
/**
* 对(c1+c2)的SM3-HASH值
*/
private byte[] c3;
/**
* SM2标准的密文,即(c1+c2+c3)
*/
private byte[] cipherText;
public byte[] getC1() {
return c1;
}
public void setC1(byte[] c1) {
this.c1 = c1;
}
public byte[] getC2() {
return c2;
}
public void setC2(byte[] c2) {
this.c2 = c2;
}
public byte[] getC3() {
return c3;
}
public void setC3(byte[] c3) {
this.c3 = c3;
}
public byte[] getCipherText() {
return cipherText;
}
public void setCipherText(byte[] cipherText) {
this.cipherText = cipherText;
}
}
4.4 GMBaseUtil
package com.zx.test.openapi.utils.gm;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
public class GMBaseUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
}
4.5 BCECUtil
package com.zx.test.openapi.utils.gm;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9ECPoint;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* 这个工具类的方法,也适用于其他基于BC库的ECC算法
*/
public class BCECUtil {
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 generateKeyPairParameter(
ECDomainParameters domainParameters, SecureRandom random) {
ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters,
random);
ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
keyGen.init(keyGenerationParams);
return keyGen.generateKeyPair();
}
public static KeyPair generateKeyPair(ECDomainParameters domainParameters, SecureRandom random)
throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
ECParameterSpec parameterSpec = new ECParameterSpec(domainParameters.getCurve(), domainParameters.getG(),
domainParameters.getN(), domainParameters.getH());
kpg.initialize(parameterSpec, random);
return kpg.generateKeyPair();
}
public static int getCurveLength(ECKeyParameters ecKey) {
return getCurveLength(ecKey.getParameters());
}
public static int getCurveLength(ECDomainParameters domainParams) {
return (domainParams.getCurve().getFieldSize() + 7) / 8;
}
public static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
if (src.length == curveLength) {
return src;
}
byte[] result = new byte[curveLength];
if (src.length > curveLength) {
System.arraycopy(src, src.length - result.length, result, 0, result.length);
} else {
System.arraycopy(src, 0, result, result.length - src.length, src.length);
}
return result;
}
/**
* @param dHex 十六进制字符串形式的私钥d值,如果是SM2算法,Hex字符串长度应该是64(即32字节)
* @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS}
* @return
*/
public static ECPrivateKeyParameters createECPrivateKeyParameters(
String dHex, ECDomainParameters domainParameters) {
return createECPrivateKeyParameters(ByteUtils.fromHexString(dHex), domainParameters);
}
/**
* @param dBytes 字节数组形式的私钥d值,如果是SM2算法,应该是32字节
* @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS}
* @return
*/
public static ECPrivateKeyParameters createECPrivateKeyParameters(
byte[] dBytes, ECDomainParameters domainParameters) {
return createECPrivateKeyParameters(new BigInteger(1, dBytes), domainParameters);
}
/**
* @param d 大数形式的私钥d值
* @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS}
* @return
*/
public static ECPrivateKeyParameters createECPrivateKeyParameters(
BigInteger d, ECDomainParameters domainParameters) {
return new ECPrivateKeyParameters(d, domainParameters);
}
/**
* 根据EC私钥构造EC公钥
*
* @param priKey ECC私钥参数对象
* @return
*/
public static ECPublicKeyParameters buildECPublicKeyByPrivateKey(ECPrivateKeyParameters priKey) {
ECDomainParameters domainParameters = priKey.getParameters();
ECPoint q = new FixedPointCombMultiplier().multiply(domainParameters.getG(), priKey.getD());
return new ECPublicKeyParameters(q, domainParameters);
}
/**
* @param pubKeyHex 十六进制的公钥
* @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS}
* @return
*/
public static ECPublicKeyParameters createECPublicKeyParameters(String pubKeyHex, ECDomainParameters domainParameters){
//提取公钥点
ECPoint pukPoint = domainParameters.getCurve().decodePoint(Hex.decode(pubKeyHex));
// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
return new ECPublicKeyParameters(pukPoint, domainParameters);
}
/**
* @param x 大数形式的公钥x分量
* @param y 大数形式的公钥y分量
* @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#CURVE}
* @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS}
* @return
*/
public static ECPublicKeyParameters createECPublicKeyParameters(
BigInteger x, BigInteger y, ECCurve curve, ECDomainParameters domainParameters) {
return createECPublicKeyParameters(x.toByteArray(), y.toByteArray(), curve, domainParameters);
}
/**
* @param xHex 十六进制形式的公钥x分量,如果是SM2算法,Hex字符串长度应该是64(即32字节)
* @param yHex 十六进制形式的公钥y分量,如果是SM2算法,Hex字符串长度应该是64(即32字节)
* @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#CURVE}
* @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS}
* @return
*/
public static ECPublicKeyParameters createECPublicKeyParameters(
String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
return createECPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
curve, domainParameters);
}
/**
* @param xBytes 十六进制形式的公钥x分量,如果是SM2算法,应该是32字节
* @param yBytes 十六进制形式的公钥y分量,如果是SM2算法,应该是32字节
* @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#CURVE}
* @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link SM2Util#DOMAIN_PARAMS}
* @return
*/
public static ECPublicKeyParameters createECPublicKeyParameters(
byte[] xBytes, byte[] yBytes, ECCurve curve, ECDomainParameters domainParameters) {
final byte uncompressedFlag = 0x04;
int curveLength = getCurveLength(domainParameters);
xBytes = fixToCurveLengthBytes(curveLength, xBytes);
yBytes = fixToCurveLengthBytes(curveLength, yBytes);
byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
encodedPubKey[0] = uncompressedFlag;
System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
}
public static ECPrivateKeyParameters convertPrivateKeyToParameters(BCECPrivateKey ecPriKey) {
ECParameterSpec parameterSpec = ecPriKey.getParameters();
ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
parameterSpec.getN(), parameterSpec.getH());
return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters);
}
public static ECPublicKeyParameters convertPublicKeyToParameters(BCECPublicKey ecPubKey) {
ECParameterSpec parameterSpec = ecPubKey.getParameters();
ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(),
parameterSpec.getN(), parameterSpec.getH());
return new ECPublicKeyParameters(ecPubKey.getQ(), domainParameters);
}
public static BCECPublicKey createPublicKeyFromSubjectPublicKeyInfo(SubjectPublicKeyInfo subPubInfo)
throws NoSuchProviderException,
NoSuchAlgorithmException, InvalidKeySpecException, IOException {
return BCECUtil.convertX509ToECPublicKey(subPubInfo.toASN1Primitive().getEncoded(ASN1Encoding.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();
}
/**
* 将PKCS8标准的私钥字节流转换为私钥对象
*
* @param pkcs8Key
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @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);
}
/**
* 将PKCS8标准的私钥字节流转换为PEM
*
* @param encodedKey
* @return
* @throws IOException
*/
public static String convertECPrivateKeyPKCS8ToPEM(byte[] encodedKey) throws IOException {
return convertEncodedDataToPEM(PEM_STRING_ECPRIVATEKEY, encodedKey);
}
/**
* 将PEM格式的私钥转换为PKCS8标准字节流
*
* @param pemString
* @return
* @throws IOException
*/
public static byte[] convertECPrivateKeyPEMToPKCS8(String pemString) throws IOException {
return convertPEMToEncodedData(pemString);
}
/**
* 将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;
}
/**
* 将SEC1标准的私钥字节流恢复为PKCS8标准的字节流
*
* @param sec1Key
* @return
* @throws IOException
*/
public static byte[] convertECPrivateKeySEC1ToPKCS8(byte[] sec1Key) throws IOException {
/**
* 参考org.bouncycastle.asn1.pkcs.PrivateKeyInfo和
* org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey,逆向拼装
*/
X962Parameters params = getDomainParametersFromName(SM2Util.JDK_EC_SPEC, false);
ASN1OctetString privKey = new DEROctetString(sec1Key);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(0)); //版本号
v.add(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params)); //算法标识
v.add(privKey);
DERSequence ds = new DERSequence(v);
return ds.getEncoded(ASN1Encoding.DER);
}
/**
* 将SEC1标准的私钥字节流转为BCECPrivateKey对象
*
* @param sec1Key
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeySpecException
* @throws IOException
*/
public static BCECPrivateKey convertSEC1ToBCECPrivateKey(byte[] sec1Key)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
PKCS8EncodedKeySpec peks = new PKCS8EncodedKeySpec(convertECPrivateKeySEC1ToPKCS8(sec1Key));
KeyFactory kf = KeyFactory.getInstance(ALGO_NAME_EC, BouncyCastleProvider.PROVIDER_NAME);
return (BCECPrivateKey) kf.generatePrivate(peks);
}
/**
* 将SEC1标准的私钥字节流转为ECPrivateKeyParameters对象
* openssl i2d_ECPrivateKey函数生成的DER编码的ecc私钥是:SEC1标准的、带有EC_GROUP、带有公钥的,
* 这个工具函数的主要目的就是为了使Java程序能够“识别”openssl生成的ECC私钥
*
* @param sec1Key
* @return
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeySpecException
*/
public static ECPrivateKeyParameters convertSEC1ToECPrivateKey(byte[] sec1Key)
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException, IOException {
BCECPrivateKey privateKey = convertSEC1ToBCECPrivateKey(sec1Key);
return convertPrivateKeyToParameters(privateKey);
}
/**
* 将ECC公钥对象转换为X509标准的字节流
*
* @param pubKey
* @return
*/
public static byte[] convertECPublicKeyToX509(ECPublicKeyParameters pubKey) {
ECDomainParameters domainParams = pubKey.getParameters();
ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(),
domainParams.getN(), domainParams.getH());
BCECPublicKey publicKey = new BCECPublicKey(ALGO_NAME_EC, pubKey, spec,
BouncyCastleProvider.CONFIGURATION);
return publicKey.getEncoded();
}
/**
* 将X509标准的公钥字节流转为公钥对象
*
* @param x509Bytes
* @return
* @throws NoSuchProviderException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
public static BCECPublicKey convertX509ToECPublicKey(byte[] x509Bytes) throws NoSuchProviderException,
NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec eks = new X509EncodedKeySpec(x509Bytes);
KeyFactory kf = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
return (BCECPublicKey) kf.generatePublic(eks);
}
/**
* 将X509标准的公钥字节流转为PEM
*
* @param encodedKey
* @return
* @throws IOException
*/
public static String convertECPublicKeyX509ToPEM(byte[] encodedKey) throws IOException {
return convertEncodedDataToPEM(PEM_STRING_PUBLIC, encodedKey);
}
/**
* 将PEM格式的公钥转为X509标准的字节流
*
* @param pemString
* @return
* @throws IOException
*/
public static byte[] convertECPublicKeyPEMToX509(String pemString) throws IOException {
return convertPEMToEncodedData(pemString);
}
/**
* copy from BC
*
* @param genSpec
* @return
*/
public static X9ECParameters getDomainParametersFromGenSpec(ECGenParameterSpec genSpec) {
return getDomainParametersFromName(genSpec.getName());
}
/**
* copy from BC
*
* @param curveName
* @return
*/
public static X9ECParameters getDomainParametersFromName(String curveName) {
X9ECParameters domainParameters;
try {
if (curveName.charAt(0) >= '0' && curveName.charAt(0) <= '2') {
ASN1ObjectIdentifier oidID = new ASN1ObjectIdentifier(curveName);
domainParameters = ECUtil.getNamedCurveByOid(oidID);
} else {
if (curveName.indexOf(' ') > 0) {
curveName = curveName.substring(curveName.indexOf(' ') + 1);
domainParameters = ECUtil.getNamedCurveByName(curveName);
} else {
domainParameters = ECUtil.getNamedCurveByName(curveName);
}
}
} catch (IllegalArgumentException ex) {
domainParameters = ECUtil.getNamedCurveByName(curveName);
}
return domainParameters;
}
/**
* copy from BC
*
* @param ecSpec
* @param withCompression
* @return
*/
public static X962Parameters getDomainParametersFromName(
java.security.spec.ECParameterSpec ecSpec, boolean withCompression) {
X962Parameters params;
if (ecSpec instanceof ECNamedCurveSpec) {
ASN1ObjectIdentifier curveOid = ECUtil.getNamedCurveOid(((ECNamedCurveSpec) ecSpec).getName());
if (curveOid == null) {
curveOid = new ASN1ObjectIdentifier(((ECNamedCurveSpec) ecSpec).getName());
}
params = new X962Parameters(curveOid);
} else if (ecSpec == null) {
params = new X962Parameters(DERNull.INSTANCE);
} else {
ECCurve curve = EC5Util.convertCurve(ecSpec.getCurve());
X9ECParameters ecP = new X9ECParameters(
curve,
new X9ECPoint(EC5Util.convertPoint(curve, ecSpec.getGenerator()), withCompression),
ecSpec.getOrder(),
BigInteger.valueOf(ecSpec.getCofactor()),
ecSpec.getCurve().getSeed());
如果是1.62或更低版本的bcprov-jdk15on应该使用以下这段代码,因为高版本的EC5Util.convertPoint没有向下兼容
/*
X9ECParameters ecP = new X9ECParameters(
curve,
EC5Util.convertPoint(curve, ecSpec.getGenerator(), withCompression),
ecSpec.getOrder(),
BigInteger.valueOf(ecSpec.getCofactor()),
ecSpec.getCurve().getSeed());
*/
params = new X962Parameters(ecP);
}
return params;
}
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());
}
private static byte[] convertPEMToEncodedData(String pemString) throws IOException {
ByteArrayInputStream bIn = new ByteArrayInputStream(pemString.getBytes());
PemReader pRdr = new PemReader(new InputStreamReader(bIn));
try {
PemObject pemObject = pRdr.readPemObject();
return pemObject.getContent();
} finally {
pRdr.close();
}
}
}