hash 运算

This commit is contained in:
liulu 2024-12-25 15:45:35 +08:00
parent 182918a7b9
commit 6cdf222def
15 changed files with 275 additions and 71 deletions

View File

@ -63,7 +63,7 @@ public class SdfApiAdapterFactory {
new Class[]{SdfApiAdapter.class},
(proxy, method, args) -> {
if (Objects.equals(method.getName(), "openDevice")) {
return rpcSdfAdapter.openDevice(ip, port, 3000, 3000, 0);
return rpcSdfAdapter.openDevice(ip, port, 3000, 10000, 0);
}
return method.invoke(rpcSdfAdapter, args);
}

View File

@ -33,7 +33,7 @@ public class SunyardJnaSdfAdaptor extends JnaSdfAdaptor {
private final Integer dealTimeout;
public SunyardJnaSdfAdaptor(String ip, int port) {
this(ip, port, 3000, 3000);
this(ip, port, 3000, 10000);
}
public SunyardJnaSdfAdaptor(String ip, int port, int connTimeout, int dealTimeout) {

View File

@ -23,7 +23,7 @@ public class AppTokenReq {
* 时间戳毫秒数与服务器时间不能超过5分钟
*/
@NotNull
private Long random;
private Long timestamp;
/**
* 使用appKey+random+appSecret作为原文使用appSecret作为key根据HmacSM3算法计算得到hmac值,转为Hex编码
*/

View File

@ -0,0 +1,23 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* @author liulu
* @since 2024/12/25
*/
@Data
public class GenRandomReq {
// 随机数长度, 最大10240
@NotNull(message = "随机数长度不能为空")
@Min(value = 1L, message = "随机数长度最小为1")
@Max(value = 10240L, message = "随机数长度最大为10240")
private Integer length;
}

View File

@ -0,0 +1,16 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author liulu
* @since 2024/12/25
*/
@Data
public class GenRandomResp {
// 随机数, base64编码
private String random;
}

View File

@ -3,15 +3,14 @@ package com.sunyard.chsm.param;
import com.sunyard.chsm.enums.HashAlg;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class HashReq {
// 明文,使用Base64编码
@NotBlank(message = "明文不能为空")
private String plainData;
// 句柄, 多包计算Init后返回
private String handle;
// Hash算法, 默认SM3
private HashAlg alg = HashAlg.SM3;

View File

@ -5,6 +5,9 @@ import lombok.Data;
@Data
public class HashResp {
// 句柄, 多包计算Init后返回
private String handle;
// mac值,使用Base64编码
private String hash;

View File

@ -41,11 +41,5 @@ public class AppLoginController {
return R.data(appToken);
}
@PostMapping("/appUser/getAppTokenTest")
public R<AppTokenResp> getAppTokenForTest(@Valid @RequestBody AppTokenReq appTokenReq) {
AppTokenResp appToken = appLoginService.getAppTokenForTest(appTokenReq);
return R.data(appToken);
}
}

View File

@ -2,17 +2,29 @@ package com.sunyard.chsm.controller;
import com.sunyard.chsm.auth.AuthCode;
import com.sunyard.chsm.constant.AuthCodeConst;
import com.sunyard.chsm.constant.CryptoConst;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.param.HashReq;
import com.sunyard.chsm.param.HashResp;
import com.sunyard.chsm.pool.DeviceManager;
import com.sunyard.chsm.pool.TMKContext;
import com.sunyard.chsm.sdf.SdfApiService;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapter;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.model.EccPubKey;
import com.sunyard.chsm.utils.CodecUtils;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 杂凑运算类接口
@ -25,6 +37,8 @@ public class HashController {
@Resource
private SdfApiService sdfApiService;
@Resource
private DeviceManager deviceManager;
/**
* 计算Hash
@ -35,7 +49,9 @@ public class HashController {
@PostMapping("/hash")
@AuthCode(AuthCodeConst.cal_hash)
public R<HashResp> hash(@Valid @RequestBody HashReq req) {
Assert.hasText(req.getPlainData(), "原文不能为空");
byte[] bytes = CodecUtils.decodeBase64(req.getPlainData());
Assert.isTrue(bytes.length <= 8192, "原文长度最大支持8192");
byte[] pubkey = CodecUtils.decodeBase64(req.getPubKey());
byte[] userId = CodecUtils.decodeBase64(req.getUserId());
byte[] hash = sdfApiService.hash(bytes, pubkey, userId);
@ -44,5 +60,89 @@ public class HashController {
return R.data(resp);
}
private static final Map<String, TMKContext> HANDLE_MAP = new ConcurrentHashMap<>();
/**
* 多包HashInit
*
* @param req
* @return
*/
@PostMapping("/multi/package/hash/init")
@AuthCode(AuthCodeConst.cal_hash)
public R<HashResp> hashInit(@Valid @RequestBody HashReq req) {
byte[] pubKey = CodecUtils.decodeBase64(req.getPubKey());
byte[] userId = CodecUtils.decodeBase64(req.getUserId());
TMKContext context = deviceManager.chooseOne();
try {
SdfApiAdapter sdf = context.getSdfApiAdapter();
TMKContext newContext = new TMKContext();
newContext.setDeviceHandle(context.getDeviceHandle());
newContext.setSdfApiAdapter(sdf);
String hs = sdf.openSession(context.getDeviceHandle());
newContext.setSessionHandle(hs);
EccPubKey eccPubKey = pubKey == null || pubKey.length == 0 ? null : EccPubKey.fromBytes(pubKey);
byte[] finalUserId = Objects.nonNull(pubKey) && Objects.isNull(userId) ? CryptoConst.USER_ID : userId;
sdf.hashInit(hs, AlgId.SGD_SM3, eccPubKey, finalUserId);
String handle = UUID.randomUUID().toString();
HANDLE_MAP.put(handle, newContext);
HashResp resp = new HashResp();
resp.setHandle(handle);
return R.data(resp);
} finally {
Optional.ofNullable(context.getPool())
.ifPresent(it -> {
context.setPool(null);
it.returnObject(context);
});
}
}
/**
* 多包Hash update
*
* @param req
* @return
*/
@PostMapping("/multi/package/hash/update")
@AuthCode(AuthCodeConst.cal_hash)
public R<HashResp> hashUpdate(@Valid @RequestBody HashReq req) {
Assert.hasText(req.getPlainData(), "原文不能为空");
Assert.hasText(req.getHandle(), "句柄不能为空");
byte[] bytes = CodecUtils.decodeBase64(req.getPlainData());
Assert.isTrue(bytes.length <= 8192, "原文长度最大支持8192");
TMKContext context = HANDLE_MAP.get(req.getHandle());
Assert.notNull(context, "句柄引用不存在");
SdfApiAdapter sdf = context.getSdfApiAdapter();
sdf.hashUpdate(context.getSessionHandle(), bytes);
HashResp resp = new HashResp();
resp.setHandle(req.getHandle());
return R.data(resp);
}
/**
* 多包Hash finish
*
* @param req
* @return
*/
@PostMapping("/multi/package/hash/finish")
@AuthCode(AuthCodeConst.cal_hash)
public R<HashResp> hashFinish(@Valid @RequestBody HashReq req) {
Assert.hasText(req.getHandle(), "句柄不能为空");
TMKContext context = HANDLE_MAP.remove(req.getHandle());
Assert.notNull(context, "句柄引用不存在");
SdfApiAdapter sdf = context.getSdfApiAdapter();
byte[] hash = sdf.hashFinish(context.getSessionHandle());
sdf.closeSession(context.getSessionHandle());
HashResp resp = new HashResp();
resp.setHandle(req.getHandle());
resp.setHash(CodecUtils.encodeBase64(hash));
return R.data(resp);
}
}

View File

@ -0,0 +1,43 @@
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.GenRandomReq;
import com.sunyard.chsm.param.GenRandomResp;
import com.sunyard.chsm.sdf.SdfApiService;
import com.sunyard.chsm.utils.CodecUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 随机数接口
*
* @author liulu
* @since 2024/12/25
*/
@RestController
public class RandomController {
@Resource
private SdfApiService sdfApiService;
/**
* 获取随机数
*
* @param req
* @return
*/
@PostMapping("/gen/random")
@AuthCode(AuthCodeConst.gen_random)
public R<GenRandomResp> hash(@RequestBody GenRandomReq req) {
byte[] random = sdfApiService.generateRandom(req.getLength());
GenRandomResp resp = new GenRandomResp();
resp.setRandom(CodecUtils.encodeBase64(random));
return R.data(resp);
}
}

View File

@ -21,14 +21,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -50,7 +43,7 @@ public class AppLoginService {
private AppServiceMapper appServiceMapper;
public AppTokenResp getAppToken(AppTokenReq req) {
Long random = req.getRandom();
Long random = req.getTimestamp();
long now = System.currentTimeMillis();
Assert.isTrue(now - random <= 5 * 60 * 1000, "请求已超时");
String appKey = req.getAppKey();
@ -58,7 +51,7 @@ public class AppLoginService {
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(application.getStatus()), "此应用已停用");
String data = appKey + random + application.getAppSecret();
byte[] hmac = BCSM3Utils.hmac(application.getAppSecret().getBytes(), data.getBytes());
String serverHmac = CodecUtils.encodeHex(hmac);
String serverHmac = CodecUtils.encodeBase64(hmac);
if (!Objects.equals(req.getHmac(), serverHmac)) {
log.warn("appKey: {}, req hmac: {}, server hmac: {}", appKey, req.getHmac(), serverHmac);
throw new IllegalArgumentException("应用认证失败");
@ -86,22 +79,6 @@ public class AppLoginService {
.compact();
}
public AppTokenResp getAppTokenForTest(@Valid AppTokenReq req) {
// Long random = req.getRandom();
// long now = System.currentTimeMillis();
// Assert.isTrue(now - random <= 5 * 60 * 1000, "请求已超时");
Application application = applicationMapper.selectByAppKey(req.getAppKey());
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(application.getStatus()), "此应用已停用");
if (!Objects.equals(req.getHmac(), application.getAppSecret())) {
log.warn("appKey: {}, req hmac: {},", req.getAppKey(), req.getHmac());
throw new IllegalArgumentException("应用认证失败");
}
AppTokenResp resp = new AppTokenResp();
resp.setToken(genToken(application));
return resp;
}
public AppUser verifyToken(String token) {
Claims claims = Jwts.parser()

View File

@ -1,27 +1,12 @@
package com.sunyard.chsm.service;
import com.sunyard.chsm.auth.UserContext;
import com.sunyard.chsm.enums.AlgMode;
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.enums.Padding;
import com.sunyard.chsm.enums.*;
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.SymDecryptReq;
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.SymHmacReq;
import com.sunyard.chsm.param.SymHmacResp;
import com.sunyard.chsm.param.SymMacCheckReq;
import com.sunyard.chsm.param.SymMacReq;
import com.sunyard.chsm.param.SymMacResp;
import com.sunyard.chsm.param.VerifyResp;
import com.sunyard.chsm.param.*;
import com.sunyard.chsm.sdf.SdfApiService;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.utils.CodecUtils;
@ -55,6 +40,7 @@ public class SymKeyService {
iv = bytes;
}
byte[] plain = CodecUtils.decodeBase64(req.getPlainData());
Assert.isTrue(plain.length <= 7680, "明文长度最大支持7680");
KeyInfo keyInfo = checkKey(req.getKeyId(), KeyUsage.ENCRYPT_DECRYPT);
KeyAlg keyAlg = KeyAlg.of(keyInfo.getKeyAlg());
Assert.notNull(keyAlg, "数据异常");
@ -98,6 +84,7 @@ public class SymKeyService {
iv = bytes;
}
byte[] cipher = CodecUtils.decodeBase64(req.getCipherData());
Assert.isTrue(cipher.length <= 7696, "密文长度最大支持7696");
KeyInfo keyInfo = checkKey(req.getKeyId(), KeyUsage.ENCRYPT_DECRYPT);
KeyAlg keyAlg = KeyAlg.of(keyInfo.getKeyAlg());
Assert.notNull(keyAlg, "数据异常");

View File

@ -6,7 +6,9 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.param.AppTokenReq;
import com.sunyard.chsm.param.AppTokenResp;
import com.sunyard.chsm.utils.CodecUtils;
import com.sunyard.chsm.utils.JsonUtils;
import com.sunyard.chsm.utils.gm.BCSM3Utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.MediaType;
@ -27,17 +29,18 @@ public abstract class BaseTest {
protected static final String keyTemplate = "sym-sm4-001";
protected static final String ak = "216205d408130d83d13c5072305b8b65";
protected static final String sk = "ae64515d1d5adec2cc6ae8726d0c1bbc";
protected static final String server = "http://172.16.18.46:8900";
protected static final String server = "http://127.0.0.1:8900";
protected static final RestTemplate restTemplate;
protected static final String token;
static {
AppTokenReq req = new AppTokenReq();
req.setAppKey(ak);
req.setHmac(sk);
req.setRandom(System.currentTimeMillis());
req.setTimestamp(System.currentTimeMillis());
byte[] hmac = BCSM3Utils.hmac(sk.getBytes(), (ak + req.getTimestamp() + sk).getBytes());
req.setHmac(CodecUtils.encodeBase64(hmac));
RequestEntity<byte[]> request = RequestEntity.post(server + "/appUser/getAppTokenTest")
RequestEntity<byte[]> request = RequestEntity.post(server + "/appUser/getAppToken")
.contentType(MediaType.APPLICATION_JSON)
.body(JsonUtils.toJsonBytes(req));
ResponseEntity<String> response = new RestTemplate().exchange(request, String.class);
@ -70,7 +73,7 @@ public abstract class BaseTest {
}
JsonNode result = jsonNode.get("result");
if (result == null) {
return null;
return null;
}
JsonParser returnJsonParser = JsonUtils.objectMapper().treeAsTokens(result);
return JsonUtils.objectMapper().readValue(returnJsonParser, tClass);

View File

@ -0,0 +1,62 @@
package api;
import com.sunyard.chsm.param.HashReq;
import com.sunyard.chsm.param.HashResp;
import com.sunyard.chsm.sdf.util.LangUtils;
import com.sunyard.chsm.utils.CodecUtils;
import com.sunyard.chsm.utils.gm.BCSM3Utils;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
/**
* @author liulu
* @since 2024/12/25
*/
@Slf4j
public class HashTest extends BaseTest {
private static final byte[] plain = new byte[1024 * 7];
static {
Arrays.fill(plain, (byte) 0x04);
}
@Test
public void testSingleHash() {
HashReq hashReq = new HashReq();
hashReq.setPlainData(CodecUtils.encodeBase64(plain));
HashResp hashResp = execute("/hash", hashReq, HashResp.class);
log.info("hash: {}", hashResp.getHash());
byte[] bcHash = BCSM3Utils.hash(plain);
log.info("bc hash: {}", CodecUtils.encodeBase64(bcHash));
Assertions.assertEquals(CodecUtils.encodeBase64(bcHash), hashResp.getHash());
}
@Test
public void testMultiHash() {
HashReq hashReq = new HashReq();
HashResp hashResp = execute("/multi/package/hash/init", hashReq, HashResp.class);
log.info("hash: {}", hashResp);
hashReq.setPlainData(CodecUtils.encodeBase64(plain));
hashReq.setHandle(hashResp.getHandle());
for (int i = 0; i < 2; i++) {
hashResp = execute("/multi/package/hash/update", hashReq, HashResp.class);
}
hashResp = execute("/multi/package/hash/finish", hashReq, HashResp.class);
log.info("hash: {}", hashResp);
byte[] bcHash = BCSM3Utils.hash(LangUtils.merge(plain, plain));
log.info("bc hash: {}", CodecUtils.encodeBase64(bcHash));
Assertions.assertEquals(CodecUtils.encodeBase64(bcHash), hashResp.getHash());
}
}

View File

@ -1,12 +1,7 @@
package api;
import com.sunyard.chsm.enums.AlgMode;
import com.sunyard.chsm.param.KeyCreateReq;
import com.sunyard.chsm.param.KeyManageReq;
import com.sunyard.chsm.param.SymDecryptReq;
import com.sunyard.chsm.param.SymDecryptResp;
import com.sunyard.chsm.param.SymEncryptReq;
import com.sunyard.chsm.param.SymEncryptResp;
import com.sunyard.chsm.param.*;
import com.sunyard.chsm.utils.CodecUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterAll;
@ -15,6 +10,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.Collections;
/**
@ -24,12 +20,13 @@ import java.util.Collections;
@Slf4j
public class SymKeyTest extends BaseTest {
private static final String plain = "hjsu234127qikqwndqqw13412as324";
private static final byte[] plain = new byte[1024 * 7];
private final static byte[] iv = "ghwikdhj1234713v".getBytes();
private static Long keyId;
@BeforeAll
public static void beforeAll() {
Arrays.fill(plain, (byte) 0x04);
keyId = execute("/key/gen", KeyCreateReq.builder().keyTemplateCode(keyTemplate).genNumber(1).build(), Long.class);
Assertions.assertTrue(keyId > 0);
}
@ -47,7 +44,7 @@ public class SymKeyTest extends BaseTest {
SymEncryptReq symEncryptReq = new SymEncryptReq();
symEncryptReq.setKeyId(keyId);
symEncryptReq.setPlainData(CodecUtils.encodeBase64(plain.getBytes()));
symEncryptReq.setPlainData(CodecUtils.encodeBase64(plain));
symEncryptReq.setIv(CodecUtils.encodeBase64(iv));
symEncryptReq.setMode(AlgMode.CBC);
@ -56,7 +53,7 @@ public class SymKeyTest extends BaseTest {
Assertions.assertNotNull(symEncryptResp);
Assertions.assertTrue(StringUtils.hasText(symEncryptResp.getKeyIndex()));
byte[] cipherData = CodecUtils.decodeBase64(symEncryptResp.getCipherData());
Assertions.assertEquals(32, cipherData.length);
// Assertions.assertEquals(32, cipherData.length);
SymDecryptReq decryptReq = new SymDecryptReq();
decryptReq.setKeyId(keyId);
@ -65,10 +62,10 @@ public class SymKeyTest extends BaseTest {
decryptReq.setMode(AlgMode.CBC);
decryptReq.setCipherData(symEncryptResp.getCipherData());
SymDecryptResp decryptResp = execute("/sym/decrypt", decryptReq, SymDecryptResp.class);
String calPlain = new String(CodecUtils.decodeBase64(decryptResp.getPlainData()));
byte[] calPlain = CodecUtils.decodeBase64(decryptResp.getPlainData());
log.info("SymDecryptResp: {}, {}", calPlain, decryptResp);
Assertions.assertNotNull(decryptResp);
Assertions.assertEquals(plain, calPlain);
Assertions.assertArrayEquals(plain, calPlain);
}