diff --git a/chsm-common/src/main/java/com/sunyard/chsm/constant/CryptoConst.java b/chsm-common/src/main/java/com/sunyard/chsm/constant/CryptoConst.java index 5ec0746..316c105 100644 --- a/chsm-common/src/main/java/com/sunyard/chsm/constant/CryptoConst.java +++ b/chsm-common/src/main/java/com/sunyard/chsm/constant/CryptoConst.java @@ -8,6 +8,8 @@ import com.sunyard.chsm.utils.CodecUtils; */ public interface CryptoConst { + byte[] USER_ID = CodecUtils.decodeHex("31323334353637383132333435363738"); + static byte[] iv() { return CodecUtils.decodeHex("30303030303030303030303030303030"); } 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 0ae4b07..a60b09a 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 @@ -121,7 +121,7 @@ public interface SdfApiService { /** * 计算MAC * - * @param algId algId + * @param algId algId * @param padding padding * @param symKey 用户指定的密钥 * @param pucIv 缓冲区指针,用于存放输入和返回的IV数据 @@ -136,8 +136,12 @@ public interface SdfApiService { * 杂凑运算 * * @param pucData 缓冲区指针,用于存放输入的数据明文 + * @param pubKey 执行预处理的公钥 + * @param userId 执行预处理的userId * @return hash值 */ + byte[] hash(byte[] pucData, byte[] pubKey, byte[] userId); + byte[] hash(byte[] pucData); byte[] encryptByTMK(byte[] data); diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/CodecUtils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/CodecUtils.java index 4a70048..314e761 100644 --- a/chsm-common/src/main/java/com/sunyard/chsm/utils/CodecUtils.java +++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/CodecUtils.java @@ -15,6 +15,9 @@ public abstract class CodecUtils { } public static byte[] decodeBase64(String str) { + if (str == null || str.isEmpty()) { + return null; + } try { return Base64.getDecoder().decode(str); } catch (Exception var3) { @@ -35,6 +38,9 @@ public abstract class CodecUtils { } public static byte[] decodeHex(String str) { + if (str == null || str.isEmpty()) { + return null; + } try { return Hex.decode(str); } catch (Exception var3) { diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymDecryptReq.java b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymDecryptReq.java new file mode 100644 index 0000000..3199b09 --- /dev/null +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymDecryptReq.java @@ -0,0 +1,30 @@ +package com.sunyard.chsm.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * @author Wangjingcheng + * @Version 1.0.0 + * @Date 2023/8/4 9:56 + */ +@Data +public class AsymDecryptReq { + // 密钥ID + @NotNull(message = "密钥ID不能为空") + private Long keyId; + + // 密钥索引 + @NotEmpty(message = "密钥索引不能为空") + @Size(min = 15, max = 24, message = "密钥索引长度在15-24") + private String keyIndex; + + // 密文,使用Base64编码 + @NotBlank(message = "密文不能为空") + private String cipherData; + +} diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymDecryptResp.java b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymDecryptResp.java new file mode 100644 index 0000000..db44ec2 --- /dev/null +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymDecryptResp.java @@ -0,0 +1,13 @@ +package com.sunyard.chsm.param; + +import lombok.Data; + +@Data +public class AsymDecryptResp { + // 密钥ID + private Long keyId; + // 密钥索引 + private String keyIndex; + // 明文,使用Base64编码 + private String plainData; +} diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEncryptReq.java b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEncryptReq.java new file mode 100644 index 0000000..824b571 --- /dev/null +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEncryptReq.java @@ -0,0 +1,18 @@ +package com.sunyard.chsm.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +public class AsymEncryptReq { + + // 密钥ID + @NotNull(message = "密钥ID不能为空") + private Long keyId; + + // 明文,使用Base64编码 + @NotBlank(message = "明文不能为空") + private String plainData; +} diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEncryptResp.java b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEncryptResp.java new file mode 100644 index 0000000..728e365 --- /dev/null +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEncryptResp.java @@ -0,0 +1,16 @@ +package com.sunyard.chsm.param; + +import lombok.Data; + +@Data +public class AsymEncryptResp { + + // 密钥ID + private Long keyId; + + // 密钥索引 + private String keyIndex; + + // 密文C1C3C2,使用Base64编码 + private String cipherData; +} diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymSignRawReq.java b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymSignRawReq.java new file mode 100644 index 0000000..691e898 --- /dev/null +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymSignRawReq.java @@ -0,0 +1,24 @@ +package com.sunyard.chsm.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +@Data +public class AsymSignRawReq { + + // 密钥ID + @NotNull(message = "密钥ID不能为空") + private Long keyId; + + // 明文,使用Base64编码 + @NotBlank(message = "明文不能为空") + private String plainData; + + // 是否进行预处理 默认是 + private boolean preProcess = true; + + // 预处理的userId,使用Base64编码 + private String userId; +} diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymSignRawResp.java b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymSignRawResp.java new file mode 100644 index 0000000..d4a5f77 --- /dev/null +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymSignRawResp.java @@ -0,0 +1,16 @@ +package com.sunyard.chsm.param; + +import lombok.Data; + +@Data +public class AsymSignRawResp { + + // 密钥ID + private Long keyId; + + // 密钥索引 + private String keyIndex; + + // 签名值 Base64编码 + private String signData; +} diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymVerifyRawReq.java b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymVerifyRawReq.java new file mode 100644 index 0000000..92df7f5 --- /dev/null +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymVerifyRawReq.java @@ -0,0 +1,35 @@ +package com.sunyard.chsm.param; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +@Data +public class AsymVerifyRawReq { + + // 密钥ID + @NotNull(message = "密钥ID不能为空") + private Long keyId; + + // 密钥索引 + @NotEmpty(message = "密钥索引不能为空") + @Size(min = 15, max = 24, message = "密钥索引长度在15-24") + private String keyIndex; + + // 签名值,使用Base64编码 + @NotBlank(message = "密文不能为空") + private String signData; + + // 明文,使用Base64编码 + @NotBlank(message = "明文不能为空") + private String plainData; + + // 是否进行预处理 默认是 + private boolean preProcess = true; + + // 预处理的userId,使用Base64编码 + private String userId; +} diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/HashReq.java b/chsm-params/src/main/java/com/sunyard/chsm/param/HashReq.java index 633ac50..13058ab 100644 --- a/chsm-params/src/main/java/com/sunyard/chsm/param/HashReq.java +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/HashReq.java @@ -12,8 +12,13 @@ public class HashReq { @NotBlank(message = "明文不能为空") private String plainData; - // 填充方式, 默认PCKS7 + // Hash算法, 默认SM3 private HashAlg alg = HashAlg.SM3; + // 公钥,使用Base64编码 + private String pubKey; + // userId,使用Base64编码 + private String userId; + } diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/SymHmacCheckResp.java b/chsm-params/src/main/java/com/sunyard/chsm/param/VerifyResp.java similarity index 52% rename from chsm-params/src/main/java/com/sunyard/chsm/param/SymHmacCheckResp.java rename to chsm-params/src/main/java/com/sunyard/chsm/param/VerifyResp.java index 37b89b0..2c3ddfe 100644 --- a/chsm-params/src/main/java/com/sunyard/chsm/param/SymHmacCheckResp.java +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/VerifyResp.java @@ -3,8 +3,8 @@ package com.sunyard.chsm.param; import lombok.Data; @Data -public class SymHmacCheckResp { +public class VerifyResp { - private Boolean valid; + private Boolean verified; } diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/sdf/SingleSdfApiService.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/sdf/SingleSdfApiService.java index 3c2948f..bc9aff6 100644 --- a/chsm-web-manage/src/main/java/com/sunyard/chsm/sdf/SingleSdfApiService.java +++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/sdf/SingleSdfApiService.java @@ -90,7 +90,10 @@ public class SingleSdfApiService implements SdfApiService, InitializingBean { @Override public EccSignature externalSignECC(byte[] privateKey, byte[] pucData) { - return externalSignWithIdECC(privateKey, pucData, new byte[0]); + Assert.notNull(pucData, "待签名数据不能为空"); + Assert.isTrue(pucData.length == 32, "待签名数据长度必须为32字节"); + EccPriKey priKey = EccPriKey.fromBytes(privateKey); + return sdfApiAdapter.externalSignECC(sessionHandle, priKey, pucData); } @Override @@ -123,7 +126,11 @@ public class SingleSdfApiService implements SdfApiService, InitializingBean { @Override public boolean externalVerifyECC(byte[] publicKey, byte[] pubData, byte[] signData) { - return externalVerifyWithIdECC(publicKey, pubData, signData, new byte[0]); + Assert.notNull(pubData, "验签原文数据不能为空"); + Assert.isTrue(pubData.length == 32, "验签原文数据长度必须为32字节"); + EccPubKey eccPublicKey = EccPubKey.fromBytes(publicKey); + EccSignature eccSignature = EccSignature.fromBytes(signData); + return sdfApiAdapter.externalVerifyECC(sessionHandle, eccPublicKey, pubData, eccSignature); } @Override @@ -154,6 +161,19 @@ public class SingleSdfApiService implements SdfApiService, InitializingBean { return BCSM3Utils.hmac(key, srcData); } + @Override + public byte[] hash(byte[] pucData, byte[] pubKey, byte[] userId) { + checkStatus(); + EccPubKey eccPubKey = pubKey == null || pubKey.length == 0 ? null : EccPubKey.fromBytes(pubKey); + byte[] finalUserId = Objects.nonNull(pubKey) && Objects.isNull(userId) ? CryptoConst.USER_ID : userId; + String newSession = sdfApiAdapter.openSession(deviceHandle); + sdfApiAdapter.hashInit(newSession, AlgId.SGD_SM3, eccPubKey, finalUserId); + sdfApiAdapter.hashUpdate(newSession, pucData); + byte[] hash = sdfApiAdapter.hashFinish(newSession); + sdfApiAdapter.closeSession(newSession); + return hash; + } + @Override public byte[] hash(byte[] pucData) { checkStatus(); diff --git a/chsm-web-manage/src/test/java/sdf/SdfApiServiceTest.java b/chsm-web-manage/src/test/java/sdf/SdfApiServiceTest.java index 87c8537..977c425 100644 --- a/chsm-web-manage/src/test/java/sdf/SdfApiServiceTest.java +++ b/chsm-web-manage/src/test/java/sdf/SdfApiServiceTest.java @@ -130,11 +130,11 @@ public class SdfApiServiceTest { @Test public void testSm3() { - byte[] sdfHash = sdfService.hash(plain.getBytes()); + byte[] sdfHash = sdfService.hash(plain.getBytes(), null, null); log.info("sdf hash: {}", CodecUtils.encodeHex(sdfHash)); Assertions.assertEquals(32, sdfHash.length); - byte[] bcHash = bcService.hash(plain.getBytes()); + byte[] bcHash = bcService.hash(plain.getBytes(),null, null); log.info("bc hash: {}", CodecUtils.encodeHex(bcHash)); Assertions.assertEquals(32, bcHash.length); diff --git a/chsm-web-server/src/main/java/com/sunyard/chsm/controller/AsymKeyController.java b/chsm-web-server/src/main/java/com/sunyard/chsm/controller/AsymKeyController.java new file mode 100644 index 0000000..da995b1 --- /dev/null +++ b/chsm-web-server/src/main/java/com/sunyard/chsm/controller/AsymKeyController.java @@ -0,0 +1,89 @@ +package com.sunyard.chsm.controller; + +import com.sunyard.chsm.auth.AuthCode; +import com.sunyard.chsm.constant.AuthCodeConst; +import com.sunyard.chsm.model.R; +import com.sunyard.chsm.param.AsymDecryptReq; +import com.sunyard.chsm.param.AsymDecryptResp; +import com.sunyard.chsm.param.AsymEncryptReq; +import com.sunyard.chsm.param.AsymEncryptResp; +import com.sunyard.chsm.param.AsymSignRawReq; +import com.sunyard.chsm.param.AsymSignRawResp; +import com.sunyard.chsm.param.AsymVerifyRawReq; +import com.sunyard.chsm.param.VerifyResp; +import com.sunyard.chsm.service.AsymKeyService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; + +/** + * 非对称运算类接口 + * + * @author liulu + * @since 2024/12/20 + */ +@RestController +@RequestMapping("/asym") +public class AsymKeyController { + + @Autowired + private AsymKeyService asymKeyService; + + /** + * 非对称加密 + * + * @param req + * @return + */ + @PostMapping("/encrypt") + @AuthCode(AuthCodeConst.asym_enc) + public R encrypt(@Valid @RequestBody AsymEncryptReq req) { + AsymEncryptResp resp = asymKeyService.encrypt(req); + return R.data(resp); + } + + /** + * 非对称解密 + * + * @param req + * @return + */ + @PostMapping("/encrypt") + @AuthCode(AuthCodeConst.asym_enc) + public R decrypt(@Valid @RequestBody AsymDecryptReq req) { + AsymDecryptResp resp = asymKeyService.decrypt(req); + return R.data(resp); + } + + /** + * RAW签名 + * + * @param req + * @return + */ + @PostMapping("/sign/raw") + @AuthCode(AuthCodeConst.sign_raw) + public R signRaw(@Valid @RequestBody AsymSignRawReq req) { + AsymSignRawResp resp = asymKeyService.signRaw(req); + return R.data(resp); + } + + /** + * RAW验签 + * + * @param req + * @return + */ + @PostMapping("/verify/raw") + @AuthCode(AuthCodeConst.verify_raw) + public R verifyRaw(@Valid @RequestBody AsymVerifyRawReq req) { + VerifyResp resp = asymKeyService.verifyRaw(req); + return R.data(resp); + } + + +} diff --git a/chsm-web-server/src/main/java/com/sunyard/chsm/controller/HashController.java b/chsm-web-server/src/main/java/com/sunyard/chsm/controller/HashController.java index c083e65..b3f313d 100644 --- a/chsm-web-server/src/main/java/com/sunyard/chsm/controller/HashController.java +++ b/chsm-web-server/src/main/java/com/sunyard/chsm/controller/HashController.java @@ -36,7 +36,9 @@ public class HashController { @AuthCode(AuthCodeConst.cal_hash) public R hash(@Valid @RequestBody HashReq req) { byte[] bytes = CodecUtils.decodeBase64(req.getPlainData()); - byte[] hash = sdfApiService.hash(bytes); + byte[] pubkey = CodecUtils.decodeBase64(req.getPubKey()); + byte[] userId = CodecUtils.decodeBase64(req.getUserId()); + byte[] hash = sdfApiService.hash(bytes, pubkey, userId); HashResp resp = new HashResp(); resp.setHash(CodecUtils.encodeBase64(hash)); return R.data(resp); diff --git a/chsm-web-server/src/main/java/com/sunyard/chsm/controller/SymKeyController.java b/chsm-web-server/src/main/java/com/sunyard/chsm/controller/SymKeyController.java index 555755f..4db57a4 100644 --- a/chsm-web-server/src/main/java/com/sunyard/chsm/controller/SymKeyController.java +++ b/chsm-web-server/src/main/java/com/sunyard/chsm/controller/SymKeyController.java @@ -8,13 +8,12 @@ import com.sunyard.chsm.param.SymDecryptResp; import com.sunyard.chsm.param.SymEncryptReq; import com.sunyard.chsm.param.SymEncryptResp; import com.sunyard.chsm.param.SymHmacCheckReq; -import com.sunyard.chsm.param.SymHmacCheckResp; import com.sunyard.chsm.param.SymHmacReq; import com.sunyard.chsm.param.SymHmacResp; import com.sunyard.chsm.param.SymMacCheckReq; -import com.sunyard.chsm.param.SymMacCheckResp; import com.sunyard.chsm.param.SymMacReq; import com.sunyard.chsm.param.SymMacResp; +import com.sunyard.chsm.param.VerifyResp; import com.sunyard.chsm.service.SymKeyService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; @@ -84,8 +83,8 @@ public class SymKeyController { */ @PostMapping("/hmac/check") @AuthCode(AuthCodeConst.check_hmac) - public R macCheck(@Valid @RequestBody SymHmacCheckReq req) { - SymHmacCheckResp resp = symKeyService.hmacCheck(req); + public R macCheck(@Valid @RequestBody SymHmacCheckReq req) { + VerifyResp resp = symKeyService.hmacCheck(req); return R.data(resp); } @@ -110,8 +109,8 @@ public class SymKeyController { */ @PostMapping("/mac/check") @AuthCode(AuthCodeConst.check_mac) - public R macCheck(@Valid @RequestBody SymMacCheckReq req) { - SymMacCheckResp resp = symKeyService.macCheck(req); + public R macCheck(@Valid @RequestBody SymMacCheckReq req) { + VerifyResp resp = symKeyService.macCheck(req); return R.data(resp); } diff --git a/chsm-web-server/src/main/java/com/sunyard/chsm/pool/LoadBalancedSdfApiService.java b/chsm-web-server/src/main/java/com/sunyard/chsm/pool/LoadBalancedSdfApiService.java index 259841e..10e51dc 100644 --- a/chsm-web-server/src/main/java/com/sunyard/chsm/pool/LoadBalancedSdfApiService.java +++ b/chsm-web-server/src/main/java/com/sunyard/chsm/pool/LoadBalancedSdfApiService.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.Assert; import javax.annotation.Resource; +import java.util.Objects; import java.util.function.Function; /** @@ -101,7 +102,10 @@ public class LoadBalancedSdfApiService implements SdfApiService { @Override public EccSignature externalSignECC(byte[] privateKey, byte[] pucData) { - return externalSignWithIdECC(privateKey, pucData, new byte[0]); + Assert.notNull(pucData, "待签名数据不能为空"); + Assert.isTrue(pucData.length == 32, "待签名数据长度必须为32字节"); + EccPriKey priKey = EccPriKey.fromBytes(privateKey); + return apply(s -> s.getSdfApiAdapter().externalSignECC(s.getSessionHandle(), priKey, pucData)); } @Override @@ -136,7 +140,11 @@ public class LoadBalancedSdfApiService implements SdfApiService { @Override public boolean externalVerifyECC(byte[] publicKey, byte[] pubData, byte[] signData) { - return externalVerifyWithIdECC(publicKey, pubData, signData, new byte[0]); + Assert.notNull(pubData, "验签原文数据不能为空"); + Assert.isTrue(pubData.length == 32, "验签原文数据长度必须为32字节"); + EccPubKey eccPublicKey = EccPubKey.fromBytes(publicKey); + EccSignature eccSignature = EccSignature.fromBytes(signData); + return apply(s -> s.getSdfApiAdapter().externalVerifyECC(s.getSessionHandle(), eccPublicKey, pubData, eccSignature)); } @Override @@ -169,10 +177,12 @@ public class LoadBalancedSdfApiService implements SdfApiService { } @Override - public byte[] hash(byte[] pucData) { + public byte[] hash(byte[] pucData, byte[] pubKey, byte[] userId) { + EccPubKey eccPubKey = pubKey == null || pubKey.length == 0 ? null : EccPubKey.fromBytes(pubKey); + byte[] finalUserId = Objects.nonNull(pubKey) && Objects.isNull(userId) ? CryptoConst.USER_ID : userId; return apply(s -> { String newSession = s.getSdfApiAdapter().openSession(s.getDeviceHandle()); - s.getSdfApiAdapter().hashInit(newSession, AlgId.SGD_SM3, null, null); + s.getSdfApiAdapter().hashInit(newSession, AlgId.SGD_SM3, eccPubKey, finalUserId); s.getSdfApiAdapter().hashUpdate(newSession, pucData); byte[] hash = s.getSdfApiAdapter().hashFinish(newSession); s.getSdfApiAdapter().closeSession(newSession); @@ -180,6 +190,11 @@ public class LoadBalancedSdfApiService implements SdfApiService { }); } + @Override + public byte[] hash(byte[] pucData) { + return hash(pucData, null, null); + } + @Override public byte[] encryptByTMK(byte[] data) { byte[] pad = PaddingUtil.PKCS7Padding(data); diff --git a/chsm-web-server/src/main/java/com/sunyard/chsm/service/AsymKeyService.java b/chsm-web-server/src/main/java/com/sunyard/chsm/service/AsymKeyService.java new file mode 100644 index 0000000..5875ff2 --- /dev/null +++ b/chsm-web-server/src/main/java/com/sunyard/chsm/service/AsymKeyService.java @@ -0,0 +1,167 @@ +package com.sunyard.chsm.service; + +import com.sunyard.chsm.auth.UserContext; +import com.sunyard.chsm.enums.KeyAlg; +import com.sunyard.chsm.enums.KeyCategory; +import com.sunyard.chsm.enums.KeyStatus; +import com.sunyard.chsm.enums.KeyUsage; +import com.sunyard.chsm.mapper.KeyInfoMapper; +import com.sunyard.chsm.mapper.SpKeyRecordMapper; +import com.sunyard.chsm.model.entity.KeyInfo; +import com.sunyard.chsm.model.entity.KeyRecord; +import com.sunyard.chsm.param.AsymDecryptReq; +import com.sunyard.chsm.param.AsymDecryptResp; +import com.sunyard.chsm.param.AsymEncryptReq; +import com.sunyard.chsm.param.AsymEncryptResp; +import com.sunyard.chsm.param.AsymSignRawReq; +import com.sunyard.chsm.param.AsymSignRawResp; +import com.sunyard.chsm.param.AsymVerifyRawReq; +import com.sunyard.chsm.param.VerifyResp; +import com.sunyard.chsm.sdf.SdfApiService; +import com.sunyard.chsm.sdf.model.EccCipher; +import com.sunyard.chsm.sdf.model.EccSignature; +import com.sunyard.chsm.utils.CodecUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.time.LocalDateTime; +import java.util.Objects; + +/** + * @author liulu + * @since 2024/12/20 + */ +@Service +@RequiredArgsConstructor +public class AsymKeyService { + + private final KeyInfoMapper keyInfoMapper; + private final SpKeyRecordMapper spKeyRecordMapper; + private final SdfApiService sdfApiService; + + + public AsymEncryptResp encrypt(AsymEncryptReq req) { + byte[] plainData = CodecUtils.decodeBase64(req.getPlainData()); + KeyInfo keyInfo = checkKey(req.getKeyId(), KeyUsage.ENCRYPT_DECRYPT); + KeyAlg keyAlg = KeyAlg.of(keyInfo.getKeyAlg()); + Assert.notNull(keyAlg, "数据异常"); + KeyRecord keyRecord = spKeyRecordMapper.selectUsedByKeyId(keyInfo.getId()); + Assert.notNull(keyRecord, "数据异常"); + byte[] cipherData; + switch (keyAlg) { + case SM2: + byte[] pub = CodecUtils.decodeHex(keyRecord.getPubKey()); + EccCipher eccCipher = sdfApiService.externalEncryptECC(pub, plainData); + cipherData = eccCipher.getC1C3C2Bytes(); + break; + default: + throw new UnsupportedOperationException("不支持的密钥算法:" + keyAlg.getCode()); + } + + AsymEncryptResp resp = new AsymEncryptResp(); + resp.setKeyId(keyInfo.getId()); + resp.setKeyIndex(keyRecord.getKeyIndex()); + resp.setCipherData(CodecUtils.encodeBase64(cipherData)); + return resp; + } + + public AsymDecryptResp decrypt(AsymDecryptReq req) { + byte[] cipherData = CodecUtils.decodeBase64(req.getCipherData()); + KeyInfo keyInfo = checkKey(req.getKeyId(), KeyUsage.ENCRYPT_DECRYPT); + KeyAlg keyAlg = KeyAlg.of(keyInfo.getKeyAlg()); + Assert.notNull(keyAlg, "数据异常"); + KeyRecord keyRecord = spKeyRecordMapper.selectById(Long.valueOf(req.getKeyIndex())); + Assert.notNull(keyRecord, "数据异常"); + Assert.isTrue(Objects.equals(keyRecord.getKeyId(), keyInfo.getId()), "密钥Id和密钥索引不匹配"); + byte[] plainData; + switch (keyAlg) { + case SM2: + byte[] encPri = CodecUtils.decodeHex(keyRecord.getKeyData()); + byte[] pri = sdfApiService.decryptByTMK(encPri); + plainData = sdfApiService.externalDecryptECC(pri, cipherData); + break; + default: + throw new UnsupportedOperationException("不支持的密钥算法:" + keyAlg.getCode()); + } + AsymDecryptResp resp = new AsymDecryptResp(); + resp.setKeyId(keyInfo.getId()); + resp.setKeyIndex(keyRecord.getKeyIndex()); + resp.setPlainData(CodecUtils.encodeBase64(plainData)); + return resp; + } + + public AsymSignRawResp signRaw(AsymSignRawReq req) { + byte[] plainData = CodecUtils.decodeBase64(req.getPlainData()); + byte[] userId = CodecUtils.decodeBase64(req.getUserId()); + KeyInfo keyInfo = checkKey(req.getKeyId(), KeyUsage.SIGN_VERIFY); + KeyAlg keyAlg = KeyAlg.of(keyInfo.getKeyAlg()); + Assert.notNull(keyAlg, "数据异常"); + KeyRecord keyRecord = spKeyRecordMapper.selectUsedByKeyId(keyInfo.getId()); + Assert.notNull(keyRecord, "数据异常"); + byte[] signData; + switch (keyAlg) { + case SM2: + byte[] encPri = CodecUtils.decodeHex(keyRecord.getKeyData()); + byte[] pri = sdfApiService.decryptByTMK(encPri); + EccSignature signature = req.isPreProcess() + ? sdfApiService.externalSignWithIdECC(pri, plainData, userId) + : sdfApiService.externalSignECC(pri, plainData); + signData = signature.getRawSignBytes(); + break; + default: + throw new UnsupportedOperationException("不支持的密钥算法:" + keyAlg.getCode()); + } + + AsymSignRawResp resp = new AsymSignRawResp(); + resp.setKeyId(keyInfo.getId()); + resp.setKeyIndex(keyRecord.getKeyIndex()); + resp.setSignData(CodecUtils.encodeBase64(signData)); + return resp; + } + + + public VerifyResp verifyRaw(AsymVerifyRawReq req) { + byte[] plainData = CodecUtils.decodeBase64(req.getPlainData()); + byte[] signData = CodecUtils.decodeBase64(req.getSignData()); + byte[] userId = CodecUtils.decodeBase64(req.getUserId()); + KeyInfo keyInfo = checkKey(req.getKeyId(), KeyUsage.SIGN_VERIFY); + KeyAlg keyAlg = KeyAlg.of(keyInfo.getKeyAlg()); + Assert.notNull(keyAlg, "数据异常"); + KeyRecord keyRecord = spKeyRecordMapper.selectById(Long.valueOf(req.getKeyIndex())); + Assert.notNull(keyRecord, "数据异常"); + Assert.isTrue(Objects.equals(keyRecord.getKeyId(), keyInfo.getId()), "密钥Id和密钥索引不匹配"); + boolean verified; + switch (keyAlg) { + case SM2: + byte[] pub = CodecUtils.decodeHex(keyRecord.getPubKey()); + verified = req.isPreProcess() + ? sdfApiService.externalVerifyWithIdECC(pub, plainData, signData, userId) + : sdfApiService.externalVerifyECC(pub, plainData, signData); + break; + default: + throw new UnsupportedOperationException("不支持的密钥算法:" + keyAlg.getCode()); + } + VerifyResp resp = new VerifyResp(); + resp.setVerified(verified); + return resp; + } + + + private KeyInfo checkKey(Long keyId, KeyUsage usage) { + + KeyInfo keyInfo = keyInfoMapper.selectById(keyId); + Assert.notNull(keyInfo, "密钥ID不存在"); + Assert.isTrue(Objects.equals(keyInfo.getApplicationId(), UserContext.getCurrentAppId()), "您无权使用此密钥ID"); + Assert.isTrue(KeyCategory.ASYM_KEY.getCode().equals(keyInfo.getKeyType()), "此密钥不是对称密钥"); + + KeyStatus status = KeyStatus.of(keyInfo.getStatus()); + LocalDateTime now = LocalDateTime.now(); + Assert.isTrue(KeyStatus.ENABLED == status, "此密钥不是启用状态, 无法操作"); + Assert.isTrue(now.isAfter(keyInfo.getEffectiveTime()) && now.isBefore(keyInfo.getExpiredTime()), "此密钥不是启用状态, 无法操作"); + Assert.isTrue(KeyUsage.hasUsage(keyInfo.getKeyUsage(), usage), "此密钥无权进行" + usage.getDesc() + "操作"); + + return keyInfo; + } + +} diff --git a/chsm-web-server/src/main/java/com/sunyard/chsm/service/SymKeyService.java b/chsm-web-server/src/main/java/com/sunyard/chsm/service/SymKeyService.java index 9cc20f3..1c858d3 100644 --- a/chsm-web-server/src/main/java/com/sunyard/chsm/service/SymKeyService.java +++ b/chsm-web-server/src/main/java/com/sunyard/chsm/service/SymKeyService.java @@ -16,13 +16,12 @@ import com.sunyard.chsm.param.SymDecryptResp; import com.sunyard.chsm.param.SymEncryptReq; import com.sunyard.chsm.param.SymEncryptResp; import com.sunyard.chsm.param.SymHmacCheckReq; -import com.sunyard.chsm.param.SymHmacCheckResp; import com.sunyard.chsm.param.SymHmacReq; import com.sunyard.chsm.param.SymHmacResp; import com.sunyard.chsm.param.SymMacCheckReq; -import com.sunyard.chsm.param.SymMacCheckResp; import com.sunyard.chsm.param.SymMacReq; import com.sunyard.chsm.param.SymMacResp; +import com.sunyard.chsm.param.VerifyResp; import com.sunyard.chsm.sdf.SdfApiService; import com.sunyard.chsm.sdf.context.AlgId; import com.sunyard.chsm.utils.CodecUtils; @@ -149,7 +148,7 @@ public class SymKeyService { return resp; } - public SymHmacCheckResp hmacCheck(SymHmacCheckReq req) { + public VerifyResp hmacCheck(SymHmacCheckReq req) { byte[] plain = CodecUtils.decodeBase64(req.getPlainData()); byte[] originHmac = CodecUtils.decodeBase64(req.getHmac()); @@ -161,8 +160,8 @@ public class SymKeyService { byte[] symKey = sdfApiService.decryptByTMK(CodecUtils.decodeHex(keyRecord.getKeyData())); byte[] hmac = sdfApiService.hmac(symKey, plain); - SymHmacCheckResp resp = new SymHmacCheckResp(); - resp.setValid(Arrays.equals(hmac, originHmac)); + VerifyResp resp = new VerifyResp(); + resp.setVerified(Arrays.equals(hmac, originHmac)); return resp; } @@ -188,7 +187,7 @@ public class SymKeyService { } - public SymMacCheckResp macCheck(SymMacCheckReq req) { + public VerifyResp macCheck(SymMacCheckReq req) { byte[] plain = CodecUtils.decodeBase64(req.getPlainData()); byte[] iv = CodecUtils.decodeBase64(req.getIv()); @@ -204,8 +203,8 @@ public class SymKeyService { byte[] symKey = sdfApiService.decryptByTMK(CodecUtils.decodeHex(keyRecord.getKeyData())); byte[] mac = sdfApiService.calculateMAC(AlgId.SGD_SM4_MAC, req.getPadding(), symKey, iv, plain); - SymMacCheckResp resp = new SymMacCheckResp(); - resp.setValid(Arrays.equals(mac, originMac)); + VerifyResp resp = new VerifyResp(); + resp.setVerified(Arrays.equals(mac, originMac)); return resp; } diff --git a/chsm-web-server/src/test/java/sdf/SdfApiAdapterTest.java b/chsm-web-server/src/test/java/sdf/SdfApiAdapterTest.java index 8c9e825..6af4ffb 100644 --- a/chsm-web-server/src/test/java/sdf/SdfApiAdapterTest.java +++ b/chsm-web-server/src/test/java/sdf/SdfApiAdapterTest.java @@ -11,7 +11,11 @@ import com.sunyard.chsm.sdf.model.EccPriKey; import com.sunyard.chsm.sdf.model.EccPubKey; import com.sunyard.chsm.sdf.model.EccSignature; import com.sunyard.chsm.utils.CodecUtils; +import com.sunyard.chsm.utils.gm.BCECUtils; +import com.sunyard.chsm.utils.gm.SM2PreprocessSigner; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.params.ParametersWithID; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -147,6 +151,30 @@ class SdfApiAdapterTest { Assertions.assertArrayEquals(hash, bcHash); } + @Test + public void testHashWithId() { + byte[] userId = "87123824awdqweqw".getBytes(); + + String newSession = sdfAdapter.openSession(hd); + EccKey eccKey = sdfAdapter.generateKeyPairECC(newSession, AlgId.SGD_SM2_1); + EccPubKey prePubKey = EccPubKey.fromBytes(eccKey.getPubKey()); + sdfAdapter.hashInit(newSession, AlgId.SGD_SM3, prePubKey, userId); + sdfAdapter.hashUpdate(newSession, plain.getBytes()); + byte[] hash = sdfAdapter.hashFinish(newSession); + log.info("sdf pubkey pre hash: {}", CodecUtils.encodeHex(hash)); + sdfAdapter.closeSession(newSession); + + + CipherParameters parameters = BCECUtils.createECPublicKeyParameters(prePubKey.getX(), prePubKey.getY()); + parameters = new ParametersWithID(parameters, userId); + SM2PreprocessSigner signer = new SM2PreprocessSigner(); + signer.init(false, parameters); + byte[] bcPrehash = signer.preprocess( plain.getBytes()); + log.info("bc preHash: {}", CodecUtils.encodeHex(bcPrehash)); + + Assertions.assertArrayEquals(hash, bcPrehash); + } + @Test public void testSm4Mac() { String hk = sdfAdapter.importKey(hs, symKey); diff --git a/chsm-web-server/src/test/java/sdf/SdfApiServiceTest.java b/chsm-web-server/src/test/java/sdf/SdfApiServiceTest.java index 2965a8e..6675c13 100644 --- a/chsm-web-server/src/test/java/sdf/SdfApiServiceTest.java +++ b/chsm-web-server/src/test/java/sdf/SdfApiServiceTest.java @@ -38,7 +38,7 @@ import java.util.UUID; public class SdfApiServiceTest { private final static byte[] symKey = CodecUtils.decodeHex("a00f10444a727d09b94e2112cd662ea4"); - private final static String plain = "hjsu234127qikqwndqqw13412as324"; + private final static String plain = "hjsu234127qikqwndqqw13412as324qw"; // private final static String ip1 = "172.16.18.41"; // private final static int port = 8889; private static final SdfApiService bcService = new SingleSdfApiService(SdfApiAdapterFactory.getBcAdapter()); @@ -164,11 +164,11 @@ public class SdfApiServiceTest { @Test public void testSm3() { - byte[] sdfHash = sdfService.hash(plain.getBytes()); + byte[] sdfHash = sdfService.hash(plain.getBytes(), null, null); log.info("sdf hash: {}", CodecUtils.encodeHex(sdfHash)); Assertions.assertEquals(32, sdfHash.length); - byte[] bcHash = bcService.hash(plain.getBytes()); + byte[] bcHash = bcService.hash(plain.getBytes(), null, null); log.info("bc hash: {}", CodecUtils.encodeHex(bcHash)); Assertions.assertEquals(32, bcHash.length); diff --git a/chsm-web-server/src/test/java/sdf/adapter/SingleSdfApiService.java b/chsm-web-server/src/test/java/sdf/adapter/SingleSdfApiService.java index 8f03ec8..fa8a992 100644 --- a/chsm-web-server/src/test/java/sdf/adapter/SingleSdfApiService.java +++ b/chsm-web-server/src/test/java/sdf/adapter/SingleSdfApiService.java @@ -1,36 +1,57 @@ package sdf.adapter; import com.sunyard.chsm.constant.CryptoConst; +import com.sunyard.chsm.enums.DeviceTmkStatus; import com.sunyard.chsm.enums.Padding; +import com.sunyard.chsm.mapper.SpDeviceMapper; +import com.sunyard.chsm.model.dto.DeviceCheckRes; +import com.sunyard.chsm.model.entity.Device; import com.sunyard.chsm.sdf.SdfApiService; import com.sunyard.chsm.sdf.adapter.SdfApiAdapter; +import com.sunyard.chsm.sdf.adapter.SdfApiAdapterFactory; import com.sunyard.chsm.sdf.context.AlgId; +import com.sunyard.chsm.sdf.model.DeviceInfo; import com.sunyard.chsm.sdf.model.EccCipher; 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.sdf.model.EccSignature; import com.sunyard.chsm.sdf.util.PaddingUtil; +import com.sunyard.chsm.service.TmkService; import com.sunyard.chsm.utils.gm.BCECUtils; import com.sunyard.chsm.utils.gm.BCSM3Utils; import com.sunyard.chsm.utils.gm.SM2PreprocessSigner; import lombok.NoArgsConstructor; +import org.apache.commons.lang3.StringUtils; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.params.ParametersWithID; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; import org.springframework.util.Assert; +import javax.annotation.Resource; +import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + /** * @author liulu * @since 2024/12/11 */ +@Service @NoArgsConstructor -public class SingleSdfApiService implements SdfApiService { +public class SingleSdfApiService implements SdfApiService, InitializingBean { private String tmkHandle; private String deviceHandle; private String sessionHandle; private SdfApiAdapter sdfApiAdapter; + @Resource + public TmkService tmkService; + @Resource + private SpDeviceMapper spDeviceMapper; + public SingleSdfApiService(SdfApiAdapter sdfApiAdapter) { this.sdfApiAdapter = sdfApiAdapter; this.deviceHandle = sdfApiAdapter.openDevice(); @@ -70,7 +91,10 @@ public class SingleSdfApiService implements SdfApiService { @Override public EccSignature externalSignECC(byte[] privateKey, byte[] pucData) { - return externalSignWithIdECC(privateKey, pucData, new byte[0]); + Assert.notNull(pucData, "待签名数据不能为空"); + Assert.isTrue(pucData.length == 32, "待签名数据长度必须为32字节"); + EccPriKey priKey = EccPriKey.fromBytes(privateKey); + return sdfApiAdapter.externalSignECC(sessionHandle, priKey, pucData); } @Override @@ -103,7 +127,11 @@ public class SingleSdfApiService implements SdfApiService { @Override public boolean externalVerifyECC(byte[] publicKey, byte[] pubData, byte[] signData) { - return externalVerifyWithIdECC(publicKey, pubData, signData, new byte[0]); + Assert.notNull(pubData, "验签原文数据不能为空"); + Assert.isTrue(pubData.length == 32, "验签原文数据长度必须为32字节"); + EccPubKey eccPublicKey = EccPubKey.fromBytes(publicKey); + EccSignature eccSignature = EccSignature.fromBytes(signData); + return sdfApiAdapter.externalVerifyECC(sessionHandle, eccPublicKey, pubData, eccSignature); } @Override @@ -134,6 +162,19 @@ public class SingleSdfApiService implements SdfApiService { return BCSM3Utils.hmac(key, srcData); } + @Override + public byte[] hash(byte[] pucData, byte[] pubKey, byte[] userId) { + checkStatus(); + EccPubKey eccPubKey = pubKey == null || pubKey.length == 0 ? null : EccPubKey.fromBytes(pubKey); + byte[] finalUserId = Objects.nonNull(pubKey) && Objects.isNull(userId) ? CryptoConst.USER_ID : userId; + String newSession = sdfApiAdapter.openSession(deviceHandle); + sdfApiAdapter.hashInit(newSession, AlgId.SGD_SM3, eccPubKey, finalUserId); + sdfApiAdapter.hashUpdate(newSession, pucData); + byte[] hash = sdfApiAdapter.hashFinish(newSession); + sdfApiAdapter.closeSession(newSession); + return hash; + } + @Override public byte[] hash(byte[] pucData) { checkStatus(); @@ -167,4 +208,51 @@ public class SingleSdfApiService implements SdfApiService { checkStatus(); Assert.hasText(tmkHandle, "没有可用的主密钥设备"); } + + private void changeDevice(boolean tmkInit) { + DeviceTmkStatus status = tmkInit ? DeviceTmkStatus.finished : DeviceTmkStatus.available; + Device device = spDeviceMapper.selectOneByStatus(status); + if (Objects.nonNull(device)) { + DeviceCheckRes checkRes = tmkService.checkDevice(device); + if (!checkRes.isHasError() && checkRes.getStatus() == status) { + this.sdfApiAdapter = checkRes.getSdfApiAdapter(); + this.deviceHandle = sdfApiAdapter.openDevice(); + this.sessionHandle = sdfApiAdapter.openSession(deviceHandle); + if (tmkInit) { + sdfApiAdapter.getPrivateKeyAccessRight(sessionHandle, device.getEncKeyIdx(), device.getAccessCredentials().getBytes()); + this.tmkHandle = sdfApiAdapter.importKeyWithISKECC(sessionHandle, device.getEncKeyIdx(), EccCipher.fromHex(device.getEncTmk())); + } + return; + } + } + if (tmkService.isEnableSoftDevice()) { + this.sdfApiAdapter = SdfApiAdapterFactory.getBcAdapter(); + this.sessionHandle = sdfApiAdapter.openSession(""); + if (tmkInit) { + byte[] encTmk = tmkService.getSoftDeviceEncTmk(); + this.tmkHandle = sdfApiAdapter.importKeyWithISKECC(sessionHandle, device.getEncKeyIdx(), EccCipher.fromBytes(encTmk)); + } + } + } + + @Override + public void afterPropertiesSet() throws Exception { + Executors.newSingleThreadScheduledExecutor() + .scheduleWithFixedDelay(() -> { + boolean tmkInit = tmkService.isTmkInit(); + if (sdfApiAdapter == null) { + changeDevice(tmkInit); + return; + } + if (tmkInit && StringUtils.isEmpty(tmkHandle)) { + changeDevice(tmkInit); + return; + } + try { + DeviceInfo deviceInfo = sdfApiAdapter.getDeviceInfo(sessionHandle); + } catch (Exception ex) { + changeDevice(tmkInit); + } + }, 0L, 5L, TimeUnit.MINUTES); + } }