非对称加解密

This commit is contained in:
liulu 2024-12-20 15:03:22 +08:00
parent 0ba56c7c25
commit 7fe6482983
23 changed files with 609 additions and 33 deletions

View File

@ -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");
}

View File

@ -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);

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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);

View File

@ -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<AsymEncryptResp> 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<AsymDecryptResp> 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<AsymSignRawResp> 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<VerifyResp> verifyRaw(@Valid @RequestBody AsymVerifyRawReq req) {
VerifyResp resp = asymKeyService.verifyRaw(req);
return R.data(resp);
}
}

View File

@ -36,7 +36,9 @@ public class HashController {
@AuthCode(AuthCodeConst.cal_hash)
public R<HashResp> 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);

View File

@ -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<SymHmacCheckResp> macCheck(@Valid @RequestBody SymHmacCheckReq req) {
SymHmacCheckResp resp = symKeyService.hmacCheck(req);
public R<VerifyResp> 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<SymMacCheckResp> macCheck(@Valid @RequestBody SymMacCheckReq req) {
SymMacCheckResp resp = symKeyService.macCheck(req);
public R<VerifyResp> macCheck(@Valid @RequestBody SymMacCheckReq req) {
VerifyResp resp = symKeyService.macCheck(req);
return R.data(resp);
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}