diff --git a/chsm-common/pom.xml b/chsm-common/pom.xml
index ad67cd8..207ee15 100644
--- a/chsm-common/pom.xml
+++ b/chsm-common/pom.xml
@@ -32,10 +32,6 @@
com.baomidou
mybatis-plus-boot-starter
-
- javax.persistence
- javax.persistence-api
-
org.bouncycastle
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/mapper/ApplicationMapper.java b/chsm-common/src/main/java/com/sunyard/chsm/mapper/ApplicationMapper.java
new file mode 100644
index 0000000..480b87a
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/mapper/ApplicationMapper.java
@@ -0,0 +1,13 @@
+package com.sunyard.chsm.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sunyard.chsm.model.entity.Application;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author liulu
+ * @since 2024/10/29
+ */
+@Mapper
+public interface ApplicationMapper extends BaseMapper {
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/mapper/KeyCsrMapper.java b/chsm-common/src/main/java/com/sunyard/chsm/mapper/KeyCsrMapper.java
new file mode 100644
index 0000000..c2a4a39
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/mapper/KeyCsrMapper.java
@@ -0,0 +1,20 @@
+package com.sunyard.chsm.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.sunyard.chsm.model.entity.KeyCsr;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author liulu
+ * @since 2024/10/22
+ */
+@Mapper
+public interface KeyCsrMapper extends BaseMapper {
+
+ default KeyCsr selectBySubject(String dn) {
+ return selectOne(
+ new LambdaQueryWrapper().eq(KeyCsr::getSubject, dn)
+ );
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/mapper/SpKeyRecordMapper.java b/chsm-common/src/main/java/com/sunyard/chsm/mapper/SpKeyRecordMapper.java
index 788a552..a7c9582 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/mapper/SpKeyRecordMapper.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/mapper/SpKeyRecordMapper.java
@@ -1,13 +1,28 @@
package com.sunyard.chsm.mapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.chsm.model.entity.KeyRecord;
import org.apache.ibatis.annotations.Mapper;
+import java.time.LocalDateTime;
+
/**
* @author liulu
* @since 2024/10/22
*/
@Mapper
public interface SpKeyRecordMapper extends BaseMapper {
+
+ default KeyRecord selectUsedByKeyId(Long id) {
+ LocalDateTime now = LocalDateTime.now();
+ return selectOne(
+ new LambdaQueryWrapper()
+ .eq(KeyRecord::getKeyId, id)
+ .lt(KeyRecord::getEffectiveTime, now)
+ .gt(KeyRecord::getExpiredTime, now)
+ );
+ }
+
+
}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/model/Subject.java b/chsm-common/src/main/java/com/sunyard/chsm/model/Subject.java
new file mode 100644
index 0000000..7322272
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/model/Subject.java
@@ -0,0 +1,105 @@
+package com.sunyard.chsm.model;
+
+import lombok.Data;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import javax.validation.constraints.NotEmpty;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author liulu
+ * @since 2024/10/29
+ */
+@Data
+public class Subject {
+
+ private static final List keys = Arrays.asList("C", "ST", "L", "O", "OU", "CN");
+
+ private static final String COMMA = ",";
+
+ /**
+ * 通用名
+ */
+ @NotEmpty(message = "通用名不能为空")
+ private String commonName;
+ /**
+ * 国家
+ */
+ private String country;
+ /**
+ * 省份
+ */
+ private String province;
+ /**
+ * 城市
+ */
+ private String city;
+ /**
+ * 组织
+ */
+ private String org;
+ /**
+ * 部门
+ */
+ private String orgUnit;
+
+
+ public String getDN() {
+ Assert.notNull(commonName, "通用名不能为空");
+ StringBuilder builder = new StringBuilder();
+ if (StringUtils.hasText(country)) {
+ builder.append("C=").append(country).append(COMMA);
+ }
+ if (StringUtils.hasText(province)) {
+ builder.append("ST=").append(province).append(COMMA);
+ }
+ if (StringUtils.hasText(city)) {
+ builder.append("L=").append(city).append(COMMA);
+ }
+ if (StringUtils.hasText(org)) {
+ builder.append("O=").append(org).append(COMMA);
+ }
+ if (StringUtils.hasText(orgUnit)) {
+ builder.append("OU=").append(orgUnit).append(COMMA);
+ }
+ if (StringUtils.hasText(commonName)) {
+ builder.append("CN=").append(commonName).append(COMMA);
+ }
+ builder.deleteCharAt(builder.lastIndexOf(COMMA));
+ return builder.toString();
+ }
+
+ public static Subject fromDN(String dn) {
+ Assert.hasText(dn, "dn不能为空值");
+ Subject subject = new Subject();
+ String[] splits = dn.trim().split(",");
+ for (String split : splits) {
+ String trim = split.trim();
+
+ if (trim.startsWith("C=")) {
+ subject.setCountry(trim.split("=")[1]);
+ }
+ if (trim.startsWith("ST=")) {
+ subject.setProvince(trim.split("=")[1]);
+ }
+ if (trim.startsWith("L=")) {
+ subject.setCity(trim.split("=")[1]);
+ }
+ if (trim.startsWith("O=")) {
+ subject.setOrg(trim.split("=")[1]);
+ }
+ if (trim.startsWith("OU=")) {
+ subject.setOrgUnit(trim.split("=")[1]);
+ }
+ if (trim.startsWith("CN=")) {
+ subject.setCommonName(trim.split("=")[1]);
+ }
+ }
+ Assert.notNull(subject.commonName, "通用名不能为空");
+ return subject;
+ }
+
+
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/model/entity/Application.java b/chsm-common/src/main/java/com/sunyard/chsm/model/entity/Application.java
new file mode 100644
index 0000000..8b5b4e2
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/model/entity/Application.java
@@ -0,0 +1,30 @@
+package com.sunyard.chsm.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author liulu
+ * @since 2024/10/29
+ */
+@Data
+@TableName("sp_application")
+public class Application {
+
+ private Long id;
+ private String name;
+ private String bindService;
+ private String status;
+ // 32位uuid
+ private String appKey;
+ // 32位uuid
+ private String appSecrete;
+ private Long creatorId;
+
+ private String remark;
+ private LocalDateTime createTime;
+ private LocalDateTime updateTime;
+
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/model/entity/KeyCsr.java b/chsm-common/src/main/java/com/sunyard/chsm/model/entity/KeyCsr.java
new file mode 100644
index 0000000..cb1d270
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/model/entity/KeyCsr.java
@@ -0,0 +1,29 @@
+package com.sunyard.chsm.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author liulu
+ * @since 2024/10/22
+ */
+@Data
+@TableName("sp_key_csr")
+public class KeyCsr {
+
+ private Long id;
+ private Long applicationId;
+ private Long keyId;
+ private Long keyRecordId;
+ private String subject;
+ private String keyData;
+ private String pubKey;
+ private String csrTxt;
+
+ private String remark;
+ private LocalDateTime createTime;
+ private LocalDateTime updateTime;
+
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/model/entity/KeyInfo.java b/chsm-common/src/main/java/com/sunyard/chsm/model/entity/KeyInfo.java
index 1ec9bd6..2a9ba57 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/model/entity/KeyInfo.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/model/entity/KeyInfo.java
@@ -1,9 +1,9 @@
package com.sunyard.chsm.model.entity;
+import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
-import javax.persistence.Transient;
import java.time.LocalDateTime;
import java.util.List;
@@ -33,7 +33,7 @@ public class KeyInfo {
private LocalDateTime createTime;
private LocalDateTime updateTime;
- @Transient
+ @TableField(exist = false)
private List records;
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/sdf/BCSdfApiService.java b/chsm-common/src/main/java/com/sunyard/chsm/sdf/BCSdfApiService.java
index 037afab..3801c33 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/sdf/BCSdfApiService.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/sdf/BCSdfApiService.java
@@ -4,22 +4,18 @@ package com.sunyard.chsm.sdf;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.sdf.model.EccPriKey;
import com.sunyard.chsm.sdf.model.EccPubKey;
+import com.sunyard.chsm.utils.gm.BCSM2Utils;
+import com.sunyard.chsm.utils.gm.BCSM3Utils;
import lombok.SneakyThrows;
-import org.bouncycastle.asn1.gm.GMNamedCurves;
-import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
-import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
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.util.BigIntegers;
import org.springframework.stereotype.Service;
import java.security.KeyPair;
-import java.security.KeyPairGenerator;
import java.security.SecureRandom;
/**
@@ -40,17 +36,8 @@ public class BCSdfApiService implements SdfApiService {
@SneakyThrows
@Override
public EccKey genKeyPairEcc() {
-
- // 获取SM2参数
- X9ECParameters sm2Params = GMNamedCurves.getByOID(GMObjectIdentifiers.sm2p256v1);
- ECParameterSpec sm2Spec = new ECParameterSpec(sm2Params.getCurve(), sm2Params.getG(), sm2Params.getN(), sm2Params.getH());
-
- // 创建密钥对生成器
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
- keyPairGenerator.initialize(sm2Spec);
-
// 生成密钥对
- KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ KeyPair keyPair = BCSM2Utils.generateKeyPair();
BCECPublicKey pubKey = (BCECPublicKey) keyPair.getPublic();
BCECPrivateKey priKey = (BCECPrivateKey) keyPair.getPrivate();
byte[] x = pubKey.getQ().getXCoord().getEncoded();
@@ -84,11 +71,17 @@ public class BCSdfApiService implements SdfApiService {
@Override
public byte[] hash(byte[] pucData) {
- SM3Digest digest = new SM3Digest();
- digest.update(pucData, 0, pucData.length);
- byte[] hash = new byte[digest.getDigestSize()];
- digest.doFinal(hash, 0);
- return hash;
+ return BCSM3Utils.hash(pucData);
+ }
+
+ @Override
+ public byte[] encryptByMKNoPadding(byte[] data) {
+ return data;
+ }
+
+ @Override
+ public byte[] decryptByMKNoPadding(byte[] data) {
+ return data;
}
}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/sdf/SdfApiService.java b/chsm-common/src/main/java/com/sunyard/chsm/sdf/SdfApiService.java
index b9f081a..bed16b9 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/sdf/SdfApiService.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/sdf/SdfApiService.java
@@ -54,4 +54,7 @@ public interface SdfApiService {
byte[] hash(byte[] pucData);
+ byte[] encryptByMKNoPadding(byte[] data);
+
+ byte[] decryptByMKNoPadding(byte[] data);
}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java
new file mode 100644
index 0000000..884f00d
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java
@@ -0,0 +1,526 @@
+package com.sunyard.chsm.utils.gm;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+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.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECKeyParameters;
+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.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.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemReader;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+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.ECGenParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * 这个工具类的方法,也适用于其他基于BC库的ECC算法
+ */
+public class BCECUtils {
+ 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 BCSM2Utils#DOMAIN_PARAMS}
+ * @return
+ */
+ public static ECPrivateKeyParameters createECPrivateKeyParameters(
+ String dHex, ECDomainParameters domainParameters) {
+ return createECPrivateKeyParameters(ByteUtils.fromHexString(dHex), domainParameters);
+ }
+
+ /**
+ * @param dBytes 字节数组形式的私钥d值,如果是SM2算法,应该是32字节
+ * @return
+ */
+ public static ECPrivateKeyParameters createECPrivateKeyParameters(byte[] dBytes) {
+ return createECPrivateKeyParameters(new BigInteger(1, dBytes), BCSM2Utils.DOMAIN_PARAMS);
+ }
+
+ /**
+ * @param dBytes 字节数组形式的私钥d值,如果是SM2算法,应该是32字节
+ * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link BCSM2Utils#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 BCSM2Utils#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 x 大数形式的公钥x分量
+ * @param y 大数形式的公钥y分量
+ * @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link BCSM2Utils#CURVE}
+ * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link BCSM2Utils#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 BCSM2Utils#CURVE}
+ * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link BCSM2Utils#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);
+ }
+
+ public static ECPublicKeyParameters createECPublicKeyParameters(byte[] xBytes, byte[] yBytes) {
+ return createECPublicKeyParameters(xBytes, yBytes, BCSM2Utils.CURVE, BCSM2Utils.DOMAIN_PARAMS);
+ }
+
+ /**
+ * @param xBytes 十六进制形式的公钥x分量,如果是SM2算法,应该是32字节
+ * @param yBytes 十六进制形式的公钥y分量,如果是SM2算法,应该是32字节
+ * @param curve EC曲线参数,一般是固定的,如果是SM2算法的可参考{@link BCSM2Utils#CURVE}
+ * @param domainParameters EC Domain参数,一般是固定的,如果是SM2算法的可参考{@link BCSM2Utils#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 BCECUtils.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(BCSM2Utils.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();
+ }
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM2Utils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM2Utils.java
new file mode 100644
index 0000000..48aa84b
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM2Utils.java
@@ -0,0 +1,606 @@
+package com.sunyard.chsm.utils.gm;
+
+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.engines.SM2Engine.Mode;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+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.math.ec.ECPoint;
+import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.security.spec.ECFieldFp;
+import java.security.spec.EllipticCurve;
+
+public class BCSM2Utils 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 = BCECUtils.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 BCECUtils.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 BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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 = BCECUtils.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;
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM3Utils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM3Utils.java
new file mode 100644
index 0000000..9f03640
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM3Utils.java
@@ -0,0 +1,58 @@
+package com.sunyard.chsm.utils.gm;
+
+import org.bouncycastle.crypto.digests.SM3Digest;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.params.KeyParameter;
+
+import java.util.Arrays;
+
+public class BCSM3Utils 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;
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM4Utils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM4Utils.java
new file mode 100644
index 0000000..d8663e2
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCSM4Utils.java
@@ -0,0 +1,191 @@
+package com.sunyard.chsm.utils.gm;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.engines.SM4Engine;
+import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
+import org.bouncycastle.crypto.macs.GMac;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.paddings.BlockCipherPadding;
+import org.bouncycastle.crypto.paddings.PKCS7Padding;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+
+public class BCSM4Utils extends GMBaseUtil {
+ public static final String ALGORITHM_NAME = "SM4";
+ public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
+ public static final String ALGORITHM_NAME_ECB_NOPADDING = "SM4/ECB/NoPadding";
+ public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
+ public static final String ALGORITHM_NAME_CBC_NOPADDING = "SM4/CBC/NoPadding";
+
+ /**
+ * SM4算法目前只支持128位(即密钥16字节)
+ */
+ public static final int DEFAULT_KEY_SIZE = 128;
+
+ public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
+ return generateKey(DEFAULT_KEY_SIZE);
+ }
+
+ public static byte[] generateKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {
+ KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
+ kg.init(keySize, new SecureRandom());
+ return kg.generateKey().getEncoded();
+ }
+
+ public static byte[] encrypt_ECB_Padding(byte[] key, byte[] data)
+ throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
+ NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
+ Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
+ return cipher.doFinal(data);
+ }
+
+ public static byte[] decrypt_ECB_Padding(byte[] key, byte[] cipherText)
+ throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
+ NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
+ Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
+ return cipher.doFinal(cipherText);
+ }
+
+ public static byte[] encrypt_ECB_NoPadding(byte[] key, byte[] data)
+ throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
+ NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
+ Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.ENCRYPT_MODE, key);
+ return cipher.doFinal(data);
+ }
+
+ public static byte[] decrypt_ECB_NoPadding(byte[] key, byte[] cipherText)
+ throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
+ NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
+ Cipher cipher = generateECBCipher(ALGORITHM_NAME_ECB_NOPADDING, Cipher.DECRYPT_MODE, key);
+ return cipher.doFinal(cipherText);
+ }
+
+ public static byte[] encrypt_CBC_Padding(byte[] key, byte[] iv, byte[] data)
+ throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
+ NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
+ InvalidAlgorithmParameterException {
+ Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key, iv);
+ return cipher.doFinal(data);
+ }
+
+ public static byte[] decrypt_CBC_Padding(byte[] key, byte[] iv, byte[] cipherText)
+ throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
+ NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
+ InvalidAlgorithmParameterException {
+ Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key, iv);
+ return cipher.doFinal(cipherText);
+ }
+
+ public static byte[] encrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] data)
+ throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
+ NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
+ InvalidAlgorithmParameterException {
+ Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.ENCRYPT_MODE, key, iv);
+ return cipher.doFinal(data);
+ }
+
+ public static byte[] decrypt_CBC_NoPadding(byte[] key, byte[] iv, byte[] cipherText)
+ throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
+ NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
+ InvalidAlgorithmParameterException {
+ Cipher cipher = generateCBCCipher(ALGORITHM_NAME_CBC_NOPADDING, Cipher.DECRYPT_MODE, key, iv);
+ return cipher.doFinal(cipherText);
+ }
+
+ public static byte[] doCMac(byte[] key, byte[] data) throws NoSuchProviderException, NoSuchAlgorithmException,
+ InvalidKeyException {
+ Key keyObj = new SecretKeySpec(key, ALGORITHM_NAME);
+ return doMac("SM4-CMAC", keyObj, data);
+ }
+
+ public static byte[] doGMac(byte[] key, byte[] iv, int tagLength, byte[] data) {
+ org.bouncycastle.crypto.Mac mac = new GMac(new GCMBlockCipher(new SM4Engine()), tagLength * 8);
+ return doMac(mac, key, iv, data);
+ }
+
+ /**
+ * 默认使用PKCS7Padding/PKCS5Padding填充的CBCMAC
+ *
+ * @param key
+ * @param iv
+ * @param data
+ * @return
+ */
+ public static byte[] doCBCMac(byte[] key, byte[] iv, byte[] data) {
+ SM4Engine engine = new SM4Engine();
+ org.bouncycastle.crypto.Mac mac = new CBCBlockCipherMac(engine, engine.getBlockSize() * 8, new PKCS7Padding());
+ return doMac(mac, key, iv, data);
+ }
+
+ /**
+ * @param key
+ * @param iv
+ * @param padding 可以传null,传null表示NoPadding,由调用方保证数据必须是BlockSize的整数倍
+ * @param data
+ * @return
+ * @throws Exception
+ */
+ public static byte[] doCBCMac(byte[] key, byte[] iv, BlockCipherPadding padding, byte[] data) throws Exception {
+ SM4Engine engine = new SM4Engine();
+ if (padding == null) {
+ if (data.length % engine.getBlockSize() != 0) {
+ throw new Exception("if no padding, data length must be multiple of SM4 BlockSize");
+ }
+ }
+ org.bouncycastle.crypto.Mac mac = new CBCBlockCipherMac(engine, engine.getBlockSize() * 8, padding);
+ return doMac(mac, key, iv, data);
+ }
+
+
+ private static byte[] doMac(org.bouncycastle.crypto.Mac mac, byte[] key, byte[] iv, byte[] data) {
+ CipherParameters cipherParameters = new KeyParameter(key);
+ mac.init(new ParametersWithIV(cipherParameters, iv));
+ mac.update(data, 0, data.length);
+ byte[] result = new byte[mac.getMacSize()];
+ mac.doFinal(result, 0);
+ return result;
+ }
+
+ private static byte[] doMac(String algorithmName, Key key, byte[] data) throws NoSuchProviderException,
+ NoSuchAlgorithmException, InvalidKeyException {
+ Mac mac = Mac.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
+ mac.init(key);
+ mac.update(data);
+ return mac.doFinal();
+ }
+
+ private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key)
+ throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
+ InvalidKeyException {
+ Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
+ Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
+ cipher.init(mode, sm4Key);
+ return cipher;
+ }
+
+ private static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv)
+ throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+ NoSuchProviderException, NoSuchPaddingException {
+ Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
+ Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+ cipher.init(mode, sm4Key, ivParameterSpec);
+ return cipher;
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/GMBaseUtil.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/GMBaseUtil.java
new file mode 100644
index 0000000..a44a47e
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/GMBaseUtil.java
@@ -0,0 +1,11 @@
+package com.sunyard.chsm.utils.gm;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import java.security.Security;
+
+public class GMBaseUtil {
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2Cipher.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2Cipher.java
new file mode 100644
index 0000000..ef6b194
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2Cipher.java
@@ -0,0 +1,55 @@
+package com.sunyard.chsm.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;
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2PreprocessSigner.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2PreprocessSigner.java
new file mode 100644
index 0000000..e479f63
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2PreprocessSigner.java
@@ -0,0 +1,294 @@
+package com.sunyard.chsm.utils.gm;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.CryptoException;
+import org.bouncycastle.crypto.CryptoServicesRegistrar;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SM3Digest;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyParameters;
+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.DSAKCalculator;
+import org.bouncycastle.crypto.signers.RandomDSAKCalculator;
+import org.bouncycastle.math.ec.ECAlgorithms;
+import org.bouncycastle.math.ec.ECConstants;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECMultiplier;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.math.ec.FixedPointCombMultiplier;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+/**
+ * 有的国密需求是用户可以自己做预处理,签名验签只是对预处理的结果进行签名和验签
+ */
+public class SM2PreprocessSigner implements ECConstants {
+ private static final int DIGEST_LENGTH = 32; // bytes
+
+ private final DSAKCalculator kCalculator = new RandomDSAKCalculator();
+ private Digest digest = null;
+
+ private ECDomainParameters ecParams;
+ private ECPoint pubPoint;
+ private ECKeyParameters ecKey;
+ private byte[] userID;
+
+ /**
+ * 初始化
+ *
+ * @param forSigning true表示用于签名,false表示用于验签
+ * @param param
+ */
+ public void init(boolean forSigning, CipherParameters param) {
+ init(forSigning, new SM3Digest(), param);
+ }
+
+ /**
+ * 初始化
+ *
+ * @param forSigning true表示用于签名,false表示用于验签
+ * @param digest SM2算法的话,一般是采用SM3摘要算法
+ * @param param
+ * @throws RuntimeException
+ */
+ public void init(boolean forSigning, Digest digest, CipherParameters param) throws RuntimeException {
+ CipherParameters baseParam;
+
+ if (digest.getDigestSize() != DIGEST_LENGTH) {
+ throw new RuntimeException("Digest size must be " + DIGEST_LENGTH);
+ }
+ this.digest = digest;
+
+ if (param instanceof ParametersWithID) {
+ baseParam = ((ParametersWithID) param).getParameters();
+ userID = ((ParametersWithID) param).getID();
+ } else {
+ baseParam = param;
+ userID = Hex.decode("31323334353637383132333435363738"); // the default value
+ }
+
+ if (forSigning) {
+ if (baseParam instanceof ParametersWithRandom) {
+ ParametersWithRandom rParam = (ParametersWithRandom) baseParam;
+
+ ecKey = (ECKeyParameters) rParam.getParameters();
+ ecParams = ecKey.getParameters();
+ kCalculator.init(ecParams.getN(), rParam.getRandom());
+ } else {
+ ecKey = (ECKeyParameters) baseParam;
+ ecParams = ecKey.getParameters();
+ kCalculator.init(ecParams.getN(), CryptoServicesRegistrar.getSecureRandom());
+ }
+ pubPoint = createBasePointMultiplier().multiply(ecParams.getG(), ((ECPrivateKeyParameters) ecKey).getD()).normalize();
+ } else {
+ ecKey = (ECKeyParameters) baseParam;
+ ecParams = ecKey.getParameters();
+ pubPoint = ((ECPublicKeyParameters) ecKey).getQ();
+ }
+ }
+
+ /**
+ * 预处理,辅助方法
+ * ZA=H256(ENT LA ∥ IDA ∥ a ∥ b ∥ xG ∥yG ∥ xA ∥ yA)。
+ * M=ZA ∥ M;
+ * e = Hv(M)
+ *
+ * @return
+ */
+ public byte[] preprocess(byte[] m) {
+ return preprocess(m, 0, m.length);
+ }
+
+
+ public byte[] preprocess(byte[] m, int off, int len) {
+ byte[] z = getZ(userID);
+ digest.update(z, 0, z.length);
+ digest.update(m, off, len);
+ byte[] eHash = new byte[DIGEST_LENGTH];
+ digest.doFinal(eHash, 0);
+ return eHash;
+ }
+
+ public boolean verifySignature(byte[] eHash, byte[] signature) {
+ try {
+ BigInteger[] rs = derDecode(signature);
+ if (rs != null) {
+ return verifySignature(eHash, rs[0], rs[1]);
+ }
+ } catch (IOException e) {
+ }
+
+ return false;
+ }
+
+ public void reset() {
+ digest.reset();
+ }
+
+ public byte[] generateSignature(byte[] eHash) throws CryptoException {
+ BigInteger n = ecParams.getN();
+ BigInteger e = calculateE(eHash);
+ BigInteger d = ((ECPrivateKeyParameters) ecKey).getD();
+
+ BigInteger r, s;
+
+ ECMultiplier basePointMultiplier = createBasePointMultiplier();
+
+ // 5.2.1 Draft RFC: SM2 Public Key Algorithms
+ do // generate s
+ {
+ BigInteger k;
+ do // generate r
+ {
+ // A3
+ k = kCalculator.nextK();
+
+ // A4
+ ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize();
+
+ // A5
+ r = e.add(p.getAffineXCoord().toBigInteger()).mod(n);
+ }
+ while (r.equals(ZERO) || r.add(k).equals(n));
+
+ // A6
+ BigInteger dPlus1ModN = d.add(ONE).modInverse(n);
+
+ s = k.subtract(r.multiply(d)).mod(n);
+ s = dPlus1ModN.multiply(s).mod(n);
+ }
+ while (s.equals(ZERO));
+
+ // A7
+ try {
+ return derEncode(r, s);
+ } catch (IOException ex) {
+ throw new CryptoException("unable to encode signature: " + ex.getMessage(), ex);
+ }
+ }
+
+ private boolean verifySignature(byte[] eHash, BigInteger r, BigInteger s) {
+ BigInteger n = ecParams.getN();
+
+ // 5.3.1 Draft RFC: SM2 Public Key Algorithms
+ // B1
+ if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) {
+ return false;
+ }
+
+ // B2
+ if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) {
+ return false;
+ }
+
+ // B3 eHash
+
+ // B4
+ BigInteger e = calculateE(eHash);
+
+ // B5
+ BigInteger t = r.add(s).mod(n);
+ if (t.equals(ZERO)) {
+ return false;
+ }
+
+ // B6
+ ECPoint q = ((ECPublicKeyParameters) ecKey).getQ();
+ ECPoint x1y1 = ECAlgorithms.sumOfTwoMultiplies(ecParams.getG(), s, q, t).normalize();
+ if (x1y1.isInfinity()) {
+ return false;
+ }
+
+ // B7
+ BigInteger expectedR = e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n);
+
+ return expectedR.equals(r);
+ }
+
+ private byte[] digestDoFinal() {
+ byte[] result = new byte[digest.getDigestSize()];
+ digest.doFinal(result, 0);
+
+ reset();
+
+ return result;
+ }
+
+ private byte[] getZ(byte[] userID) {
+ digest.reset();
+
+ addUserID(digest, userID);
+
+ addFieldElement(digest, ecParams.getCurve().getA());
+ addFieldElement(digest, ecParams.getCurve().getB());
+ addFieldElement(digest, ecParams.getG().getAffineXCoord());
+ addFieldElement(digest, ecParams.getG().getAffineYCoord());
+ addFieldElement(digest, pubPoint.getAffineXCoord());
+ addFieldElement(digest, pubPoint.getAffineYCoord());
+
+ byte[] result = new byte[digest.getDigestSize()];
+
+ digest.doFinal(result, 0);
+
+ return result;
+ }
+
+ private void addUserID(Digest digest, byte[] userID) {
+ int len = userID.length * 8;
+ digest.update((byte) (len >> 8 & 0xFF));
+ digest.update((byte) (len & 0xFF));
+ digest.update(userID, 0, userID.length);
+ }
+
+ private void addFieldElement(Digest digest, ECFieldElement v) {
+ byte[] p = v.getEncoded();
+ digest.update(p, 0, p.length);
+ }
+
+ protected ECMultiplier createBasePointMultiplier() {
+ return new FixedPointCombMultiplier();
+ }
+
+ protected BigInteger calculateE(byte[] message) {
+ return new BigInteger(1, message);
+ }
+
+ protected BigInteger[] derDecode(byte[] encoding)
+ throws IOException {
+ ASN1Sequence seq = ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(encoding));
+ if (seq.size() != 2) {
+ return null;
+ }
+
+ BigInteger r = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
+ BigInteger s = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
+
+ byte[] expectedEncoding = derEncode(r, s);
+ if (!Arrays.constantTimeAreEqual(expectedEncoding, encoding)) {
+ return null;
+ }
+
+ return new BigInteger[]{r, s};
+ }
+
+ protected byte[] derEncode(BigInteger r, BigInteger s)
+ throws IOException {
+
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ v.add(new ASN1Integer(r));
+ v.add(new ASN1Integer(s));
+ return new DERSequence(v).getEncoded(ASN1Encoding.DER);
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/BCSM2CertUtils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/BCSM2CertUtils.java
new file mode 100644
index 0000000..b9c6ce6
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/BCSM2CertUtils.java
@@ -0,0 +1,160 @@
+package com.sunyard.chsm.utils.gm.cert;
+
+
+import com.sunyard.chsm.utils.gm.BCECUtils;
+import com.sunyard.chsm.utils.gm.BCSM2Utils;
+import com.sunyard.chsm.utils.gm.GMBaseUtil;
+import org.bouncycastle.asn1.pkcs.ContentInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jce.interfaces.ECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.pkcs.PKCS12PfxPdu;
+import org.bouncycastle.pkcs.PKCS12SafeBag;
+import org.bouncycastle.pkcs.PKCS12SafeBagFactory;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertPath;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+public class BCSM2CertUtils extends GMBaseUtil {
+ public static BCECPublicKey getBCECPublicKey(X509Certificate sm2Cert) {
+ ECPublicKey pubKey = (ECPublicKey) sm2Cert.getPublicKey();
+ ECPoint q = pubKey.getQ();
+ ECParameterSpec parameterSpec = new ECParameterSpec(BCSM2Utils.CURVE, BCSM2Utils.G_POINT,
+ BCSM2Utils.SM2_ECC_N, BCSM2Utils.SM2_ECC_H);
+ ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(q, parameterSpec);
+ return new BCECPublicKey(pubKey.getAlgorithm(), pubKeySpec,
+ BouncyCastleProvider.CONFIGURATION);
+ }
+
+ /**
+ * 校验证书
+ *
+ * @param issuerPubKey 从颁发者CA证书中提取出来的公钥
+ * @param cert 待校验的证书
+ * @return
+ */
+ public static boolean verifyCertificate(BCECPublicKey issuerPubKey, X509Certificate cert) {
+ try {
+ cert.verify(issuerPubKey, BouncyCastleProvider.PROVIDER_NAME);
+ } catch (Exception ex) {
+ return false;
+ }
+ return true;
+ }
+
+ public static X509Certificate getX509Certificate(String certFilePath) throws IOException, CertificateException,
+ NoSuchProviderException {
+ try (InputStream is = Files.newInputStream(Paths.get(certFilePath))) {
+ return getX509Certificate(is);
+ }
+ }
+
+ public static X509Certificate getX509Certificate(byte[] certBytes) throws CertificateException,
+ NoSuchProviderException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(certBytes);
+ return getX509Certificate(bais);
+ }
+
+ public static X509Certificate getX509Certificate(InputStream is) throws CertificateException,
+ NoSuchProviderException {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+ return (X509Certificate) cf.generateCertificate(is);
+ }
+
+ public static CertPath getCertificateChain(String certChainPath) throws IOException, CertificateException,
+ NoSuchProviderException {
+ try (InputStream is = Files.newInputStream(Paths.get(certChainPath))) {
+ return getCertificateChain(is);
+ }
+ }
+
+ public static CertPath getCertificateChain(byte[] certChainBytes) throws CertificateException,
+ NoSuchProviderException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(certChainBytes);
+ return getCertificateChain(bais);
+ }
+
+ public static byte[] getCertificateChainBytes(CertPath certChain) throws CertificateEncodingException {
+ return certChain.getEncoded("PKCS7");
+ }
+
+ public static CertPath getCertificateChain(InputStream is) throws CertificateException, NoSuchProviderException {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+ return cf.generateCertPath(is, "PKCS7");
+ }
+
+ public static CertPath getCertificateChain(List certs) throws CertificateException,
+ NoSuchProviderException {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
+ return cf.generateCertPath(certs);
+ }
+
+ public static X509Certificate getX509CertificateFromPfx(byte[] pfxDER, String passwd) throws Exception {
+ InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
+ .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passwd.toCharArray());
+ PKCS12PfxPdu pfx = new PKCS12PfxPdu(pfxDER);
+
+ ContentInfo[] infos = pfx.getContentInfos();
+ if (infos.length != 2) {
+ throw new Exception("Only support one pair ContentInfo");
+ }
+
+ for (int i = 0; i != infos.length; i++) {
+ if (infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData)) {
+ PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i], inputDecryptorProvider);
+ PKCS12SafeBag[] bags = dataFact.getSafeBags();
+ X509CertificateHolder certHoler = (X509CertificateHolder) bags[0].getBagValue();
+ return BCSM2CertUtils.getX509Certificate(certHoler.getEncoded());
+ }
+ }
+
+ throw new Exception("Not found X509Certificate in this pfx");
+ }
+
+ public static BCECPublicKey getPublicKeyFromPfx(byte[] pfxDER, String passwd) throws Exception {
+ return BCSM2CertUtils.getBCECPublicKey(getX509CertificateFromPfx(pfxDER, passwd));
+ }
+
+ public static BCECPrivateKey getPrivateKeyFromPfx(byte[] pfxDER, String passwd) throws Exception {
+ InputDecryptorProvider inputDecryptorProvider = new JcePKCSPBEInputDecryptorProviderBuilder()
+ .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passwd.toCharArray());
+ PKCS12PfxPdu pfx = new PKCS12PfxPdu(pfxDER);
+
+ ContentInfo[] infos = pfx.getContentInfos();
+ if (infos.length != 2) {
+ throw new Exception("Only support one pair ContentInfo");
+ }
+
+ for (int i = 0; i != infos.length; i++) {
+ if (!infos[i].getContentType().equals(PKCSObjectIdentifiers.encryptedData)) {
+ PKCS12SafeBagFactory dataFact = new PKCS12SafeBagFactory(infos[i]);
+ PKCS12SafeBag[] bags = dataFact.getSafeBags();
+ PKCS8EncryptedPrivateKeyInfo encInfo = (PKCS8EncryptedPrivateKeyInfo) bags[0].getBagValue();
+ PrivateKeyInfo info = encInfo.decryptPrivateKeyInfo(inputDecryptorProvider);
+ return BCECUtils.convertPKCS8ToECPrivateKey(info.getEncoded());
+ }
+ }
+
+ throw new Exception("Not found Private Key in this pfx");
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CertSNAllocator.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CertSNAllocator.java
new file mode 100644
index 0000000..76f93e3
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CertSNAllocator.java
@@ -0,0 +1,7 @@
+package com.sunyard.chsm.utils.gm.cert;
+
+import java.math.BigInteger;
+
+public interface CertSNAllocator {
+ BigInteger nextSerialNumber() throws Exception;
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CommonCertUtils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CommonCertUtils.java
new file mode 100644
index 0000000..91e7ac1
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CommonCertUtils.java
@@ -0,0 +1,69 @@
+package com.sunyard.chsm.utils.gm.cert;
+
+import com.sunyard.chsm.utils.gm.cert.exception.InvalidX500NameException;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+
+import java.security.PrivateKey;
+import java.util.Iterator;
+import java.util.Map;
+
+public class CommonCertUtils {
+ /**
+ * 如果不知道怎么填充names,可以查看org.bouncycastle.asn1.x500.style.BCStyle这个类,
+ * names的key值必须是BCStyle.DefaultLookUp中存在的(可以不关心大小写)
+ *
+ * @param names
+ * @return
+ * @throws InvalidX500NameException
+ */
+ public static X500Name buildX500Name(Map names) throws InvalidX500NameException {
+ if (names == null || names.size() == 0) {
+ throw new InvalidX500NameException("names can not be empty");
+ }
+ try {
+ X500NameBuilder builder = new X500NameBuilder();
+ Iterator itr = names.entrySet().iterator();
+ BCStyle x500NameStyle = (BCStyle) BCStyle.INSTANCE;
+ Map.Entry entry;
+ while (itr.hasNext()) {
+ entry = (Map.Entry) itr.next();
+ ASN1ObjectIdentifier oid = x500NameStyle.attrNameToOID((String) entry.getKey());
+ builder.addRDN(oid, (String) entry.getValue());
+ }
+ return builder.build();
+ } catch (Exception ex) {
+ throw new InvalidX500NameException(ex.getMessage(), ex);
+ }
+ }
+
+ public static PKCS10CertificationRequest createCSR(X500Name subject, SM2PublicKey pubKey, PrivateKey priKey,
+ String signAlgo) throws OperatorCreationException {
+ PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, pubKey);
+ ContentSigner signerBuilder = new JcaContentSignerBuilder(signAlgo)
+ .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(priKey);
+ return csrBuilder.build(signerBuilder);
+ }
+
+ public static AlgorithmIdentifier findSignatureAlgorithmIdentifier(String algoName) {
+ DefaultSignatureAlgorithmIdentifierFinder sigFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+ return sigFinder.find(algoName);
+ }
+
+ public static AlgorithmIdentifier findDigestAlgorithmIdentifier(String algoName) {
+ DefaultDigestAlgorithmIdentifierFinder digFinder = new DefaultDigestAlgorithmIdentifierFinder();
+ return digFinder.find(findSignatureAlgorithmIdentifier(algoName));
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/FileSNAllocator.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/FileSNAllocator.java
new file mode 100644
index 0000000..4d54f86
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/FileSNAllocator.java
@@ -0,0 +1,48 @@
+package com.sunyard.chsm.utils.gm.cert;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.math.BigInteger;
+
+public class FileSNAllocator implements CertSNAllocator {
+ private static final String SN_FILENAME = "sn.dat";
+ private static String snFilePath;
+
+ static {
+ ClassLoader loader = FileSNAllocator.class.getClassLoader();
+ snFilePath = loader.getResource(SN_FILENAME).getPath();
+ }
+
+ public synchronized BigInteger nextSerialNumber() throws Exception {
+ BigInteger sn = readSN();
+ writeSN(sn.add(BigInteger.ONE));
+ return sn;
+ }
+
+ private BigInteger readSN() throws IOException {
+ RandomAccessFile raf = null;
+ try {
+ raf = new RandomAccessFile(snFilePath, "r");
+ byte[] data = new byte[(int) raf.length()];
+ raf.read(data);
+ String snStr = new String(data);
+ return new BigInteger(snStr);
+ } finally {
+ if (raf != null) {
+ raf.close();
+ }
+ }
+ }
+
+ private void writeSN(BigInteger sn) throws IOException {
+ RandomAccessFile raf = null;
+ try {
+ raf = new RandomAccessFile(snFilePath, "rw");
+ raf.writeBytes(sn.toString(10));
+ } finally {
+ if (raf != null) {
+ raf.close();
+ }
+ }
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/RandomSNAllocator.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/RandomSNAllocator.java
new file mode 100644
index 0000000..b7b7939
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/RandomSNAllocator.java
@@ -0,0 +1,78 @@
+/*
+ *
+ * This is simplified version of the RandomSerialNumberGenerator from the project
+ * https://github.com/xipki/xipki.
+ */
+
+package com.sunyard.chsm.utils.gm.cert;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+/**
+ * Random serial number generator.
+ *
+ * This class is thread safe.
+ *
+ * @author Lijun Liao
+ */
+public class RandomSNAllocator implements CertSNAllocator {
+
+ /**
+ * The highest bit is always set to 1, so the effective bit length is bitLen - 1. To ensure that
+ * at least 64 bit entropy, bitLen must be at least 65.
+ */
+ private final static int MIN_SERIALNUMBER_SIZE = 65;
+
+ /**
+ * Since serial number should be positive and maximal 20 bytes, the maximal value of bitLen is
+ * 159.
+ */
+ private final static int MAX_SERIALNUMBER_SIZE = 159;
+
+ private static int[] AND_MASKS = new int[] {0xFF, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F};
+
+ private static int[] OR_MASKS = new int[] {0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40};
+
+ private final SecureRandom random;
+
+ private final int bitLen;
+
+ /**
+ * Constructor with the bitLen = 65.
+ */
+ public RandomSNAllocator() {
+ this(MIN_SERIALNUMBER_SIZE);
+ }
+
+ /**
+ * Constructor with the specification of bitLen.
+ * @param bitLen bit length of the serial number. The highest bit is always set to 1, so the
+ * effective bit length is bitLen - 1. Valid value is [65, 159].
+ */
+ public RandomSNAllocator(int bitLen) {
+ if (bitLen < MIN_SERIALNUMBER_SIZE || bitLen > MAX_SERIALNUMBER_SIZE) {
+ throw new IllegalArgumentException(String.format(
+ "%s may not be out of the range [%d, %d]: %d",
+ "bitLen", MIN_SERIALNUMBER_SIZE, MAX_SERIALNUMBER_SIZE, bitLen));
+ }
+
+ this.random = new SecureRandom();
+ this.bitLen = bitLen;
+ }
+
+ @Override
+ public BigInteger nextSerialNumber() {
+ final byte[] rdnBytes = new byte[(bitLen + 7) / 8];
+ final int ci = bitLen % 8;
+
+ random.nextBytes(rdnBytes);
+ if (ci != 0) {
+ rdnBytes[0] = (byte) (rdnBytes[0] & AND_MASKS[ci]);
+ }
+ rdnBytes[0] = (byte) (rdnBytes[0] | OR_MASKS[ci]);
+
+ return new BigInteger(1, rdnBytes);
+ }
+
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PrivateKey.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PrivateKey.java
new file mode 100644
index 0000000..9f95d08
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PrivateKey.java
@@ -0,0 +1,81 @@
+package com.sunyard.chsm.utils.gm.cert;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
+import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import java.io.IOException;
+import java.security.spec.ECParameterSpec;
+
+public class SM2PrivateKey extends BCECPrivateKey {
+ private transient DERBitString sm2PublicKey;
+ private boolean withCompression;
+
+ public SM2PrivateKey(BCECPrivateKey privateKey, BCECPublicKey publicKey) {
+ super(privateKey.getAlgorithm(), privateKey);
+ this.sm2PublicKey = getSM2PublicKeyDetails(new SM2PublicKey(publicKey.getAlgorithm(), publicKey));
+ this.withCompression = false;
+ }
+
+ @Override
+ public void setPointFormat(String style) {
+ withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+ }
+
+ /**
+ * Return a PKCS8 representation of the key. The sequence returned
+ * represents a full PrivateKeyInfo object.
+ *
+ * @return a PKCS8 representation of the key.
+ */
+ @Override
+ public byte[] getEncoded() {
+ ECParameterSpec ecSpec = getParams();
+ ProviderConfiguration configuration = BouncyCastleProvider.CONFIGURATION;
+ ASN1Encodable params = SM2PublicKey.ID_SM2_PUBKEY_PARAM;
+
+ int orderBitLength;
+ if (ecSpec == null) {
+ orderBitLength = ECUtil.getOrderBitLength(configuration, null, this.getS());
+ } else {
+ orderBitLength = ECUtil.getOrderBitLength(configuration, ecSpec.getOrder(), this.getS());
+ }
+
+ PrivateKeyInfo info;
+ org.bouncycastle.asn1.sec.ECPrivateKey keyStructure;
+
+ if (sm2PublicKey != null) {
+ keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(orderBitLength, this.getS(), sm2PublicKey, params);
+ } else {
+ keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(orderBitLength, this.getS(), params);
+ }
+
+ try {
+ info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), keyStructure);
+
+ return info.getEncoded(ASN1Encoding.DER);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private DERBitString getSM2PublicKeyDetails(SM2PublicKey pub) {
+ try {
+ SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded()));
+
+ return info.getPublicKeyData();
+ } catch (IOException e) { // should never happen
+ return null;
+ }
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PublicKey.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PublicKey.java
new file mode 100644
index 0000000..1433f04
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PublicKey.java
@@ -0,0 +1,44 @@
+package com.sunyard.chsm.utils.gm.cert;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
+
+public class SM2PublicKey extends BCECPublicKey {
+ public static final ASN1ObjectIdentifier ID_SM2_PUBKEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301");
+
+ private boolean withCompression;
+
+ public SM2PublicKey(BCECPublicKey key) {
+ super(key.getAlgorithm(), key);
+ this.withCompression = false;
+ }
+
+ public SM2PublicKey(String algorithm, BCECPublicKey key) {
+ super(algorithm, key);
+ this.withCompression = false;
+ }
+
+ @Override
+ public byte[] getEncoded() {
+ ASN1OctetString p = ASN1OctetString.getInstance(
+ new X9ECPoint(getQ(), withCompression).toASN1Primitive());
+
+ // stored curve is null if ImplicitlyCa
+ SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
+ new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, ID_SM2_PUBKEY_PARAM),
+ p.getOctets());
+
+ return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
+ }
+
+ @Override
+ public void setPointFormat(String style) {
+ withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2X509CertMaker.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2X509CertMaker.java
new file mode 100644
index 0000000..943d781
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2X509CertMaker.java
@@ -0,0 +1,280 @@
+package com.sunyard.chsm.utils.gm.cert;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
+import org.bouncycastle.asn1.x500.RDN;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x500.style.IETFUtils;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+public class SM2X509CertMaker {
+
+ private static enum CertLevel {
+ RootCA,
+ SubCA,
+ EndEntity
+ } // class CertLevel
+
+ public static final String SIGN_ALGO_SM3WITHSM2 = "SM3withSM2";
+
+ private long certExpire;
+ private X500Name issuerDN;
+ private CertSNAllocator snAllocator;
+ private KeyPair issuerKeyPair;
+
+ /**
+ * @param issuerKeyPair 证书颁发者的密钥对。
+ * 其实一般的CA的私钥都是要严格保护的。
+ * 一般CA的私钥都会放在加密卡/加密机里,证书的签名由加密卡/加密机完成。
+ * 这里仅是为了演示BC库签发证书的用法,所以暂时不作太多要求。
+ * @param certExpire 证书有效时间,单位毫秒
+ * @param issuer 证书颁发者信息
+ * @param snAllocator 维护/分配证书序列号的实例,证书序列号应该递增且不重复
+ */
+ public SM2X509CertMaker(KeyPair issuerKeyPair, long certExpire, X500Name issuer,
+ CertSNAllocator snAllocator) {
+ this.issuerKeyPair = issuerKeyPair;
+ this.certExpire = certExpire;
+ this.issuerDN = issuer;
+ this.snAllocator = snAllocator;
+ }
+
+ /**
+ * 生成根CA证书
+ *
+ * @param csr CSR
+ * @return 新的证书
+ * @throws Exception 如果错误发生
+ */
+ public X509Certificate makeRootCACert(byte[] csr)
+ throws Exception {
+ KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign);
+ return makeCertificate(CertLevel.RootCA, null, csr, usage, null);
+ }
+
+ /**
+ * 生成SubCA证书
+ *
+ * @param csr CSR
+ * @return 新的证书
+ * @throws Exception 如果错误发生
+ */
+ public X509Certificate makeSubCACert(byte[] csr)
+ throws Exception {
+ KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign);
+ return makeCertificate(CertLevel.SubCA, 0, csr, usage, null);
+ }
+
+ /**
+ * 生成SSL用户证书
+ *
+ * @param csr CSR
+ * @return 新的证书
+ * @throws Exception 如果错误发生
+ */
+ public X509Certificate makeSSLEndEntityCert(byte[] csr)
+ throws Exception {
+ return makeEndEntityCert(csr,
+ new KeyPurposeId[] {KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth});
+ }
+
+ /**
+ * 生成用户证书
+ *
+ * @param csr CSR
+ * @param extendedKeyUsages 扩展指数用途。
+ * @return 新的证书
+ * @throws Exception 如果错误发生
+ */
+ public X509Certificate makeEndEntityCert(byte[] csr,
+ KeyPurposeId[] extendedKeyUsages)
+ throws Exception {
+ KeyUsage usage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyAgreement
+ | KeyUsage.dataEncipherment | KeyUsage.keyEncipherment);
+ return makeCertificate(CertLevel.EndEntity, null, csr, usage, extendedKeyUsages);
+ }
+
+ /**
+ * @param isCA 是否是颁发给CA的证书
+ * @param keyUsage 证书用途
+ * @param csr CSR
+ * @return
+ * @throws Exception
+ */
+ private X509Certificate makeCertificate(CertLevel certLevel, Integer pathLenConstrain,
+ byte[] csr, KeyUsage keyUsage, KeyPurposeId[] extendedKeyUsages)
+ throws Exception {
+ if (certLevel == CertLevel.EndEntity) {
+ if (keyUsage.hasUsages(KeyUsage.keyCertSign)) {
+ throw new IllegalArgumentException(
+ "keyusage keyCertSign is not allowed in EndEntity Certificate");
+ }
+ }
+
+ PKCS10CertificationRequest request = new PKCS10CertificationRequest(csr);
+ SubjectPublicKeyInfo subPub = request.getSubjectPublicKeyInfo();
+
+ PrivateKey issPriv = issuerKeyPair.getPrivate();
+ PublicKey issPub = issuerKeyPair.getPublic();
+
+ X500Name subject = request.getSubject();
+ String email = null;
+ String commonName = null;
+ /*
+ * RFC 5280 §4.2.1.6 Subject
+ * Conforming implementations generating new certificates with
+ * electronic mail addresses MUST use the rfc822Name in the subject
+ * alternative name extension (Section 4.2.1.6) to describe such
+ * identities. Simultaneous inclusion of the emailAddress attribute in
+ * the subject distinguished name to support legacy implementations is
+ * deprecated but permitted.
+ */
+ RDN[] rdns = subject.getRDNs();
+ List newRdns = new ArrayList<>(rdns.length);
+ for (int i = 0; i < rdns.length; i++) {
+ RDN rdn = rdns[i];
+
+ AttributeTypeAndValue atv = rdn.getFirst();
+ ASN1ObjectIdentifier type = atv.getType();
+ if (BCStyle.EmailAddress.equals(type)) {
+ email = IETFUtils.valueToString(atv.getValue());
+ } else {
+ if (BCStyle.CN.equals(type)) {
+ commonName = IETFUtils.valueToString(atv.getValue());
+ }
+ newRdns.add(rdn);
+ }
+ }
+
+ List subjectAltNames = new LinkedList<>();
+ if (email != null) {
+ subject = new X500Name(newRdns.toArray(new RDN[0]));
+ subjectAltNames.add(
+ new GeneralName(GeneralName.rfc822Name,
+ new DERIA5String(email, true)));
+ }
+
+ boolean selfSignedEECert = false;
+ switch (certLevel) {
+ case RootCA:
+ if (issuerDN.equals(subject)) {
+ subject = issuerDN;
+ } else {
+ throw new IllegalArgumentException("subject != issuer for certLevel " + CertLevel.RootCA);
+ }
+ break;
+ case SubCA:
+ if (issuerDN.equals(subject)) {
+ throw new IllegalArgumentException(
+ "subject MUST not equals issuer for certLevel " + certLevel);
+ }
+ break;
+ default:
+ if (issuerDN.equals(subject)) {
+ selfSignedEECert = true;
+ subject = issuerDN;
+ }
+ }
+
+ BigInteger serialNumber = snAllocator.nextSerialNumber();
+ Date notBefore = new Date();
+ Date notAfter = new Date(notBefore.getTime() + certExpire);
+ X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
+ issuerDN, serialNumber,
+ notBefore, notAfter,
+ subject, subPub);
+
+ JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+ v3CertGen.addExtension(Extension.subjectKeyIdentifier, false,
+ extUtils.createSubjectKeyIdentifier(subPub));
+ if (certLevel != CertLevel.RootCA && !selfSignedEECert) {
+ v3CertGen.addExtension(Extension.authorityKeyIdentifier, false,
+ extUtils.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(issPub.getEncoded())));
+ }
+
+ // RFC 5280 §4.2.1.9 Basic Constraints:
+ // Conforming CAs MUST include this extension in all CA certificates
+ // that contain public keys used to validate digital signatures on
+ // certificates and MUST mark the extension as critical in such
+ // certificates.
+ BasicConstraints basicConstraints;
+ if (certLevel == CertLevel.EndEntity) {
+ basicConstraints = new BasicConstraints(false);
+ } else {
+ basicConstraints = pathLenConstrain == null
+ ? new BasicConstraints(true) : new BasicConstraints(pathLenConstrain.intValue());
+ }
+ v3CertGen.addExtension(Extension.basicConstraints, true, basicConstraints);
+
+ // RFC 5280 §4.2.1.3 Key Usage: When present, conforming CAs SHOULD mark this extension as critical.
+ v3CertGen.addExtension(Extension.keyUsage, true, keyUsage);
+
+ if (extendedKeyUsages != null) {
+ ExtendedKeyUsage xku = new ExtendedKeyUsage(extendedKeyUsages);
+ v3CertGen.addExtension(Extension.extendedKeyUsage, false, xku);
+
+ boolean forSSLServer = false;
+ for (KeyPurposeId purposeId : extendedKeyUsages) {
+ if (KeyPurposeId.id_kp_serverAuth.equals(purposeId)) {
+ forSSLServer = true;
+ break;
+ }
+ }
+
+ if (forSSLServer) {
+ if (commonName == null) {
+ throw new IllegalArgumentException("commonName must not be null");
+ }
+ GeneralName name = new GeneralName(GeneralName.dNSName,
+ new DERIA5String(commonName, true));
+ subjectAltNames.add(name);
+ }
+ }
+
+ if (!subjectAltNames.isEmpty()) {
+ v3CertGen.addExtension(Extension.subjectAlternativeName, false,
+ new GeneralNames(subjectAltNames.toArray(new GeneralName[0])));
+ }
+
+ JcaContentSignerBuilder contentSignerBuilder = makeContentSignerBuilder(issPub);
+ X509Certificate cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
+ .getCertificate(v3CertGen.build(contentSignerBuilder.build(issPriv)));
+ cert.verify(issPub);
+
+ return cert;
+ }
+
+ private JcaContentSignerBuilder makeContentSignerBuilder(PublicKey issPub) throws Exception {
+ if (issPub.getAlgorithm().equals("EC")) {
+ JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGN_ALGO_SM3WITHSM2);
+ contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
+ return contentSignerBuilder;
+ }
+ throw new Exception("Unsupported PublicKey Algorithm:" + issPub.getAlgorithm());
+ }
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/exception/InvalidX500NameException.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/exception/InvalidX500NameException.java
new file mode 100644
index 0000000..92f5874
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/exception/InvalidX500NameException.java
@@ -0,0 +1,21 @@
+package com.sunyard.chsm.utils.gm.cert.exception;
+
+public class InvalidX500NameException extends Exception {
+ private static final long serialVersionUID = 3192247087539921768L;
+
+ public InvalidX500NameException() {
+ super();
+ }
+
+ public InvalidX500NameException(String message) {
+ super(message);
+ }
+
+ public InvalidX500NameException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public InvalidX500NameException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/chsm-web-manage/pom.xml b/chsm-web-manage/pom.xml
index 8e53a91..426e23d 100644
--- a/chsm-web-manage/pom.xml
+++ b/chsm-web-manage/pom.xml
@@ -49,6 +49,10 @@
jedis
+
+ javax.persistence
+ javax.persistence-api
+
com.dm
DmJdbcDriver
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/ApplicationController.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/ApplicationController.java
new file mode 100644
index 0000000..22fc89e
--- /dev/null
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/ApplicationController.java
@@ -0,0 +1,8 @@
+package com.sunyard.chsm.controller;
+
+/**
+ * @author liulu
+ * @since 2024/10/29
+ */
+public class ApplicationController {
+}
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/KeyInfoAsymController.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/KeyInfoAsymController.java
index 8def252..af7d71e 100644
--- a/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/KeyInfoAsymController.java
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/KeyInfoAsymController.java
@@ -21,6 +21,7 @@ import javax.validation.Valid;
/**
* 非对称密钥管理接口
+ *
* @author liulu
* @since 2024/10/28
*/
@@ -71,5 +72,14 @@ public class KeyInfoAsymController {
.body(new ByteArrayResource(content));
}
+ @PostMapping("/createCsr")
+ public R createCsr(KeyInfoDTO.CreateCsr createCsr) {
+
+ String csr = keyInfoService.createCsr(createCsr);
+
+ KeyInfoDTO.CreateCSRResp resp = new KeyInfoDTO.CreateCSRResp();
+ resp.setCsr(csr);
+ return R.data(resp);
+ }
}
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/dto/KeyInfoDTO.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/dto/KeyInfoDTO.java
index 5488025..f947839 100644
--- a/chsm-web-manage/src/main/java/com/sunyard/chsm/dto/KeyInfoDTO.java
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/dto/KeyInfoDTO.java
@@ -1,6 +1,7 @@
package com.sunyard.chsm.dto;
import com.sunyard.chsm.model.PageQuery;
+import com.sunyard.chsm.model.Subject;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -118,5 +119,21 @@ public abstract class KeyInfoDTO {
}
+ @EqualsAndHashCode(callSuper = true)
+ @Data
+ public static class CreateCsr extends Subject {
+ /**
+ * 密钥id
+ */
+ @NotNull(message = "密钥id不能为空")
+ private Long id;
+ }
+
+ @Data
+ public static class CreateCSRResp {
+ // 证书请求内容
+ private String csr;
+ }
+
}
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/KeyInfoService.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/KeyInfoService.java
index c47fbd5..a265bae 100644
--- a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/KeyInfoService.java
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/KeyInfoService.java
@@ -22,6 +22,8 @@ public interface KeyInfoService {
String recoveryKey(InputStream is);
+ String createCsr(KeyInfoDTO.CreateCsr createCsr);
+
void enableKey(List ids);
void disableKey(List ids);
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyInfoServiceImpl.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyInfoServiceImpl.java
index 5f82d17..6b4dac9 100644
--- a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyInfoServiceImpl.java
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyInfoServiceImpl.java
@@ -9,9 +9,11 @@ import com.sunyard.chsm.dto.KeyInfoDTO;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.KeyStatus;
import com.sunyard.chsm.enums.KeyUsage;
+import com.sunyard.chsm.mapper.KeyCsrMapper;
import com.sunyard.chsm.mapper.KeyInfoMapper;
import com.sunyard.chsm.mapper.KeyTemplateMapper;
import com.sunyard.chsm.mapper.SpKeyRecordMapper;
+import com.sunyard.chsm.model.entity.KeyCsr;
import com.sunyard.chsm.model.entity.KeyInfo;
import com.sunyard.chsm.model.entity.KeyRecord;
import com.sunyard.chsm.model.entity.KeyTemplate;
@@ -19,9 +21,23 @@ import com.sunyard.chsm.sdf.SdfApiService;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.service.KeyInfoService;
import com.sunyard.chsm.utils.JsonUtils;
+import com.sunyard.chsm.utils.gm.BCECUtils;
+import com.sunyard.chsm.utils.gm.cert.CommonCertUtils;
+import com.sunyard.chsm.utils.gm.cert.SM2PublicKey;
+import com.sunyard.chsm.utils.gm.cert.SM2X509CertMaker;
import com.sunyard.ssp.common.exception.SspwebException;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.codec.binary.Hex;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+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.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.io.pem.PemObject;
+import org.bouncycastle.util.io.pem.PemWriter;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -35,12 +51,14 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -55,6 +73,8 @@ import java.util.stream.Collectors;
@Service
public class KeyInfoServiceImpl implements KeyInfoService {
+ @Resource
+ private KeyCsrMapper keyCsrMapper;
@Resource
private KeyInfoMapper keyInfoMapper;
@Resource
@@ -210,18 +230,20 @@ public class KeyInfoServiceImpl implements KeyInfoService {
if (KeyCategory.SYM_KEY.getCode().equals(info.getKeyType())) {
byte[] symKey = sdfApiService.generateRandom(16);
- record.setKeyData(Hex.encodeHexString(symKey));
- String checkHash = Hex.encodeHexString(sdfApiService.hash(symKey));
+ byte[] encSymKey = sdfApiService.encryptByMKNoPadding(symKey);
+ record.setKeyData(Hex.toHexString(encSymKey));
+ String checkHash = Hex.toHexString(sdfApiService.hash(symKey));
record.setCheckValue(checkHash);
} else {
EccKey eccKey = sdfApiService.genKeyPairEcc();
byte[] d = eccKey.getPriKey().getD();
- record.setKeyData(Hex.encodeHexString(d));
- String checkHash = Hex.encodeHexString(sdfApiService.hash(d));
+ byte[] encD = sdfApiService.encryptByMKNoPadding(d);
+ record.setKeyData(Hex.toHexString(encD));
+ String checkHash = Hex.toHexString(sdfApiService.hash(d));
record.setCheckValue(checkHash);
byte[] pubKeyBytes = eccKey.getPubKey().getPubKeyBytes();
- record.setPubKey(Hex.encodeHexString(pubKeyBytes));
+ record.setPubKey(Hex.toHexString(pubKeyBytes));
}
return record;
}
@@ -254,7 +276,7 @@ public class KeyInfoServiceImpl implements KeyInfoService {
public String recoveryKey(InputStream is) {
int suc = 0, count = 0, err = 0, exd = 0;
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))){
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
String line;
while (true) {
try {
@@ -290,6 +312,75 @@ public class KeyInfoServiceImpl implements KeyInfoService {
return String.format("恢复完成,共%d条数据,跳过已经存在的密钥%d条,恢复成功%d条,解析失败%d条", count, exd, suc, err);
}
+ @Override
+ public String createCsr(KeyInfoDTO.CreateCsr createCsr) {
+
+ String subject = createCsr.getDN();
+ KeyCsr exist = keyCsrMapper.selectBySubject(subject);
+ if (Objects.nonNull(exist)) {
+ if (Objects.equals(exist.getKeyId(), createCsr.getId())) {
+ return exist.getCsrTxt();
+ }
+ throw new IllegalArgumentException("此证书主题已经被使用!");
+ }
+ KeyInfo keyInfo = keyInfoMapper.selectById(createCsr.getId());
+ Assert.notNull(keyInfo, "密钥不存在!");
+ LocalDateTime now = LocalDateTime.now();
+ Assert.isTrue(KeyCategory.ASYM_KEY.getCode().equals(keyInfo.getKeyType())
+ && KeyStatus.ENABLED.getCode().equals(keyInfo.getStatus())
+ && now.isAfter(keyInfo.getEffectiveTime())
+ && now.isBefore(keyInfo.getExpiredTime()),
+ "只能选择已启用的非对称密钥生成证书请求");
+ KeyRecord record = spKeyRecordMapper.selectUsedByKeyId(keyInfo.getId());
+ Assert.notNull(record, "数据异常,没有在使用中的密钥");
+
+ X500Name dn = new X500Name(subject);
+
+ byte[] xy = Hex.decode(record.getPubKey());
+ byte[] x = Arrays.copyOfRange(xy, 0, 32);
+ byte[] y = Arrays.copyOfRange(xy, 32, 64);
+ ECPublicKeyParameters pubKeyParam = BCECUtils.createECPublicKeyParameters(x, y);
+
+ byte[] priKeyBytes = sdfApiService.decryptByMKNoPadding(Hex.decode(record.getKeyData()));
+// byte[][] pri18 = LangUtils.splitAverage(priKeyBytes);
+ ECPrivateKeyParameters priKeyParam = BCECUtils.createECPrivateKeyParameters(priKeyBytes);
+ ECDomainParameters domainParams = priKeyParam.getParameters();
+ ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(), domainParams.getN(), domainParams.getH());
+ BCECPublicKey pubKey = new BCECPublicKey("EC", pubKeyParam, spec, BouncyCastleProvider.CONFIGURATION);
+ BCECPrivateKey priKey = new BCECPrivateKey("EC", priKeyParam, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
+//
+ SM2PublicKey sm2SubPub = new SM2PublicKey(pubKey.getAlgorithm(), pubKey);
+ String csrTxt;
+ try {
+ byte[] csr = CommonCertUtils.createCSR(dn, sm2SubPub, priKey, SM2X509CertMaker.SIGN_ALGO_SM3WITHSM2).getEncoded();
+
+ PemObject pem = new PemObject("CERTIFICATE REQUEST", csr);
+ StringWriter str = new StringWriter();
+ PemWriter pemWriter = new PemWriter(str);
+ pemWriter.writeObject(pem);
+ pemWriter.close();
+ str.close();
+
+ csrTxt = str.toString();
+ } catch (Exception e) {
+ log.error("生成CSR异常", e);
+ throw new IllegalArgumentException("生成证书请求异常");
+ }
+ KeyCsr csr = new KeyCsr();
+ csr.setId(IdWorker.getId());
+ csr.setApplicationId(keyInfo.getApplicationId());
+ csr.setKeyId(keyInfo.getId());
+ csr.setKeyRecordId(record.getId());
+ csr.setPubKey(record.getPubKey());
+ csr.setKeyData(record.getKeyData());
+ csr.setSubject(subject);
+ csr.setCreateTime(LocalDateTime.now());
+ csr.setCsrTxt(csrTxt);
+ keyCsrMapper.insert(csr);
+ return csrTxt;
+ }
+
+
@Override
public void enableKey(List ids) {
if (CollectionUtils.isEmpty(ids)) {
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyTemplateServiceImpl.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyTemplateServiceImpl.java
index 5c0851e..51dda9c 100644
--- a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyTemplateServiceImpl.java
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyTemplateServiceImpl.java
@@ -42,13 +42,13 @@ public class KeyTemplateServiceImpl implements KeyTemplateService {
@Override
public Page selectPageList(KeyTemplateDTO.Query query) {
- LambdaQueryWrapper wrapper = new LambdaQueryWrapper()
- .eq(StringUtils.hasText(query.getKeyType()), KeyTemplate::getKeyType, query.getKeyType())
- .orderByDesc(KeyTemplate::getCreateTime);
IPage page = keyTemplateMapper.selectPage(
new Page<>(query.getPageNumber(), query.getPageSize()),
- wrapper);
+ new LambdaQueryWrapper()
+ .eq(StringUtils.hasText(query.getKeyType()), KeyTemplate::getKeyType, query.getKeyType())
+ .orderByDesc(KeyTemplate::getCreateTime)
+ );
List records = page.getRecords();
if (CollectionUtils.isEmpty(records)) {
return new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
@@ -145,7 +145,7 @@ public class KeyTemplateServiceImpl implements KeyTemplateService {
.map(KeyUsage::valueOf)
.collect(Collectors.toList());
- if (KeyCategory.SYM_KEY == keyCategory ) {
+ if (KeyCategory.SYM_KEY == keyCategory) {
Assert.isTrue(!usageList.contains(KeyUsage.SIGN_VERIFY), "对称密钥不能用于签名验签");
} else {
Assert.isTrue(!usageList.contains(KeyUsage.HMAC), "非对称密钥不能用于计算HMac");
diff --git a/doc/ssp_dm.sql b/doc/ssp_dm.sql
new file mode 100644
index 0000000..56cd9e1
--- /dev/null
+++ b/doc/ssp_dm.sql
@@ -0,0 +1,148 @@
+
+-- 密码设备
+CREATE TABLE sp_device (
+ id BIGINT NOT NULL COMMENT 'id',
+ name VARCHAR(255) COMMENT '名称',
+ device_number VARCHAR(255) COMMENT '编号',
+ manufacturer VARCHAR(255) COMMENT '制造厂商',
+ manufacturer_model VARCHAR(255) COMMENT '制造厂商型号',
+ service_ip VARCHAR(30) COMMENT '服务ip',
+ service_port INT COMMENT '服务端口',
+ manage_ip VARCHAR(30) COMMENT '管理ip',
+ manage_port INT COMMENT '管理端口',
+ access_credentials VARCHAR(1000) COMMENT '访问凭证',
+ status VARCHAR(25) DEFAULT '' COMMENT '设备状态',
+ group_id BIGINT NOT NULL DEFAULT 0 COMMENT '设备组id',
+ group_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '设备组名称',
+ weight INT DEFAULT 1 COMMENT '负载时权重',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
+
+CREATE TABLE sp_device_group (
+ id BIGINT NOT NULL COMMENT 'id',
+ name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '服务名称',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
+
+-- 密码服务
+CREATE TABLE sp_crypto_service (
+ id BIGINT NOT NULL COMMENT 'id',
+ name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '服务名称',
+ device_group_id BIGINT NOT NULL DEFAULT 0 COMMENT '设备组id',
+ device_group_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '设备组名称',
+ status VARCHAR(50) NOT NULL DEFAULT '' COMMENT '状态',
+ creator_id BIGINT COMMENT '创建者id',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
+
+-- 密码服务api
+CREATE TABLE sp_crypto_service_api (
+ id BIGINT NOT NULL COMMENT 'id',
+ crypto_service_id BIGINT NOT NULL COMMENT '密码服务id',
+ api_group VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'API分组',
+ api_code VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'API标识',
+ api_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'API名称',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
+
+-- 业务应用
+CREATE TABLE sp_application (
+ id BIGINT NOT NULL COMMENT 'id',
+ name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '应用名称',
+ bind_service VARCHAR(1020) NOT NULL DEFAULT '' COMMENT '密码服务 ,分隔',
+ app_key VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'app_key',
+ app_secret VARCHAR(100) NOT NULL DEFAULT '' COMMENT 'app_secret',
+ status VARCHAR(50) NOT NULL DEFAULT '' COMMENT '状态',
+ creator_id BIGINT COMMENT '创建者id',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
+
+-- 密钥模版
+CREATE TABLE sp_key_template (
+ id BIGINT NOT NULL COMMENT 'id',
+ code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '编号',
+ name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '名称',
+ key_type VARCHAR(30) NOT NULL DEFAULT '' COMMENT '密钥类型',
+ key_alg VARCHAR(30) NOT NULL DEFAULT '' COMMENT '密钥算法',
+ key_length INT NOT NULL DEFAULT 0 COMMENT '密钥长度',
+ key_usage INT NOT NULL DEFAULT 0 COMMENT '密钥用途',
+ check_alg VARCHAR(30) NOT NULL DEFAULT '' COMMENT '校验算法',
+ check_value VARCHAR(255) NOT NULL DEFAULT '' COMMENT '校验值',
+ valid_time INT NOT NULL DEFAULT 0 COMMENT '有效期',
+ valid_unit VARCHAR(30) NOT NULL DEFAULT '' COMMENT '有效期时间单位',
+ start_after_create_time INT NOT NULL DEFAULT 0 COMMENT '创建后多长时间生效',
+ start_after_create_unit VARCHAR(30) NOT NULL DEFAULT '' COMMENT '创建后多长时间生效时间单位',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
+
+-- 密钥信息
+CREATE TABLE sp_key_info (
+ id BIGINT NOT NULL COMMENT 'id',
+ application_id BIGINT NOT NULL COMMENT '应用id',
+ key_template_id BIGINT NOT NULL COMMENT '模版id',
+ code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '编号',
+ key_type VARCHAR(30) NOT NULL DEFAULT '' COMMENT '密钥分类',
+ key_alg VARCHAR(30) NOT NULL DEFAULT '' COMMENT '密钥算法',
+ key_length INT NOT NULL DEFAULT 0 COMMENT '密钥长度',
+ key_usage INT NOT NULL DEFAULT 0 COMMENT '密钥用途',
+ status VARCHAR(30) NOT NULL DEFAULT '' COMMENT '密钥状态',
+ check_alg VARCHAR(30) NOT NULL DEFAULT '' COMMENT '校验算法',
+ check_value VARCHAR(255) NOT NULL DEFAULT '' COMMENT '校验值',
+ effective_time TIMESTAMP COMMENT '启用时间',
+ expired_time TIMESTAMP COMMENT '停用时间',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
+
+-- 密钥记录
+CREATE TABLE sp_key_record (
+ id BIGINT NOT NULL COMMENT 'id',
+ key_id BIGINT NOT NULL COMMENT '密钥id',
+ key_index VARCHAR(100) NOT NULL DEFAULT '' COMMENT '密钥索引',
+ key_data VARCHAR(255) NOT NULL DEFAULT '' COMMENT '密钥密文',
+ pub_key VARCHAR(400) NOT NULL DEFAULT '' COMMENT '公钥',
+ check_alg VARCHAR(30) NOT NULL DEFAULT '' COMMENT '校验算法',
+ check_value VARCHAR(255) NOT NULL DEFAULT '' COMMENT '校验值',
+ effective_time TIMESTAMP COMMENT '生效时间',
+ expired_time TIMESTAMP COMMENT '过期时间',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
+
+-- 证书请求记录
+CREATE TABLE sp_key_csr (
+ id BIGINT NOT NULL COMMENT 'id',
+ application_id BIGINT NOT NULL COMMENT '应用id',
+ key_id BIGINT NOT NULL COMMENT '密钥id',
+ key_record_id BIGINT NOT NULL COMMENT '密钥记录id',
+ subject VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'DN',
+ key_data VARCHAR(255) NOT NULL DEFAULT '' COMMENT '密钥密文',
+ pub_key VARCHAR(400) NOT NULL DEFAULT '' COMMENT '公钥',
+ csr_txt VARCHAR(2000) COMMENT '证书',
+ remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+ PRIMARY KEY (id)
+);
\ No newline at end of file