diff --git a/admin/src/main/resources/static/designer/editor-app/stencil-controller.js b/admin/src/main/resources/static/designer/editor-app/stencil-controller.js
index 5a1fdf9..bf16ded 100644
--- a/admin/src/main/resources/static/designer/editor-app/stencil-controller.js
+++ b/admin/src/main/resources/static/designer/editor-app/stencil-controller.js
@@ -85,7 +85,7 @@ angular.module('flowableModeler')
}
// Check all received items
- debugger
+ //debugger
for (var stencilIndex = 0; stencilIndex < data.stencils.length; stencilIndex++) {
// Check if the root group is the 'diagram' group. If so, this item should not be shown.
diff --git a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/activity/list/type.decision.png b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/activity/list/type.decision.png
index 0351fee..fef769c 100644
Binary files a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/activity/list/type.decision.png and b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/activity/list/type.decision.png differ
diff --git a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/activity/list/type.http.png b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/activity/list/type.http.png
index ffba8de..5b64cfb 100644
Binary files a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/activity/list/type.http.png and b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/activity/list/type.http.png differ
diff --git a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/endevent/cancel.png b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/endevent/cancel.png
index a4302bf..af0982d 100644
Binary files a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/endevent/cancel.png and b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/endevent/cancel.png differ
diff --git a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/endevent/terminate.png b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/endevent/terminate.png
index 0d6f780..4165a1f 100644
Binary files a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/endevent/terminate.png and b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/endevent/terminate.png differ
diff --git a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/startevent/conditional.png b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/startevent/conditional.png
index fe56892..7f13f3b 100644
Binary files a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/startevent/conditional.png and b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/startevent/conditional.png differ
diff --git a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/startevent/signal.png b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/startevent/signal.png
index 828a260..92adefc 100644
Binary files a/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/startevent/signal.png and b/admin/src/main/resources/static/designer/editor-app/stencilsets/bpmn2.0/icons/startevent/signal.png differ
diff --git a/common/pom.xml b/common/pom.xml
index 1909581..aca3066 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -29,6 +29,12 @@
spring-web
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
org.apache.shiro
@@ -105,6 +111,16 @@
hutool-all
+
+
+ org.jasig.cas.client
+ cas-client-core
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+
+
\ No newline at end of file
diff --git a/common/src/main/java/com/ruoyi/common/utils/sm/SM2EngineExtend.java b/common/src/main/java/com/ruoyi/common/utils/sm/SM2EngineExtend.java
new file mode 100644
index 0000000..be4916e
--- /dev/null
+++ b/common/src/main/java/com/ruoyi/common/utils/sm/SM2EngineExtend.java
@@ -0,0 +1,282 @@
+package com.ruoyi.common.utils.sm;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.digests.SM3Digest;
+import org.bouncycastle.crypto.params.*;
+import org.bouncycastle.math.ec.ECConstants;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+public class SM2EngineExtend {
+ private final Digest digest;
+
+ /**是否为加密模式*/
+ private boolean forEncryption;
+ private ECKeyParameters ecKey;
+ private ECDomainParameters ecParams;
+ private int curveLength;
+ private SecureRandom random;
+ /**密文排序方式*/
+ private int cipherMode;
+
+ /**BC库默认排序方式-C1C2C3*/
+ public static int CIPHERMODE_BC = 0;
+ /**国密标准排序方式-C1C3C2*/
+ public static int CIPHERMODE_NORM = 1;
+
+ public SM2EngineExtend() {
+ this(new SM3Digest());
+ }
+
+ public SM2EngineExtend(Digest digest) {
+ this.digest = digest;
+ }
+
+ /**
+ * 设置密文排序方式
+ * @param cipherMode
+ */
+ public void setCipherMode(int cipherMode){
+ this.cipherMode = cipherMode;
+ }
+
+ /**
+ * 默认初始化方法,使用国密排序标准
+ * @param forEncryption - 是否以加密模式初始化
+ * @param param - 曲线参数
+ */
+ public void init(boolean forEncryption, CipherParameters param) {
+ init(forEncryption, CIPHERMODE_NORM, param);
+ }
+
+ /**
+ * 默认初始化方法,使用国密排序标准
+ * @param forEncryption 是否以加密模式初始化
+ * @param cipherMode 加密数据排列模式:1-标准排序;0-BC默认排序
+ * @param param 曲线参数
+ */
+ public void init(boolean forEncryption, int cipherMode, CipherParameters param) {
+ this.forEncryption = forEncryption;
+ this.cipherMode = cipherMode;
+ if (forEncryption) {
+ ParametersWithRandom rParam = (ParametersWithRandom) param;
+
+ ecKey = (ECKeyParameters) rParam.getParameters();
+ ecParams = ecKey.getParameters();
+
+ ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
+ if (s.isInfinity()) {
+ throw new IllegalArgumentException("invalid key: [h]Q at infinity");
+ }
+
+ random = rParam.getRandom();
+ } else {
+ ecKey = (ECKeyParameters) param;
+ ecParams = ecKey.getParameters();
+ }
+
+ curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
+ }
+
+ /**
+ * 加密或解密输入数据
+ * @param in
+ * @param inOff
+ * @param inLen
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ public byte[] processBlock( byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
+ if (forEncryption) {
+ // 加密
+ return encrypt(in, inOff, inLen);
+ } else {
+ return decrypt(in, inOff, inLen);
+ }
+ }
+
+ /**
+ * 加密实现,根据cipherMode输出指定排列的结果,默认按标准方式排列
+ * @param in
+ * @param inOff
+ * @param inLen
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ private byte[] encrypt(byte[] in, int inOff, int inLen)
+ throws InvalidCipherTextException {
+ byte[] c2 = new byte[inLen];
+
+ System.arraycopy(in, inOff, c2, 0, c2.length);
+
+ byte[] c1;
+ ECPoint kPB;
+ do {
+ BigInteger k = nextK();
+
+ ECPoint c1P = ecParams.getG().multiply(k).normalize();
+
+ c1 = c1P.getEncoded(false);
+
+ kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();
+
+ kdf(digest, kPB, c2);
+ }
+ while (notEncrypted(c2, in, inOff));
+
+ byte[] c3 = new byte[digest.getDigestSize()];
+
+ addFieldElement(digest, kPB.getAffineXCoord());
+ digest.update(in, inOff, inLen);
+ addFieldElement(digest, kPB.getAffineYCoord());
+
+ digest.doFinal(c3, 0);
+ if (cipherMode == CIPHERMODE_NORM){
+ return Arrays.concatenate(c1, c3, c2);
+ }
+ return Arrays.concatenate(c1, c2, c3);
+ }
+
+ /**
+ * 解密实现,默认按标准排列方式解密,解密时解出c2部分原文并校验c3部分
+ * @param in
+ * @param inOff
+ * @param inLen
+ * @return
+ * @throws InvalidCipherTextException
+ */
+ private byte[] decrypt(byte[] in, int inOff, int inLen)
+ throws InvalidCipherTextException {
+ byte[] c1 = new byte[curveLength * 2 + 1];
+
+ System.arraycopy(in, inOff, c1, 0, c1.length);
+
+ ECPoint c1P = ecParams.getCurve().decodePoint(c1);
+
+ ECPoint s = c1P.multiply(ecParams.getH());
+ if (s.isInfinity()) {
+ throw new InvalidCipherTextException("[h]C1 at infinity");
+ }
+
+ c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();
+
+ byte[] c2 = new byte[inLen - c1.length - digest.getDigestSize()];
+ if (cipherMode == CIPHERMODE_BC) {
+ System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);
+ }else{
+ // C1 C3 C2
+ System.arraycopy(in, inOff + c1.length + digest.getDigestSize(), c2, 0, c2.length);
+ }
+
+ kdf(digest, c1P, c2);
+
+ byte[] c3 = new byte[digest.getDigestSize()];
+
+ addFieldElement(digest, c1P.getAffineXCoord());
+ digest.update(c2, 0, c2.length);
+ addFieldElement(digest, c1P.getAffineYCoord());
+
+ digest.doFinal(c3, 0);
+
+ int check = 0;
+ // 检查密文输入值C3部分和由摘要生成的C3是否一致
+ if (cipherMode == CIPHERMODE_BC) {
+ for (int i = 0; i != c3.length; i++) {
+ check |= c3[i] ^ in[c1.length + c2.length + i];
+ }
+ }else{
+ for (int i = 0; i != c3.length; i++) {
+ check |= c3[i] ^ in[c1.length + i];
+ }
+ }
+
+ clearBlock(c1);
+ clearBlock(c3);
+
+ if (check != 0) {
+ clearBlock(c2);
+ throw new InvalidCipherTextException("invalid cipher text");
+ }
+
+ return c2;
+ }
+
+ private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
+ for (int i = 0; i != encData.length; i++) {
+ if (encData[i] != in[inOff]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void kdf(Digest digest, ECPoint c1, byte[] encData) {
+ int ct = 1;
+ int v = digest.getDigestSize();
+
+ byte[] buf = new byte[digest.getDigestSize()];
+ int off = 0;
+
+ for (int i = 1; i <= ((encData.length + v - 1) / v); i++) {
+ addFieldElement(digest, c1.getAffineXCoord());
+ addFieldElement(digest, c1.getAffineYCoord());
+ digest.update((byte) (ct >> 24));
+ digest.update((byte) (ct >> 16));
+ digest.update((byte) (ct >> 8));
+ digest.update((byte) ct);
+
+ digest.doFinal(buf, 0);
+
+ if (off + buf.length < encData.length) {
+ xor(encData, buf, off, buf.length);
+ } else {
+ xor(encData, buf, off, encData.length - off);
+ }
+
+ off += buf.length;
+ ct++;
+ }
+ }
+
+ private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
+ for (int i = 0; i != dRemaining; i++) {
+ data[dOff + i] ^= kdfOut[i];
+ }
+ }
+
+ private BigInteger nextK() {
+ int qBitLength = ecParams.getN().bitLength();
+
+ BigInteger k;
+ do {
+ k = new BigInteger(qBitLength, random);
+ }
+ while (k.equals(ECConstants.ZERO) || k.compareTo(ecParams.getN()) >= 0);
+
+ return k;
+ }
+
+ private void addFieldElement(Digest digest, ECFieldElement v) {
+ byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());
+
+ digest.update(p, 0, p.length);
+ }
+
+ /**
+ * clear possible sensitive data
+ */
+ private void clearBlock(
+ byte[] block) {
+ for (int i = 0; i != block.length; i++) {
+ block[i] = 0;
+ }
+ }
+}
diff --git a/common/src/main/java/com/ruoyi/common/utils/sm/SM2Utils.java b/common/src/main/java/com/ruoyi/common/utils/sm/SM2Utils.java
new file mode 100644
index 0000000..b2d844c
--- /dev/null
+++ b/common/src/main/java/com/ruoyi/common/utils/sm/SM2Utils.java
@@ -0,0 +1,177 @@
+package com.ruoyi.common.utils.sm;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.bouncycastle.asn1.gm.GMNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.*;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.encoders.Hex;
+
+import java.math.BigInteger;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+/**
+ * SM2 加解密攻击类
+ *
+ * 注意:为了兼容前端js库(原文转base64后加密),后端使用加密方法请先转Base64字符, 否则解密会失败!
+ * @author ZYL
+ *
+ */
+@Slf4j
+public class SM2Utils {
+ public static void main(String[] args) {
+// SM2KeyPair sM2KeyPair = getSm2Keys(false);
+// System.out.println(sM2KeyPair.getPubKey());
+// System.out.println(sM2KeyPair.getPriKey());
+
+
+ String a = Base64.encodeBase64String("123".getBytes());
+ System.out.println("文本="+a);
+ String aa = SM2Utils.encrypt(SM2crypto.pubKey, a, SM2EngineExtend.CIPHERMODE_BC);
+ System.out.println("密文="+aa);
+ String aaa = SM2Utils.decrypt(SM2crypto.priKey, aa, SM2EngineExtend.CIPHERMODE_BC);
+ System.out.println("解密后="+aaa);
+ }
+
+ /**
+ * SM2加密算法
+ * @param publicKey 公钥
+ * @param data 待加密的数据
+ * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
+ */
+ public static String encrypt(String publicKey, String data){
+ // 按国密排序标准加密
+ return encrypt(publicKey, data, SM2EngineExtend.CIPHERMODE_NORM);
+ }
+
+ /**
+ * SM2加密算法
+ * @param publicKey 公钥
+ * @param data 待加密的数据
+ * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
+ * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
+ */
+ public static String encrypt(String publicKey, String data, int cipherMode){
+ // 获取一条SM2曲线参数
+ X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
+ // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
+ ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
+ //提取公钥点
+ ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
+ // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
+ ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
+
+ SM2EngineExtend sm2Engine = new SM2EngineExtend();
+ // 设置sm2为加密模式
+ sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
+
+ byte[] arrayOfBytes = null;
+ try {
+ byte[] in = data.getBytes();
+ arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
+ } catch (Exception e) {
+ log.error("SM2加密时出现异常:{}", e.getMessage(), e);
+ }
+ return Hex.toHexString(arrayOfBytes);
+ }
+
+ /**
+ * 获取sm2密钥对
+ * BC库使用的公钥=64个字节+1个字节(04标志位),BC库使用的私钥=32个字节
+ * SM2秘钥的组成部分有 私钥D 、公钥X 、 公钥Y , 他们都可以用长度为64的16进制的HEX串表示,
+ *
SM2公钥并不是直接由X+Y表示 , 而是额外添加了一个头,当启用压缩时:公钥=有头+公钥X ,即省略了公钥Y的部分
+ * @param compressed 是否压缩公钥(加密解密都使用BC库才能使用压缩)
+ * @return
+ */
+ public static SM2KeyPair getSm2Keys(boolean compressed){
+ //获取一条SM2曲线参数
+ X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
+ //构造domain参数
+ ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
+ //1.创建密钥生成器
+ ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
+ //2.初始化生成器,带上随机数
+ try {
+ keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
+ } catch (NoSuchAlgorithmException e) {
+ log.error("生成公私钥对时出现异常:", e);
+ }
+ //3.生成密钥对
+ AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
+ ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters)asymmetricCipherKeyPair.getPublic();
+ ECPoint ecPoint = publicKeyParameters.getQ();
+ // 把公钥放入map中,默认压缩公钥
+ // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
+ String publicKey = Hex.toHexString(ecPoint.getEncoded(compressed));
+ ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
+ BigInteger intPrivateKey = privateKeyParameters.getD();
+ // 把私钥放入map中
+ String privateKey = intPrivateKey.toString(16);
+ return new SM2KeyPair(publicKey, privateKey);
+ }
+
+ /**
+ * SM2解密算法
+ * @param privateKey 私钥
+ * @param cipherData 密文数据
+ * @return
+ */
+ public static String decrypt(String privateKey, String cipherData) {
+ // // 按国密排序标准解密
+ return decrypt(privateKey, cipherData, SM2EngineExtend.CIPHERMODE_NORM);
+ }
+
+ /**
+ * SM2解密算法
+ * @param privateKey 私钥
+ * @param cipherData 密文数据
+ * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
+ * @return
+ */
+ public static String decrypt(String privateKey, String cipherData, int cipherMode) {
+ // 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上
+ if (!cipherData.startsWith("04")){
+ cipherData = "04" + cipherData;
+ }
+ byte[] cipherDataByte = Hex.decode(cipherData);
+
+ //获取一条SM2曲线参数
+ X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
+ //构造domain参数
+ ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
+
+ BigInteger privateKeyD = new BigInteger(privateKey, 16);
+ ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
+
+ SM2EngineExtend sm2Engine = new SM2EngineExtend();
+ // 设置sm2为解密模式
+ sm2Engine.init(false, cipherMode, privateKeyParameters);
+
+ String result = "";
+ try {
+ //processBlock得到Base64格式,记得解码
+ byte[] arrayOfBytes = Base64.decodeBase64(sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length));
+ return new String(arrayOfBytes);
+ } catch (Exception e) {
+ log.error("SM2解密时出现异常:{}", e.getMessage(), e);
+ }
+ return result;
+
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class SM2KeyPair{
+ private String pubKey;
+ private String priKey;
+ }
+}
diff --git a/common/src/main/java/com/ruoyi/common/utils/sm/SM2crypto.java b/common/src/main/java/com/ruoyi/common/utils/sm/SM2crypto.java
new file mode 100644
index 0000000..63301d4
--- /dev/null
+++ b/common/src/main/java/com/ruoyi/common/utils/sm/SM2crypto.java
@@ -0,0 +1,9 @@
+package com.ruoyi.common.utils.sm;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class SM2crypto {
+ public static String pubKey = "04ebe49444321237d1d2415427a78b73ab1661e11a511221ac635546f647e44347332857b37f59705c6b95dbd9fcf0188f830946ac156b64c0e30623c3a0500342";
+ public static String priKey = "7a70604a24c35fbdd099a84307bff1196eb9955280cc98cadcd18442ba3e3a83";
+}
diff --git a/common/src/main/java/com/ruoyi/common/utils/sm/SM3Utils.java b/common/src/main/java/com/ruoyi/common/utils/sm/SM3Utils.java
new file mode 100644
index 0000000..8d0ad01
--- /dev/null
+++ b/common/src/main/java/com/ruoyi/common/utils/sm/SM3Utils.java
@@ -0,0 +1,221 @@
+package com.ruoyi.common.utils.sm;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 国密SM3,消息摘要(MD5)
+ */
+@Slf4j
+public class SM3Utils {
+ private static char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+ public static final byte[] IV = {0x73, (byte) 0x80, 0x16, 0x6f, 0x49, 0x14, (byte) 0xb2, (byte) 0xb9, 0x17, 0x24, 0x42,
+ (byte) 0xd7, (byte) 0xda, (byte) 0x8a, 0x06, 0x00, (byte) 0xa9, 0x6f, 0x30, (byte) 0xbc, (byte) 0x16, 0x31,
+ 0x38, (byte) 0xaa, (byte) 0xe3, (byte) 0x8d, (byte) 0xee, 0x4d, (byte) 0xb0, (byte) 0xfb, 0x0e, 0x4e};
+ private static final Integer TJ_15 = Integer.valueOf("79cc4519", 16);
+ private static final Integer TJ_63 = Integer.valueOf("7a879d8a", 16);
+ private static final byte[] FirstPadding = {(byte) 0x80};
+ private static final byte[] ZeroPadding = {(byte) 0x00};
+
+ private static int T(int j) {
+ if (j >= 0 && j <= 15) {
+ return TJ_15.intValue();
+ } else if (j >= 16 && j <= 63) {
+ return TJ_63.intValue();
+ } else {
+ throw new RuntimeException("data invalid");
+ }
+ }
+
+ private static Integer FF(Integer x, Integer y, Integer z, int j) {
+ if (j >= 0 && j <= 15) {
+ return Integer.valueOf(x.intValue() ^ y.intValue() ^ z.intValue());
+ } else if (j >= 16 && j <= 63) {
+ return Integer.valueOf(
+ (x.intValue() & y.intValue()) | (x.intValue() & z.intValue()) | (y.intValue() & z.intValue()));
+ } else {
+ throw new RuntimeException("data invalid");
+ }
+ }
+
+ private static Integer GG(Integer x, Integer y, Integer z, int j) {
+ if (j >= 0 && j <= 15) {
+ return Integer.valueOf(x.intValue() ^ y.intValue() ^ z.intValue());
+ } else if (j >= 16 && j <= 63) {
+ return Integer.valueOf((x.intValue() & y.intValue()) | (~x.intValue() & z.intValue()));
+ } else {
+ throw new RuntimeException("data invalid");
+ }
+ }
+
+ private static Integer P0(Integer x) {
+ return Integer
+ .valueOf(x.intValue() ^ Integer.rotateLeft(x.intValue(), 9) ^ Integer.rotateLeft(x.intValue(), 17));
+ }
+
+ private static Integer P1(Integer x) {
+ return Integer.valueOf(x.intValue() ^ Integer.rotateLeft(x.intValue(), 15) ^ Integer.rotateLeft(x.intValue(), 23));
+ }
+
+ private static byte[] padding(byte[] source) throws IOException {
+ if (source.length >= 0x2000000000000000L) {
+ throw new RuntimeException("src data invalid.");
+ }
+ long l = source.length * 8;
+ long k = 448 - (l + 1) % 512;
+ if (k < 0) {
+ k = k + 512;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("k = " + k);
+ }
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
+ baos.write(source);
+ baos.write(FirstPadding);
+ long i = k - 7;
+ while (i > 0) {
+ baos.write(ZeroPadding);
+ i -= 8;
+ }
+ baos.write(long2bytes(l));
+ if (log.isDebugEnabled()) {
+ log.debug("paded size = " + baos.size());
+ }
+ return baos.toByteArray();
+ }
+ }
+
+ private static byte[] long2bytes(long l) {
+ byte[] bytes = new byte[8];
+ for (int i = 0; i < 8; i++) {
+ bytes[i] = (byte) (l >>> ((7 - i) * 8));
+ }
+ return bytes;
+ }
+
+ public static String encodeSM3(String source) throws IOException {
+ byte[] b = encodeSM3(source.getBytes());
+ return byteToHexString(b);
+ }
+
+ public static byte[] encodeSM3(byte[] source) throws IOException {
+ byte[] m1 = padding(source);
+ int n = m1.length / (512 / 8);
+ if (log.isDebugEnabled()) {
+ log.debug("n = " + n);
+ }
+ byte[] b;
+ byte[] vi = IV.clone();
+ byte[] vi1 = null;
+ for (int i = 0; i < n; i++) {
+ b = Arrays.copyOfRange(m1, i * 64, (i + 1) * 64);
+ vi1 = CF(vi, b);
+ vi = vi1;
+ }
+ return vi1;
+ }
+
+ private static byte[] CF(byte[] vi, byte[] bi) throws IOException {
+ int a, b, c, d, e, f, g, h;
+ a = toInteger(vi, 0);
+ b = toInteger(vi, 1);
+ c = toInteger(vi, 2);
+ d = toInteger(vi, 3);
+ e = toInteger(vi, 4);
+ f = toInteger(vi, 5);
+ g = toInteger(vi, 6);
+ h = toInteger(vi, 7);
+
+ int[] w = new int[68];
+ int[] w1 = new int[64];
+ for (int i = 0; i < 16; i++) {
+ w[i] = toInteger(bi, i);
+ }
+ for (int j = 16; j < 68; j++) {
+ w[j] = P1(w[j - 16] ^ w[j - 9] ^ Integer.rotateLeft(w[j - 3], 15)) ^ Integer.rotateLeft(w[j - 13], 7)
+ ^ w[j - 6];
+ }
+ for (int j = 0; j < 64; j++) {
+ w1[j] = w[j] ^ w[j + 4];
+ }
+ int ss1, ss2, tt1, tt2;
+ for (int j = 0; j < 64; j++) {
+ ss1 = Integer.rotateLeft(Integer.rotateLeft(a, 12) + e + Integer.rotateLeft(T(j), j), 7);
+ ss2 = ss1 ^ Integer.rotateLeft(a, 12);
+ tt1 = FF(a, b, c, j) + d + ss2 + w1[j];
+ tt2 = GG(e, f, g, j) + h + ss1 + w[j];
+ d = c;
+ c = Integer.rotateLeft(b, 9);
+ b = a;
+ a = tt1;
+ h = g;
+ g = Integer.rotateLeft(f, 19);
+ f = e;
+ e = P0(tt2);
+ }
+ byte[] v = toByteArray(a, b, c, d, e, f, g, h);
+ for (int i = 0; i < v.length; i++) {
+ v[i] = (byte) (v[i] ^ vi[i]);
+ }
+ return v;
+ }
+
+ private static int toInteger(byte[] source, int index) {
+ StringBuilder valueStr = new StringBuilder("");
+ for (int i = 0; i < 4; i++) {
+ valueStr.append(chars[(byte) ((source[index * 4 + i] & 0xF0) >> 4)]);
+ valueStr.append(chars[(byte) (source[index * 4 + i] & 0x0F)]);
+ }
+ return Long.valueOf(valueStr.toString(), 16).intValue();
+
+ }
+
+ private static byte[] toByteArray(int a, int b, int c, int d, int e, int f, int g, int h) throws IOException {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream(32);) {
+ baos.write(toByteArray(a));
+ baos.write(toByteArray(b));
+ baos.write(toByteArray(c));
+ baos.write(toByteArray(d));
+ baos.write(toByteArray(e));
+ baos.write(toByteArray(f));
+ baos.write(toByteArray(g));
+ baos.write(toByteArray(h));
+ return baos.toByteArray();
+ }
+ }
+
+ private static byte[] toByteArray(int i) {
+ byte[] byteArray = new byte[4];
+ byteArray[0] = (byte) (i >>> 24);
+ byteArray[1] = (byte) ((i & 0xFFFFFF) >>> 16);
+ byteArray[2] = (byte) ((i & 0xFFFF) >>> 8);
+ byteArray[3] = (byte) (i & 0xFF);
+ return byteArray;
+ }
+
+ private static String byteToHexString(byte[] bytes)
+ {
+ StringBuilder resultHexString = new StringBuilder();
+ String tempStr;
+ for (byte b: bytes) {
+ //这里需要对b与0xff做位与运算,
+ //若b为负数,强制转换将高位位扩展,导致错误,
+ //故需要高位清零
+ tempStr = Integer.toHexString(b & 0xff);
+ //若转换后的十六进制数字只有一位,
+ //则在前补"0"
+ if (tempStr.length() == 1) {
+ resultHexString.append(0).append(tempStr);
+ } else {
+ resultHexString.append(tempStr);
+ }
+ }
+ return resultHexString.toString();
+ }
+
+ private SM3Utils() {
+ }
+}
diff --git a/common/src/main/java/com/ruoyi/common/utils/sm/SM3crypto.java b/common/src/main/java/com/ruoyi/common/utils/sm/SM3crypto.java
new file mode 100644
index 0000000..728a6ac
--- /dev/null
+++ b/common/src/main/java/com/ruoyi/common/utils/sm/SM3crypto.java
@@ -0,0 +1,71 @@
+package com.ruoyi.common.utils.sm;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.bouncycastle.crypto.digests.SM3Digest;
+
+/**
+ * SM3加密
+ */
+public class SM3crypto {
+ public static String getSaltStr() {
+ return Base64.encodeBase64String(getSalt());
+ }
+ public static byte[] getSalt() {
+ /*
+ * 随机生成128位的随机数
+ */
+ SecureRandom random = new SecureRandom();
+ byte bytes1[] = new byte[16];
+ random.nextBytes(bytes1);
+ return bytes1;
+ }
+
+ public static String pwdSaltedHashValueStr(String salt, String passwdString) {
+ return Base64.encodeBase64String(pwdSaltedHashValue(Base64.decodeBase64(salt), passwdString));
+ }
+ public static String pwdSaltedHashValueStr(byte[] salt, String passwdString) {
+ return Base64.encodeBase64String(pwdSaltedHashValue(salt, passwdString));
+ }
+ public static byte[] pwdSaltedHashValue(byte[] bytes1, String passwdString) {
+ // sm3加密密码
+ try {
+ passwdString = SM3Utils.encodeSM3(passwdString);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ /*
+ * 加盐:即随机数和口令组合
+ */
+ byte passwdbyte[] = arraycat(bytes1, passwdString.getBytes());
+ // SM3计算
+ SM3Digest mdDigest = new SM3Digest();
+ mdDigest.update(passwdbyte, 0, passwdbyte.length);
+ byte[] result = new byte[mdDigest.getDigestSize()];
+ mdDigest.doFinal(result, 0);
+ return result;
+ }
+
+ /*
+ * 拼接buf1和buf2数组
+ */
+ public static byte[] arraycat(byte[] buf1, byte[] buf2) {
+ byte[] bufret = null;
+ int len1 = 0;
+ int len2 = 0;
+ if (buf1 != null)
+ len1 = buf1.length;
+ if (buf2 != null)
+ len2 = buf2.length;
+ if (len1 + len2 > 0)
+ bufret = new byte[len1 + len2];
+ if (len1 > 0)
+ System.arraycopy(buf1, 0, bufret, 0, len1);
+ if (len2 > 0)
+ System.arraycopy(buf2, 0, bufret, len1, len2);
+ return bufret;
+ }
+}
diff --git a/common/src/main/java/com/ruoyi/common/utils/sm/SM4Utils.java b/common/src/main/java/com/ruoyi/common/utils/sm/SM4Utils.java
new file mode 100644
index 0000000..2b70e26
--- /dev/null
+++ b/common/src/main/java/com/ruoyi/common/utils/sm/SM4Utils.java
@@ -0,0 +1,178 @@
+package com.ruoyi.common.utils.sm;
+
+import java.security.Key;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.Random;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
+
+public class SM4Utils {
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ public static final String[] POOL = new String[]{"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
+ private static final String ENCODING = "UTF-8";
+ public static final String ALGORITHM_NAME = "SM4";
+
+ // 加密算法/分组加密模式/分组填充方式
+ // PKCS5Padding-以8个字节为一组进行分组加密
+ // 定义分组加密模式使用:PKCS5Padding
+ public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
+
+ public static void main(String[] args) {
+ try {
+ System.out.println("开始测试SM4加密解密====================");
+ String json = "18511921736";
+ System.out.println("加密前:" + json);
+ //自定义的32位16进制秘钥
+ String key = SM4Utils.generateKeyStr();
+ //sm4加密
+ String cipher = SM4Utils.encryptEcb(key, json);
+ System.out.println("加密后:" + cipher);
+ //校验加密前后是否为同一数据
+ System.out.println("校验:" + SM4Utils.verifyEcb(key, cipher, json));
+ //解密
+ json = SM4Utils.decryptEcb(key, cipher);
+ System.out.println("解密后:" + json);
+ System.out.println("结束===================");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 生成字符串
+ * @return 生成的32位长度的16进制字符串
+ */
+ public static String generateKeyStr(){
+ StringBuilder sb = new StringBuilder();
+ Random random = new Random();
+ for (int i = 0; i < 32; i++) {
+ sb.append(POOL[random.nextInt(POOL.length)]);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * sm4加密
+ *
+ * @param hexKey 16进制密钥(忽略大小写)
+ * @param paramStr 待加密字符串
+ * @return 返回16进制的加密字符串
+ * @throws Exception
+ * @explain 加密模式:ECB 密文长度不固定,会随着被加密字符串长度的变化而变化
+ */
+ public static String encryptEcb(String hexKey, String paramStr) throws Exception {
+ String cipherText = "";
+ // 16进制字符串-->byte[]
+ byte[] keyData = ByteUtils.fromHexString(hexKey);
+ // String-->byte[]
+ byte[] srcData = paramStr.getBytes(ENCODING);
+ // 加密后的数组
+ byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
+ // byte[]-->hexString
+ cipherText = ByteUtils.toHexString(cipherArray);
+ return cipherText;
+ }
+
+ /**
+ * 加密模式之Ecb
+ *
+ * @param key
+ * @param data
+ * @return
+ * @throws Exception
+ */
+ public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
+ Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);//声称Ecb暗号,通过第二个参数判断加密还是解密
+ return cipher.doFinal(data);
+ }
+
+ /**
+ * 生成ECB暗号
+ *
+ * @param algorithmName 算法名称
+ * @param mode 模式
+ * @param key
+ * @return
+ * @throws Exception
+ * @explain ECB模式(电子密码本模式:Electronic codebook)
+ */
+ private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
+ Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
+ Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
+ cipher.init(mode, sm4Key);
+ return cipher;
+ }
+
+ //解密****************************************
+
+ /**
+ * sm4解密
+ *
+ * @param hexKey 16进制密钥
+ * @param cipherText 16进制的加密字符串(忽略大小写)
+ * @return 解密后的字符串
+ * @throws Exception
+ * @explain 解密模式:采用ECB
+ */
+ public static String decryptEcb(String hexKey, String cipherText) throws Exception {
+ // 用于接收解密后的字符串
+ String decryptStr = "";
+ // hexString-->byte[]
+ byte[] keyData = ByteUtils.fromHexString(hexKey);
+ // hexString-->byte[]
+ byte[] cipherData = ByteUtils.fromHexString(cipherText);
+ // 解密
+ byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
+ // byte[]-->String
+ decryptStr = new String(srcData, ENCODING);
+ return decryptStr;
+ }
+
+ /**
+ * 解密
+ *
+ * @param key
+ * @param cipherText
+ * @return
+ * @throws Exception
+ * @explain
+ */
+ public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
+ Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);//生成Ecb暗号,通过第二个参数判断加密还是解密
+ return cipher.doFinal(cipherText);
+ }
+
+ /**
+ * 校验加密前后的字符串是否为同一数据
+ *
+ * @param hexKey 16进制密钥(忽略大小写)
+ * @param cipherText 16进制加密后的字符串
+ * @param paramStr 加密前的字符串
+ * @return 是否为同一数据
+ * @throws Exception
+ * @explain
+ */
+ public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception {
+ // 用于接收校验结果
+ boolean flag = false;
+ // hexString-->byte[]
+ byte[] keyData = ByteUtils.fromHexString(hexKey);
+ // 将16进制字符串转换成数组
+ byte[] cipherData = ByteUtils.fromHexString(cipherText);
+ // 解密
+ byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
+ // 将原字符串转换成byte[]
+ byte[] srcData = paramStr.getBytes(ENCODING);
+ // 判断2个数组是否一致
+ flag = Arrays.equals(decryptData, srcData);
+ return flag;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 121158a..5a56252 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,12 +26,13 @@
UTF-8
1.8
3.1.1
- 1.8.0
+ 1.12.0
2.1.0
1.2.16
1.21
2.3.2
3.0.0
+ 2.7.12
2.2.2
1.4.1
1.2.79
@@ -81,10 +82,15 @@
org.springframework.boot
spring-boot-dependencies
- 2.7.12
+ ${springboot.version}
pom
import
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${springboot.version}
+
@@ -236,6 +242,13 @@
hutool-all
5.8.17
+
+
+
+ org.jasig.cas.client
+ cas-client-core
+ 3.6.1
+