From a98659f3a9fbce2f5e4334886a78cb5e58b22ad3 Mon Sep 17 00:00:00 2001 From: Cheney Date: Thu, 14 Nov 2024 15:31:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20BCSdfApiService=20?= =?UTF-8?q?=E4=B8=AD=20sm4=20=E5=8A=A0=E8=A7=A3=E5=AF=86=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sunyard/chsm/enums/AlgMode.java | 36 ++++++ .../java/com/sunyard/chsm/enums/Padding.java | 34 +++++ .../chsm/sdf/AbstractSdfApiService.java | 61 +++++++++ .../com/sunyard/chsm/sdf/BCSdfApiService.java | 120 +++++++++++++++--- .../com/sunyard/chsm/sdf/SdfApiService.java | 42 ++++++ .../com/sunyard/chsm/utils/gm/BCSM4Utils.java | 4 +- chsm-web-server/pom.xml | 15 +++ .../test/java/sdf/BCSdfApiServiceTest.java | 34 +++++ .../src/test/resources/application.yml | 62 +++++++++ .../src/test/resources/logback.xml | 91 +++++++++++++ 10 files changed, 482 insertions(+), 17 deletions(-) create mode 100644 chsm-common/src/main/java/com/sunyard/chsm/enums/AlgMode.java create mode 100644 chsm-common/src/main/java/com/sunyard/chsm/enums/Padding.java create mode 100644 chsm-common/src/main/java/com/sunyard/chsm/sdf/AbstractSdfApiService.java create mode 100644 chsm-web-server/src/test/java/sdf/BCSdfApiServiceTest.java create mode 100644 chsm-web-server/src/test/resources/application.yml create mode 100644 chsm-web-server/src/test/resources/logback.xml diff --git a/chsm-common/src/main/java/com/sunyard/chsm/enums/AlgMode.java b/chsm-common/src/main/java/com/sunyard/chsm/enums/AlgMode.java new file mode 100644 index 0000000..e0bda2e --- /dev/null +++ b/chsm-common/src/main/java/com/sunyard/chsm/enums/AlgMode.java @@ -0,0 +1,36 @@ +package com.sunyard.chsm.enums; + + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Objects; + + +/** + * 算法的轮模式 + * @author Cheney + */ +@Getter +@AllArgsConstructor +public enum AlgMode { + ECB("ECB", "ECB"), + CBC( "CBC", "CBC"), + ; + + private final String code; + private final String desc; + + + public static AlgMode of(String code) { + if (code == null || code.trim().isEmpty()) { + return null; + } + + return Arrays.stream(AlgMode.values()) + .filter(it -> Objects.equals(it.getCode(), code)) + .findFirst() + .orElse(null); + } +} diff --git a/chsm-common/src/main/java/com/sunyard/chsm/enums/Padding.java b/chsm-common/src/main/java/com/sunyard/chsm/enums/Padding.java new file mode 100644 index 0000000..dd058bb --- /dev/null +++ b/chsm-common/src/main/java/com/sunyard/chsm/enums/Padding.java @@ -0,0 +1,34 @@ +package com.sunyard.chsm.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 数据的填充模式 + * @author Cheney + */ +@Getter +@AllArgsConstructor +public enum Padding { + NOPadding("NoPadding", "NoPadding"), + PCKS5Padding( "PKCS5Padding", "PKCS5Padding"), + ; + + private final String code; + private final String desc; + + + public static Padding of(String code) { + if (code == null || code.trim().isEmpty()) { + return null; + } + + return Arrays.stream(Padding.values()) + .filter(it -> Objects.equals(it.getCode(), code)) + .findFirst() + .orElse(null); + } +} diff --git a/chsm-common/src/main/java/com/sunyard/chsm/sdf/AbstractSdfApiService.java b/chsm-common/src/main/java/com/sunyard/chsm/sdf/AbstractSdfApiService.java new file mode 100644 index 0000000..cb4b764 --- /dev/null +++ b/chsm-common/src/main/java/com/sunyard/chsm/sdf/AbstractSdfApiService.java @@ -0,0 +1,61 @@ +package com.sunyard.chsm.sdf; + +import com.sunyard.chsm.enums.AlgMode; +import com.sunyard.chsm.enums.KeyAlg; +import com.sunyard.chsm.enums.Padding; + +import java.util.HashMap; +import java.util.Map; + +import static com.sunyard.chsm.utils.gm.BCSM4Utils.DEFAULT_KEY_SIZE; + +/** + * 实现 SdfApiService 接口中的通用逻辑 + * 处理参数缺失时的默认参数,确保下层入参没有 null + * @author Cheney + */ +public abstract class AbstractSdfApiService implements SdfApiService { + + private static final Map algKeyLen = new HashMap<>(); + static { + algKeyLen.put( KeyAlg.SM4, DEFAULT_KEY_SIZE ); + } + + /** + * 处理算法的默认密钥长度 + * @param alg 算法,只支持对称算法 + * 密钥长度根据 alg 指定的算法自动选择。 + * @return 密钥 + */ + @Override + public byte[] genSymKey(KeyAlg alg) { + return genSymKey( alg, algKeyLen.get( alg ) ); + } + + + /** + * 对称加密 + * 内部实现和解密共用逻辑 + * @param alg 算法,只支持对称算法 + * @param key 密钥值,明文 + * @param data 原始数据 + */ + @Override + public byte[] symEncrypt(KeyAlg alg, byte[] key, byte[] data){ + return symEncrypt( alg, AlgMode.ECB, Padding.PCKS5Padding, key, data ); + } + + + /** + * 对称解密 + * 内部实现和加密共用逻辑 + * @param alg 算法,只支持对称算法 + * @param key 密钥值,明文 + * @param data 密文数据 + */ + @Override + public byte[] symDecrypt(KeyAlg alg, byte[] key, byte[] data) { + return symDecrypt( alg, AlgMode.ECB, Padding.PCKS5Padding, key, data ); + } + +} 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 19cb23e..efa11c2 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 @@ -1,29 +1,50 @@ package com.sunyard.chsm.sdf; +import com.sunyard.chsm.enums.AlgMode; +import com.sunyard.chsm.enums.KeyAlg; +import com.sunyard.chsm.enums.KeyCategory; +import com.sunyard.chsm.enums.Padding; import com.sunyard.chsm.sdf.model.EccKey; import com.sunyard.chsm.sdf.util.LangUtils; import com.sunyard.chsm.utils.gm.BCSM2Utils; import com.sunyard.chsm.utils.gm.BCSM3Utils; +import com.sunyard.chsm.utils.gm.BCSM4Utils; import lombok.SneakyThrows; -import org.bouncycastle.crypto.digests.SM3Digest; -import org.bouncycastle.crypto.macs.HMac; -import org.bouncycastle.crypto.params.KeyParameter; +import lombok.extern.slf4j.Slf4j; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.util.BigIntegers; import org.springframework.stereotype.Service; -import java.security.KeyPair; -import java.security.SecureRandom; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.security.*; + /** - * @author liulu + * 基于 BC 库的国密软算法实现 + * 当前实现类的接口返回的对称密钥和私钥认为是明文,在上层 Service 进行使用和存储时的加密和解密运算。 + *

+ * 主密钥: + * - 生成。 软随机数。项目启动时不存在则自动生成。 + * - 存储。 存储在 SC_PARAM_CONF 表, KEY 为 mk 的字段,值为固定的 48 字节,以 HEX 格式 96 字节存储。 + * 前 16 字节为主密钥值,后 32 字节为主密钥值使用 SM3 计算的校验值。 + * - 同步。基于数据库进行导入、导出、和备份、恢复。多台应用服务器连接同一个数据库,无需同步。 + * - 使用。顶层密钥,用于保护其他存于数据的密钥。 + * + * @author liulu 、Cheney * @since 2024/10/23 */ +@Slf4j @Service -public class BCSdfApiService implements SdfApiService { +public class BCSdfApiService extends AbstractSdfApiService { + public BCSdfApiService() { + super(); + } @Override public byte[] generateRandom(int len) { @@ -32,6 +53,82 @@ public class BCSdfApiService implements SdfApiService { return res; } + + /** + * 生成对称算法密钥 + * + * @param alg 算法,只支持对称算法 + * @param keyLen 密钥长度(bit),只针对密钥长度可变的算法。 + * 禁止传 null + * @return + */ + @Override + public byte[] genSymKey(KeyAlg alg, Integer keyLen) { + switch (alg) { + case SM4: { + if (null == keyLen || keyLen != BCSM4Utils.DEFAULT_KEY_SIZE) { + throw new IllegalArgumentException("Invalid key length: " + keyLen); + } + try { + return BCSM4Utils.generateKey(); + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + throw new RuntimeException("算法实现错误", e); + } + } + default: { + throw new IllegalArgumentException("Unsupported algorithm: " + alg); + } + } + } + + @Override + public byte[] symEncrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) { + return symCalc(Cipher.ENCRYPT_MODE, alg, mode, padding, key, data); + } + + + @Override + public byte[] symDecrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) { + return symCalc(Cipher.DECRYPT_MODE, alg, mode, padding, key, data); + } + + + /** + * 对称加解密的统一实现 + * + * @param alg 算法,只支持对称算法 + * @param key 密钥值,明文 + * @param data 加密时明文数据,解密时为密文数据 + */ + private byte[] symCalc(int cipherMode, KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) { + if (alg.getCategory() != KeyCategory.SYM_KEY) { + throw new IllegalArgumentException("Must SYM_KEY, unsupported algorithm: " + alg); + } + + // 算法 + String algName = null; + if (alg == KeyAlg.SM4) { + algName = "SM4/"; + } else { + throw new IllegalArgumentException("Unsupported algorithm: " + alg); + } + + // 算法轮模式 + algName += mode.getCode() + "/"; + + // 填充模式 + algName += padding.getCode(); + + + Cipher cipher = null; + try { + cipher = BCSM4Utils.generateECBCipher(algName, cipherMode, key); + return cipher.doFinal(data); + } catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException e) { + throw new RuntimeException("算法执行错误", e); + } + } + @SneakyThrows @Override public EccKey genKeyPairEcc() { @@ -53,14 +150,7 @@ public class BCSdfApiService implements SdfApiService { @Override public 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; + return BCSM3Utils.hmac(key, srcData); } @Override 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 e151c4b..a7a4ad1 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 @@ -1,8 +1,12 @@ package com.sunyard.chsm.sdf; +import com.sunyard.chsm.enums.AlgMode; +import com.sunyard.chsm.enums.KeyAlg; +import com.sunyard.chsm.enums.Padding; import com.sunyard.chsm.sdf.model.EccKey; + /** * @author liulu * @since 2024/10/23 @@ -18,6 +22,44 @@ public interface SdfApiService { */ byte[] generateRandom(int len); + + + /** + * 生成对称密钥 + * @param alg 算法,只支持对称算法 + * @param keyLen 密钥长度(bit),只针对密钥长度可变的算法。 + * 禁止传 null + * @return 对称密钥 + */ + byte[] genSymKey(KeyAlg alg, Integer keyLen); + byte[] genSymKey(KeyAlg alg); + + + /** + * 对称加密 + * @param alg 算法,只支持对称算法 + * @param mode 轮模式 + * @param padding 填充模式 + * @param key 密钥值,明文 + * @param data 原始数据 + */ + byte[] symEncrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data); + byte[] symEncrypt(KeyAlg alg, byte[] key, byte[] data); + + /** + * 对称解密 + * @param alg 算法,只支持对称算法 + * @param key 密钥值,明文 + * @param mode 轮模式 + * @param padding 填充模式 + * @param data 密文数据 + */ + byte[] symDecrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data); + byte[] symDecrypt(KeyAlg alg, byte[] key, byte[] data); + + + + /** * 产生ECC密钥对并输出 * 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 index d8663e2..a8d57d2 100644 --- 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 @@ -170,7 +170,7 @@ public class BCSM4Utils extends GMBaseUtil { return mac.doFinal(); } - private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key) + public static Cipher generateECBCipher(String algorithmName, int mode, byte[] key) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException { Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME); @@ -179,7 +179,7 @@ public class BCSM4Utils extends GMBaseUtil { return cipher; } - private static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv) + public 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); diff --git a/chsm-web-server/pom.xml b/chsm-web-server/pom.xml index 92c954c..5df15a7 100644 --- a/chsm-web-server/pom.xml +++ b/chsm-web-server/pom.xml @@ -19,6 +19,21 @@ + + + + org.junit.jupiter + junit-jupiter-api + 5.8.0 + test + + + + org.springframework.boot + spring-boot-starter-test + test + + com.sunyard.chsm chsm-common diff --git a/chsm-web-server/src/test/java/sdf/BCSdfApiServiceTest.java b/chsm-web-server/src/test/java/sdf/BCSdfApiServiceTest.java new file mode 100644 index 0000000..40232e8 --- /dev/null +++ b/chsm-web-server/src/test/java/sdf/BCSdfApiServiceTest.java @@ -0,0 +1,34 @@ +package sdf; +import com.sunyard.chsm.WebServerApp; +import com.sunyard.chsm.enums.KeyAlg; +import com.sunyard.chsm.sdf.BCSdfApiService; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Random; + + +@Slf4j +@SpringBootTest(classes = WebServerApp.class) +public class BCSdfApiServiceTest { + + @Autowired + private BCSdfApiService bcSdfApiService; + + // 对称加密解密测试 + @Test + void testSym() { + byte[] data = new byte[128]; + new Random().nextBytes( data ); + + byte[] key = bcSdfApiService.genSymKey( KeyAlg.SM4 ); + byte[] enData = bcSdfApiService.symEncrypt( KeyAlg.SM4, key, data ); + byte[] deData = bcSdfApiService.symDecrypt( KeyAlg.SM4, key, enData ); + + Assert.assertArrayEquals( data, deData ); + + } +} diff --git a/chsm-web-server/src/test/resources/application.yml b/chsm-web-server/src/test/resources/application.yml new file mode 100644 index 0000000..a1e566e --- /dev/null +++ b/chsm-web-server/src/test/resources/application.yml @@ -0,0 +1,62 @@ +server: + port: 89 + tomcat: + uri-encoding: UTF-8 + threads: + max: 1000 + min-spare: 30 + +spring: + main: + allow-circular-references: true + # 数据源 + datasource: + driverClassName: dm.jdbc.driver.DmDriver + url: jdbc:dm://172.16.17.236:5236?schema=SSP&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8 + username: SUNYARD + # Jasypt加密 可到common-utils中找到JasyptUtil加解密工具类生成加密结果 格式为ENC(加密结果) + password: 123456 + hikari: + minimum-idle: 5 + maximum-pool-size: 100 + idle-timeout: 600000 # 空闲连接的最大等待时间,单位为毫秒 (10 分钟) + max-lifetime: 1800000 # 连接池中连接的最大存活时间,单位为毫秒 (30 分钟) + connection-timeout: 30000 # 获取连接的超时时间,单位为毫秒 (30 秒) + leak-detection-threshold: 2000 # 连接泄漏检测阈值,单位为毫秒 (2 秒) + # 连接测试配置,确保连接有效性 + connection-test-query: SELECT 1 + validation-timeout: 5000 # 验证连接的超时时间,单位为毫秒 (5 秒) + jackson: + time-zone: GMT+8 + date-format: yyyy-MM-dd HH:mm:ss + +mybatis-plus: + mapper-locations: classpath*:mapper/**/*Mapper.xml + # 原生配置 + configuration: + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + map-underscore-to-camel-case: true + cache-enabled: false + lazy-loading-enabled: false + global-config: + # 数据库相关配置 + db-config: + #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID"; + id-type: AUTO + #驼峰下划线转换 + table-underline: true + #是否开启大写命名,默认不开启 + capital-mode: true + #逻辑删除配置 + #logic-delete-value: 1 + #logic-not-delete-value: 0 + +logging: + level: + root: info + com.sunyard.chsm.mapper: debug +# org.springframework.web: trace +# config: classpath:log4j2.xml + + + diff --git a/chsm-web-server/src/test/resources/logback.xml b/chsm-web-server/src/test/resources/logback.xml new file mode 100644 index 0000000..0def50d --- /dev/null +++ b/chsm-web-server/src/test/resources/logback.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + ${LOG_PATTERN} + UTF-8 + + + + ${LOG_HOME}/info.log + + ${LOG_HOME}/%d{yyyy-MM}/INFO-%d{yyyy-MM-dd}_%i.log + + 10MB + + 30 + + 20GB + + + + ${LOG_PATTERN} + UTF-8 + + + INFO + + + + + ${LOG_HOME}/debug.log + + ${LOG_HOME}/%d{yyyy-MM}/sspweb-DEBUG-%d{yyyy-MM-dd}_%i.log + + 10MB + + 30 + + 20GB + + + + ${LOG_PATTERN} + UTF-8 + + + DEBUG + + + + + ${LOG_HOME}/error.log + + ${LOG_HOME}/%d{yyyy-MM}/ERROR-%d{yyyy-MM-dd}_%i.log + + 10MB + + 30 + + 20GB + + + + ${LOG_PATTERN} + UTF-8 + + + ERROR + + + + + + + + + + + + +