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 +