Compare commits

..

2 Commits

Author SHA1 Message Date
zhengyf 158b9bee73 1、新增认领功能。 2025-08-26 18:42:50 +08:00
zhengyf f468be5cf2 1、新增浦发GTS签名校验 2025-08-26 18:42:11 +08:00
30 changed files with 2287 additions and 12 deletions

View File

@ -20,6 +20,61 @@
<artifactId>base-webapp</artifactId> <artifactId>base-webapp</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcmail-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk15on</artifactId>
<version>1.56</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.57</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.57</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.xiaoleilu/hutool-all -->
<dependency>
<groupId>com.xiaoleilu</groupId>
<artifactId>hutool-all</artifactId>
<version>3.0.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.squareup.okio/okio -->
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>1.8.0</version>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>

View File

@ -52,4 +52,7 @@ public class ClaimController extends DefaultController {
} }

View File

@ -1,7 +1,9 @@
package com.hzya.frame.finance.claim.controller; package com.hzya.frame.finance.claim.controller;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import com.hzya.frame.finance.claim.entity.ClaimVO;
import com.hzya.frame.finance.claim.service.IClaimSKService; import com.hzya.frame.finance.claim.service.IClaimSKService;
import com.hzya.frame.finance.claim.service.IClaimService;
import com.hzya.frame.finance.conf.fileeigen.entity.FeConfFileEigenEntity; import com.hzya.frame.finance.conf.fileeigen.entity.FeConfFileEigenEntity;
import com.hzya.frame.mdm.entity.MdmViewVo; import com.hzya.frame.mdm.entity.MdmViewVo;
import com.hzya.frame.voucher.ae.comf.bd.entity.vo.MdmDBQueryVO; import com.hzya.frame.voucher.ae.comf.bd.entity.vo.MdmDBQueryVO;
@ -26,6 +28,8 @@ public class ClaimSKController extends DefaultController {
@Autowired @Autowired
private IClaimSKService claimSKService; private IClaimSKService claimSKService;
@Autowired
private IClaimService claimService;
/** /**
@ -58,7 +62,7 @@ public class ClaimSKController extends DefaultController {
/** /**
* 流水查询数据 * 流水查询数据ids
*/ */
@RequestMapping(value = "/queryFlowDateIds", method = RequestMethod.POST) @RequestMapping(value = "/queryFlowDateIds", method = RequestMethod.POST)
public JsonResultEntity queryFlowDateIds(@RequestBody MdmDBQueryVO entity) { public JsonResultEntity queryFlowDateIds(@RequestBody MdmDBQueryVO entity) {
@ -72,5 +76,44 @@ public class ClaimSKController extends DefaultController {
} }
/**
* 流水生成认领单
*/
@RequestMapping(value = "/generate", method = RequestMethod.POST)
public JsonResultEntity generate(@RequestBody MdmDBQueryVO vo) throws Exception {
int i=0;
int j=0;
StringBuffer cgsb = new StringBuffer();
StringBuffer sbsb = new StringBuffer();
try {
String ids = vo.getIds();
String[] split = ids.split(",");
for (String s : split) {
try {
vo.setId(s);
claimService.generate(vo);
j++;
cgsb.append(j).append("、流水号:[");
cgsb.append(s).append("];");
}catch (Exception e){
i++;
sbsb.append(i).append("、流水号:[").append(s);
sbsb.append("]失败原因:").append(e.getMessage()).append(";");
}
}
if(i==0){
return getSuccessMessageEntity("生成成功。"+cgsb);
}else {
return getFailureMessageEntity("生成成功:"+j+"条。:"+cgsb+"\n失败"+i+"条,失败原因:"+sbsb);
}
}catch (Exception e){
e.printStackTrace();
return getFailureMessageEntity(e.getMessage());
}
}
} }

View File

@ -17,6 +17,6 @@ public class ClaimVO extends BaseEntity {
private String billDate; private String billDate;
private String billCustomer; private String billCustomer;
private String isAutoClaim;//自动认领 private String isAutoClaim;//自动认领
private String claimUser;//自动认领 private String claimUser;
private String claimStatus;//自动认领 private String claimStatus;
} }

View File

@ -13,7 +13,7 @@ import lombok.Data;
* @since 2025-08-26 11:18:08 * @since 2025-08-26 11:18:08
*/ */
@Data @Data
public class FeClaimBillHEntity extends BaseEntity { public class FeClaimBillHEntity extends FeClaimBillBEntity {
// private Long id; // private Long id;

View File

@ -3,6 +3,7 @@ package com.hzya.frame.finance.claim.service;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import com.hzya.frame.finance.claim.entity.ClaimVO; import com.hzya.frame.finance.claim.entity.ClaimVO;
import com.hzya.frame.finance.claim.entity.FeClaimBillHEntity; import com.hzya.frame.finance.claim.entity.FeClaimBillHEntity;
import com.hzya.frame.voucher.ae.comf.bd.entity.vo.MdmDBQueryVO;
import java.util.List; import java.util.List;
@ -13,4 +14,6 @@ public interface IClaimService {
List<FeClaimBillHEntity> queryAll(ClaimVO vo); List<FeClaimBillHEntity> queryAll(ClaimVO vo);
PageInfo queryPaged(ClaimVO vo); PageInfo queryPaged(ClaimVO vo);
String generate(MdmDBQueryVO vo);
} }

View File

@ -1,5 +1,6 @@
package com.hzya.frame.finance.claim.service.impl; package com.hzya.frame.finance.claim.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
@ -11,6 +12,11 @@ import com.hzya.frame.finance.claim.entity.ClaimVO;
import com.hzya.frame.finance.claim.entity.FeClaimBillBEntity; import com.hzya.frame.finance.claim.entity.FeClaimBillBEntity;
import com.hzya.frame.finance.claim.entity.FeClaimBillHEntity; import com.hzya.frame.finance.claim.entity.FeClaimBillHEntity;
import com.hzya.frame.finance.claim.service.IClaimService; import com.hzya.frame.finance.claim.service.IClaimService;
import com.hzya.frame.finance.conf.billcode.dao.IFeConfBillcodeRuleDao;
import com.hzya.frame.finance.flow.dao.IMdmKkBankflowGtsDao;
import com.hzya.frame.finance.flow.entity.MdmKkBankflowGtsEntity;
import com.hzya.frame.finance.utils.ClaimBillCodeUtil;
import com.hzya.frame.voucher.ae.comf.bd.entity.vo.MdmDBQueryVO;
import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -31,6 +37,12 @@ public class IClaimServiceImpl implements IClaimService {
@Autowired @Autowired
private IFeClaimBillBDao claimBillBDao; private IFeClaimBillBDao claimBillBDao;
@Autowired
private IMdmKkBankflowGtsDao kkBankflowGtsDao;
@Autowired
private ClaimBillCodeUtil claimBillCodeUtil;
@Override @Override
public List<FeClaimBillHEntity> queryAll(ClaimVO vo) { public List<FeClaimBillHEntity> queryAll(ClaimVO vo) {
@ -77,4 +89,88 @@ public class IClaimServiceImpl implements IClaimService {
} }
return null; return null;
} }
/**
* 流水生成 - 生成认领单
* 1保存单号 流水单号-认领单号
* 2回写流水数据中的认领单号
* 3保存认领单
*
*/
@Override
public String generate(MdmDBQueryVO vo) {
try {
String id = vo.getId();
MdmKkBankflowGtsEntity mdmKkBankflowGtsEntity = new MdmKkBankflowGtsEntity();
mdmKkBankflowGtsEntity.setId(id);
List<MdmKkBankflowGtsEntity> bankflowGtsList = kkBankflowGtsDao.query(mdmKkBankflowGtsEntity);
if(bankflowGtsList.size()==0){
Assert.state(false,"根据流水id[{}],未查询到流水信息。",id);
}
MdmKkBankflowGtsEntity kkBankflow = bankflowGtsList.get(0);
System.out.println(kkBankflow);
if("Y".equals(kkBankflow.getClaimstatus())){
Assert.state(false,"流水已认领流水id{}。",id);
}
FeClaimBillHEntity feClaimBillHEntity = new FeClaimBillHEntity();
//认领类型 SK/FK D-C- 转入/转出标志
String outflag = kkBankflow.getOutflag();
if("D".equals(outflag)){
feClaimBillHEntity.setClaimType("SK");
}else if("C".equals(outflag)){
feClaimBillHEntity.setClaimType("FK");
}
//获取认领单单号
String claimBillCode = claimBillCodeUtil.saveBillCodeByTypeAndSourceCode(feClaimBillHEntity.getClaimType(), kkBankflow.getTranseqno1());
feClaimBillHEntity.setBillCode(claimBillCode);
//认领时间默认流水交易时间
String trandate = kkBankflow.getTrandate();
String trantimep = kkBankflow.getTrantimep();
feClaimBillHEntity.setBillData(trandate+" "+trantimep);
//对方户名cnterAcctName
String cnteracctname = kkBankflow.getCnteracctname();
feClaimBillHEntity.setCustomerName(cnteracctname);
//收付交易账号
String cnteracctno = kkBankflow.getCnteracctno();
feClaimBillHEntity.setBankNum(cnteracctno);
//往来对象
feClaimBillHEntity.setWldxName("客商");
feClaimBillHEntity.setIsAutoClaim("N");
//认领人 当前登陆人
// Object loginId = StpUtil.getLoginId();
// feClaimBillHEntity.setClaimUserId(loginId.toString());
feClaimBillHEntity.setClaimUserId("1");
//认领金额
String tranamt = kkBankflow.getTranamt();
feClaimBillHEntity.setClaimSum(tranamt);
//保存认领单
claimBillHDao.save(feClaimBillHEntity);
//更新流水认领单号以及认领状态
mdmKkBankflowGtsEntity.setClaimbillcode(claimBillCode);
mdmKkBankflowGtsEntity.setClaimstatus("Y");
kkBankflowGtsDao.update(mdmKkBankflowGtsEntity);
}catch (Exception e){
e.printStackTrace();
Assert.state(false,"生成认领单失败,失败原因:{}",e.getMessage());
}
return null;
}
} }

View File

@ -1,8 +1,10 @@
package com.hzya.frame.finance.flow.controller; package com.hzya.frame.finance.flow.controller;
import com.hzya.frame.finance.claim.entity.ClaimVO;
import com.hzya.frame.finance.flow.entity.MdmKkBankflowGtsEntity; import com.hzya.frame.finance.flow.entity.MdmKkBankflowGtsEntity;
import com.hzya.frame.finance.flow.service.IMdmKkBankflowGtsService; import com.hzya.frame.finance.flow.service.IMdmKkBankflowGtsService;
import com.hzya.frame.web.action.DefaultController; import com.hzya.frame.web.action.DefaultController;
import com.hzya.frame.web.entity.JsonResultEntity;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -21,13 +23,27 @@ public class GTSController extends DefaultController {
@Autowired @Autowired
private IMdmKkBankflowGtsService bankflowGtsService; private IMdmKkBankflowGtsService bankflowGtsService;
@RequestMapping(value = "/insert", method = RequestMethod.POST) // @RequestMapping(value = "/insert", method = RequestMethod.POST)
public void insert(@RequestBody MdmKkBankflowGtsEntity entity) throws Exception { // public void insert(@RequestBody MdmKkBankflowGtsEntity entity) throws Exception {
for (int i = 0; i < 4000; i++) { // for (int i = 0; i < 4000; i++) {
MdmKkBankflowGtsEntity mdmKkBankflowGtsEntity = new MdmKkBankflowGtsEntity(); // MdmKkBankflowGtsEntity mdmKkBankflowGtsEntity = new MdmKkBankflowGtsEntity();
mdmKkBankflowGtsEntity.setId(UUID.randomUUID().toString()); // mdmKkBankflowGtsEntity.setId(UUID.randomUUID().toString());
bankflowGtsService.save(mdmKkBankflowGtsEntity); // bankflowGtsService.save(mdmKkBankflowGtsEntity);
} // }
} // }
//流水生成认领单
// @RequestMapping(value = "/generate", method = RequestMethod.POST)
// public JsonResultEntity generate(@RequestBody ClaimVO vo) throws Exception {
// try {
// return getSuccessMessageEntity(null);
// }catch (Exception e){
// e.printStackTrace();
// return getFailureMessageEntity(e.getMessage());
// }
//
// }
} }

View File

@ -0,0 +1,21 @@
package com.hzya.frame.finance.utils.pufabank;
import java.security.MessageDigest;
import javax.xml.bind.DatatypeConverter;
public class SHA1 {
public static String digest(String content) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.update(content.getBytes("UTF-8"));
byte[] digestBytes = messageDigest.digest();
return DatatypeConverter.printBase64Binary(digestBytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,105 @@
package com.hzya.frame.finance.utils.pufabank;
import java.math.BigInteger;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
/**
* 国密加密类实现
*
*/
public class SM2Cipher {
private int ct;
private ECPoint p2;
private SM3Digest sm3keybase;// SM3摘要
private SM3Digest sm3c3;
private byte key[];
private byte keyOff;
public SM2Cipher() {
this.ct = 1;
this.key = new byte[32];
this.keyOff = 0;
}
/**
* 重置
*/
private void Reset() {
this.sm3keybase = new SM3Digest();
this.sm3c3 = new SM3Digest();
byte p[] = SM2Util.byteConvert32Bytes(p2.getXCoord().toBigInteger());
this.sm3keybase.update(p, 0, p.length);
this.sm3c3.update(p, 0, p.length);
p = SM2Util.byteConvert32Bytes(p2.getYCoord().toBigInteger());
this.sm3keybase.update(p, 0, p.length);
this.ct = 1;
NextKey();
}
private void NextKey() {
SM3Digest sm3keycur = new SM3Digest(this.sm3keybase);
sm3keycur.update((byte) (ct >> 24 & 0xff));
sm3keycur.update((byte) (ct >> 16 & 0xff));
sm3keycur.update((byte) (ct >> 8 & 0xff));
sm3keycur.update((byte) (ct & 0xff));
sm3keycur.doFinal(key, 0);
this.keyOff = 0;
this.ct++;
}
public ECPoint Init_enc(SM2Factory sm2, ECPoint userKey) {
AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
BigInteger k = ecpriv.getD();
ECPoint c1 = ecpub.getQ();
this.p2 = userKey.multiply(k);
Reset();
return c1;
}
public void Encrypt(byte data[]) {
this.sm3c3.update(data, 0, data.length);
for (int i = 0; i < data.length; i++) {
if (keyOff == key.length) {
NextKey();
}
data[i] ^= key[keyOff++];
}
}
public void Init_dec(BigInteger userD, ECPoint c1) {
this.p2 = c1.multiply(userD);
Reset();
}
public void Decrypt(byte data[]) {
for (int i = 0; i < data.length; i++) {
if (keyOff == key.length) {
NextKey();
}
data[i] ^= key[keyOff++];
}
this.sm3c3.update(data, 0, data.length);
}
public void Dofinal(byte c3[]) {
byte p[] = SM2Util.byteConvert32Bytes(p2.getYCoord().toBigInteger());
this.sm3c3.update(p, 0, p.length);
this.sm3c3.doFinal(c3, 0);
Reset();
}
}

View File

@ -0,0 +1,173 @@
package com.hzya.frame.finance.utils.pufabank;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
/**
* 报文加解密工具类
*
*/
public class SM2Cryptor {
static {
Security.addProvider(new BouncyCastleProvider());
}
// 算法名称
public static final String ALGORITHM_NAME = "sm4";
// P5填充
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
/**
* SHA256加密
*
* @param str
* @return
*/
public static String sha256(String str) {
MessageDigest messageDigest = null;
String enencdeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest.digest(str.getBytes("UTF-8"));
enencdeStr = Hex.encodeHexString(hash);
} catch (Exception e) {
e.printStackTrace();
}
return enencdeStr;
}
/**
* MD5加密
*
* @param hash
* @return
*/
public static String md5(String hash) {
String md5Str = DigestUtils.md5Hex(hash);
return md5Str;
}
/**
*
* sm3加密处理
*
* @param data
* @return 2019年11月26日
*
*/
public static String sm3(String data) {
String charset = "UTF-8";
String sm3Data = "";
try {
byte[] dataBytes = data.getBytes(charset);
byte[] hashBytes = hash(dataBytes);
sm3Data = ByteUtils.toHexString(hashBytes);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return sm3Data;
}
/**
*
* 返回长度为32位的byte数组 生成对应的hash值
*
* @param dataBytes
* @return 2019年10月28日
*
*/
public static byte[] hash(byte[] dataBytes) {
SM3Digest digest = new SM3Digest();
digest.update(dataBytes, 0, dataBytes.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return hash;
}
/**
* 报文体加密
*
* @param key
* @param data
* @return
*/
public static String encrypt(String key, String data) {
String encrypted = "";
try {
String charset = "UTF-8";
String sha256Key = sha256(key);
String sm3Key = sm3(sha256Key);
String md5Key = md5(sm3Key);
byte[] keyBytes = ByteUtils.fromHexString(md5Key);
byte[] dataBytes = data.getBytes(charset);
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING,
BouncyCastleProvider.PROVIDER_NAME);
SecretKeySpec sm4Key = new SecretKeySpec(keyBytes, ALGORITHM_NAME);
cipher.init(Cipher.ENCRYPT_MODE, sm4Key);
byte[] encryptBytes = cipher.doFinal(dataBytes);
String hexSignature = ByteUtils.toHexString(encryptBytes).toUpperCase();
byte[] signBytes = hexSignature.getBytes(charset);
encrypted = DatatypeConverter.printBase64Binary(signBytes);
return encrypted;
} catch (Exception e) {
e.printStackTrace();
}
return encrypted;
}
/**
* 报文体解密
*
* @param key
* @param signature
* @return
*/
public static String decrypt(String key, String encrypted) {
String decrypted = "";
try {
String sha256Key = sha256(key);
String sm3Key = sm3(sha256Key);
String md5Key = md5(sm3Key);
byte[] keyBytes = ByteUtils.fromHexString(md5Key);
byte[] encryptBytes = DatatypeConverter.parseBase64Binary(encrypted);
String hexSignature = new String(encryptBytes).toLowerCase();
byte[] cipherBytes = ByteUtils.fromHexString(hexSignature);
Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING,
BouncyCastleProvider.PROVIDER_NAME);
SecretKeySpec sm4Key = new SecretKeySpec(keyBytes, ALGORITHM_NAME);
cipher.init(Cipher.DECRYPT_MODE, sm4Key);
byte[] doFinal = cipher.doFinal(cipherBytes);
decrypted = new String(doFinal);
return decrypted;
} catch (Exception e) {
e.printStackTrace();
}
return decrypted;
}
}

View File

@ -0,0 +1,79 @@
package com.hzya.frame.finance.utils.pufabank;
import java.io.IOException;
import java.math.BigInteger;
import org.bouncycastle.math.ec.ECPoint;
/**
* SM2加解密工具类
*
*/
public class SM2EnDecryptor {
/**
* SM2加密
*
* @param hexStrPub 公钥信息
* @param data 加密数据信息
* @return
*/
public static String encrypt(byte[] hexStrPub, byte[] data) {
byte[] source = new byte[data.length];
byte[] formatedPubKey;
System.arraycopy(data, 0, source, 0, data.length);
SM2Cipher cipher2sm2 = new SM2Cipher();
SM2Factory sm2Factory = SM2Factory.getInstance();
if (hexStrPub.length == 64) {
formatedPubKey = new byte[65];
formatedPubKey[0] = 0x04;
System.arraycopy(hexStrPub, 0, formatedPubKey, 1, hexStrPub.length);
} else {
formatedPubKey = hexStrPub;
}
ECPoint ecPoint = sm2Factory.ecc_curve.decodePoint(formatedPubKey);
ECPoint c1 = cipher2sm2.Init_enc(sm2Factory, ecPoint);
cipher2sm2.Encrypt(source);
byte[] c3 = new byte[32];
cipher2sm2.Dofinal(c3);
return SM2Util.byteToHex(c1.getEncoded(false)) + SM2Util.byteToHex(source) + SM2Util.byteToHex(c3);
}
/**
* SM2解密
*
* @param privateKey 私钥信息
* @param encryptedData 加密后的数据信息
* @return
* @throws IOException
*/
public static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException {
if (privateKey == null || privateKey.length == 0) {
return null;
}
if (encryptedData == null || encryptedData.length == 0) {
return null;
}
// 加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2
String data = SM2Util.byteToHex(encryptedData);
/***
* 分解加密字串 C1 = C1标志位2位 + C1实体部分128位 = 130 C3 = C3实体部分64位 = 64 C2 = encryptedData.length
* * 2 - C1长度 - C2长度
*/
byte[] c1Bytes = SM2Util.hexToByte(data.substring(0, 130));
int c2Len = encryptedData.length - 97;
byte[] c2 = SM2Util.hexToByte(data.substring(130, 130 + 2 * c2Len));
byte[] c3 = SM2Util.hexToByte(data.substring(130 + 2 * c2Len, 194 + 2 * c2Len));
SM2Factory sm2 = SM2Factory.getInstance();
BigInteger userD = new BigInteger(1, privateKey);
// 通过C1实体字节来生成ECPoint
ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes);
SM2Cipher cipher = new SM2Cipher();
cipher.Init_dec(userD, c1);
cipher.Decrypt(c2);
cipher.Dofinal(c3);
// 返回解密结果
return c2;
}
}

View File

@ -0,0 +1,197 @@
package com.hzya.frame.finance.utils.pufabank;
import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.ECFieldElement.Fp;
/**
* 国密工厂类
*
*/
@SuppressWarnings(value = { "deprecation", "static-access" })
public class SM2Factory {
/*-----------------------国密算法相关参数begin-----------
* ------------------*/
// A 第一系数
private static final BigInteger a = new BigInteger(
"fffffffeffffffffffffffffffffffffffffffff00000000fffffffffffffffc", 16);
// B 第二系数
private static final BigInteger b = new BigInteger(
"28e9fa9e9d9f5e344d5a9e4bcf6509a7f39789f515ab8f92ddbcbd414d940e93", 16);
// 曲线X系数
private static final BigInteger gx = new BigInteger(
"32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7", 16);
// 曲线Y系数
private static final BigInteger gy = new BigInteger(
"bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0", 16);
// 生产者顺序系数
private static final BigInteger n = new BigInteger(
"fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123", 16);
// 素数
private static final BigInteger p = new BigInteger(
"fffffffeffffffffffffffffffffffffffffffff00000000ffffffffffffffff", 16);
// 因子系数 1
// private static final int h = 1;
/*-----------------------国密算法相关参数end-----------------------------*/
// 一些必要类
public final ECFieldElement ecc_gx_fieldelement;
public final ECFieldElement ecc_gy_fieldelement;
public final ECCurve ecc_curve;
public final ECPoint ecc_point_g;
public final ECDomainParameters ecc_bc_spec;
public final ECKeyPairGenerator ecc_key_pair_generator;
/**
* 初始化方法
*
* @return
*/
public static SM2Factory getInstance() {
return new SM2Factory();
}
public SM2Factory() {
this.ecc_gx_fieldelement = new Fp(this.p, this.gx);
this.ecc_gy_fieldelement = new Fp(this.p, this.gy);
this.ecc_curve = new ECCurve.Fp(this.p, this.a, this.b);
this.ecc_point_g = new ECPoint.Fp(this.ecc_curve, this.ecc_gx_fieldelement, this.ecc_gy_fieldelement);
this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_point_g, this.n);
ECKeyGenerationParameters ecc_ecgenparam;
ecc_ecgenparam = new ECKeyGenerationParameters(this.ecc_bc_spec, new SecureRandom());
this.ecc_key_pair_generator = new ECKeyPairGenerator();
this.ecc_key_pair_generator.init(ecc_ecgenparam);
}
/**
* 根据私钥曲线参数计算Z
*
* @param userId
* @param userKey
* @return
*/
public byte[] sm2GetZ(byte[] userId, ECPoint userKey) {
SM3Digest sm3 = new SM3Digest();
int len = userId.length * 8;
sm3.update((byte) (len >> 8 & 0xFF));
sm3.update((byte) (len & 0xFF));
sm3.update(userId, 0, userId.length);
byte[] p = SM2Util.byteConvert32Bytes(this.a);
sm3.update(p, 0, p.length);
p = SM2Util.byteConvert32Bytes(this.b);
sm3.update(p, 0, p.length);
p = SM2Util.byteConvert32Bytes(this.gx);
sm3.update(p, 0, p.length);
p = SM2Util.byteConvert32Bytes(this.gy);
sm3.update(p, 0, p.length);
p = SM2Util.byteConvert32Bytes(userKey.normalize().getXCoord().toBigInteger());
sm3.update(p, 0, p.length);
p = SM2Util.byteConvert32Bytes(userKey.normalize().getYCoord().toBigInteger());
sm3.update(p, 0, p.length);
byte[] md = new byte[sm3.getDigestSize()];
sm3.doFinal(md, 0);
return md;
}
/**
* 签名相关值计算
*
* @param md
* @param userD
* @param userKey
* @param sm2Result
*/
public void sm2Sign(byte[] md, BigInteger userD, ECPoint userKey, SM2Result sm2Result) {
BigInteger e = new BigInteger(1, md);
BigInteger k = null;
ECPoint kp = null;
BigInteger r = null;
BigInteger s = null;
do {
do {
// 正式环境
AsymmetricCipherKeyPair keypair = ecc_key_pair_generator.generateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) keypair.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) keypair.getPublic();
k = ecpriv.getD();
kp = ecpub.getQ();
// r
r = e.add(kp.getXCoord().toBigInteger());
r = r.mod(this.n);
} while (r.equals(BigInteger.ZERO) || r.add(k).equals(this.n));
// (1 + dA)~-1
BigInteger da_1 = userD.add(BigInteger.ONE);
da_1 = da_1.modInverse(this.n);
// s
s = r.multiply(userD);
s = k.subtract(s).mod(this.n);
s = da_1.multiply(s).mod(this.n);
} while (s.equals(BigInteger.ZERO));
sm2Result.r = r;
sm2Result.s = s;
}
/**
* 验签
*
* @param md sm3摘要
* @param userKey 根据公钥decode一个ecpoint对象
* @param r 没有特殊含义
* @param s 没有特殊含义
* @param sm2Result 接收参数的对象
*/
public void sm2Verify(byte md[], ECPoint userKey, BigInteger r, BigInteger s, SM2Result sm2Result) {
sm2Result.R = null;
BigInteger e = new BigInteger(1, md);
BigInteger t = r.add(s).mod(this.n);
if (t.equals(BigInteger.ZERO)) {
return;
} else {
ECPoint x1y1 = ecc_point_g.multiply(sm2Result.s);
x1y1 = x1y1.add(userKey.multiply(t));
sm2Result.R = e.add(x1y1.normalize().getXCoord().toBigInteger()).mod(this.n);
return;
}
}
}

View File

@ -0,0 +1,33 @@
package com.hzya.frame.finance.utils.pufabank;
import java.math.BigInteger;
import org.bouncycastle.math.ec.ECPoint;
/**
* SM2
*
*/
public class SM2Result {
public SM2Result() {}
public BigInteger r;// 签名r
public BigInteger s;
public BigInteger R;// 验签R
public byte[] sa;// 密钥交换
public byte[] sb;
public byte[] s1;
public byte[] s2;
public ECPoint keyra;
public ECPoint keyrb;
}

View File

@ -0,0 +1,151 @@
package com.hzya.frame.finance.utils.pufabank;
/**
* SM2签名所计算的值 可以根据实际情况增加删除字段属性
*
*/
public class SM2Sign {
// 16进制的私钥
public String sm2_userd;
// 椭圆曲线点X
public String x_coord;
// 椭圆曲线点Y
public String y_coord;
// SM3摘要Z
public String sm3_z;
// 明文数据16进制
public String sign_express;
// SM3摘要值
public String sm3_digest;
// R
public String sign_r;
// S
public String sign_s;
// R
public String verify_r;
// S
public String verify_s;
// 签名值
public String sm2_sign;
// sign 签名 verfiy验签
public String sm2_type;
// 是否验签成功 true false
public boolean isVerify;
public String getX_coord() {
return x_coord;
}
public void setX_coord(String x_coord) {
this.x_coord = x_coord;
}
public String getY_coord() {
return y_coord;
}
public void setY_coord(String y_coord) {
this.y_coord = y_coord;
}
public String getSm3_z() {
return sm3_z;
}
public void setSm3_z(String sm3_z) {
this.sm3_z = sm3_z;
}
public String getSm3_digest() {
return sm3_digest;
}
public void setSm3_digest(String sm3_digest) {
this.sm3_digest = sm3_digest;
}
public String getSm2_sign() {
return sm2_sign;
}
public void setSm2_sign(String sm2_sign) {
this.sm2_sign = sm2_sign;
}
public String getSign_express() {
return sign_express;
}
public void setSign_express(String sign_express) {
this.sign_express = sign_express;
}
public String getSm2_userd() {
return sm2_userd;
}
public void setSm2_userd(String sm2_userd) {
this.sm2_userd = sm2_userd;
}
public String getSm2_type() {
return sm2_type;
}
public void setSm2_type(String sm2_type) {
this.sm2_type = sm2_type;
}
public boolean isVerify() {
return isVerify;
}
public void setVerify(boolean isVerify) {
this.isVerify = isVerify;
}
public String getSign_r() {
return sign_r;
}
public void setSign_r(String sign_r) {
this.sign_r = sign_r;
}
public String getSign_s() {
return sign_s;
}
public void setSign_s(String sign_s) {
this.sign_s = sign_s;
}
public String getVerify_r() {
return verify_r;
}
public void setVerify_r(String verify_r) {
this.verify_r = verify_r;
}
public String getVerify_s() {
return verify_s;
}
public void setVerify_s(String verify_s) {
this.verify_s = verify_s;
}
}

View File

@ -0,0 +1,132 @@
package com.hzya.frame.finance.utils.pufabank;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.util.Enumeration;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
/**
* 国密算法的签名验签
*
*/
public class SM2SignVerify {
/**
* 默认USERID
*/
public static String USER_ID = "1234567812345678";
/**
* 私钥签名 使用SM3进行对明文数据计算一个摘要值
*
* @param privatekey 私钥
* @param sourceData 明文数据
* @return 签名后的值
* @throws Exception
*/
public static SM2Sign sign(byte[] privatekey, byte[] sourceData) throws Exception {
SM2Sign sm2SignVO = new SM2Sign();
sm2SignVO.setSm2_type("sign");
SM2Factory factory = SM2Factory.getInstance();
BigInteger userD = new BigInteger(1, privatekey);
sm2SignVO.setSm2_userd(userD.toString(16));
ECPoint userKey = factory.ecc_point_g.multiply(userD);
sm2SignVO.setX_coord(userKey.getXCoord().toBigInteger().toString(16));
sm2SignVO.setY_coord(userKey.getYCoord().toBigInteger().toString(16));
SM3Digest sm3Digest = new SM3Digest();
byte[] z = factory.sm2GetZ(USER_ID.getBytes(), userKey);
sm2SignVO.setSm3_z(SM2Util.getHexString(z));
sm2SignVO.setSign_express(SM2Util.getHexString(sourceData));
sm3Digest.update(z, 0, z.length);
sm3Digest.update(sourceData, 0, sourceData.length);
byte[] md = new byte[32];
sm3Digest.doFinal(md, 0);
sm2SignVO.setSm3_digest(SM2Util.getHexString(md));
SM2Result sm2Result = new SM2Result();
factory.sm2Sign(md, userD, userKey, sm2Result);
sm2SignVO.setSign_r(sm2Result.r.toString(16));
sm2SignVO.setSign_s(sm2Result.s.toString(16));
ASN1Integer d_r = new ASN1Integer(sm2Result.r);
ASN1Integer d_s = new ASN1Integer(sm2Result.s);
ASN1EncodableVector v2 = new ASN1EncodableVector();
v2.add(d_r);
v2.add(d_s);
DERSequence sign = new DERSequence(v2);
String result = ByteUtils.toHexString(sign.getEncoded());
sm2SignVO.setSm2_sign(result);
return sm2SignVO;
}
/**
* 验证签名(验签)
*
* @param publicKey 公钥信息
* @param sourceData 密文信息
* @param signData 签名信息
* @return 验签的对象 包含了相关参数和验签结果
*/
@SuppressWarnings("unchecked")
public static SM2Sign validateSign(byte[] publicKey, byte[] sourceData, byte[] signData) {
try {
byte[] formatedPubKey;
SM2Sign verifyVo = new SM2Sign();
verifyVo.setSm2_type("verify");
if (publicKey.length == 64) {
// 添加一字节标识用于ECPoint解析
formatedPubKey = new byte[65];
formatedPubKey[0] = 0x04;
System.arraycopy(publicKey, 0, formatedPubKey, 1, publicKey.length);
} else {
formatedPubKey = publicKey;
}
SM2Factory factory = SM2Factory.getInstance();
ECPoint userKey = factory.ecc_curve.decodePoint(formatedPubKey);
SM3Digest sm3Digest = new SM3Digest();
byte[] z = factory.sm2GetZ(USER_ID.getBytes(), userKey);
verifyVo.setSm3_z(SM2Util.getHexString(z));
sm3Digest.update(z, 0, z.length);
sm3Digest.update(sourceData, 0, sourceData.length);
byte[] md = new byte[32];
sm3Digest.doFinal(md, 0);
verifyVo.setSm3_digest(SM2Util.getHexString(md));
ByteArrayInputStream bis = new ByteArrayInputStream(signData);
ASN1InputStream dis = new ASN1InputStream(bis);
SM2Result sm2Result = null;
ASN1Primitive derObj = dis.readObject();
dis.close();
bis.close();
Enumeration<ASN1Integer> e = ((ASN1Sequence) derObj).getObjects();
BigInteger r = ((ASN1Integer) e.nextElement()).getValue();
BigInteger s = ((ASN1Integer) e.nextElement()).getValue();
sm2Result = new SM2Result();
sm2Result.r = r;
sm2Result.s = s;
verifyVo.setVerify_r(sm2Result.r.toString(16));
verifyVo.setVerify_s(sm2Result.s.toString(16));
factory.sm2Verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result);
boolean verifyFlag = sm2Result.r.equals(sm2Result.R);
verifyVo.setVerify(verifyFlag);
return verifyVo;
} catch (IllegalArgumentException e) {
System.out.println(e);
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -0,0 +1,609 @@
package com.hzya.frame.finance.utils.pufabank;
import java.math.BigInteger;
public class SM2Util {
/**
* 整形转换成网络传输的字节流字节数组型数据
*
* @param num 一个整型数据
* @return 4个字节的自己数组
*/
public static byte[] intToBytes(int num) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (0xff & (num >> 0));
bytes[1] = (byte) (0xff & (num >> 8));
bytes[2] = (byte) (0xff & (num >> 16));
bytes[3] = (byte) (0xff & (num >> 24));
return bytes;
}
/**
* 四个字节的字节数据转换成一个整形数据
*
* @param bytes 4个字节的字节数组
* @return 一个整型数据
*/
public static int byteToInt(byte[] bytes) {
int num = 0;
int temp;
temp = (0x000000ff & (bytes[0])) << 0;
num = num | temp;
temp = (0x000000ff & (bytes[1])) << 8;
num = num | temp;
temp = (0x000000ff & (bytes[2])) << 16;
num = num | temp;
temp = (0x000000ff & (bytes[3])) << 24;
num = num | temp;
return num;
}
/**
* 长整形转换成网络传输的字节流字节数组型数据
*
* @param num 一个长整型数据
* @return 4个字节的自己数组
*/
public static byte[] longToBytes(long num) {
byte[] bytes = new byte[8];
for (int i = 0; i < 8; i++) {
bytes[i] = (byte) (0xff & (num >> (i * 8)));
}
return bytes;
}
/**
* 大数字转换字节流字节数组型数据
*
* @param n
* @return
*/
public static byte[] byteConvert32Bytes(BigInteger n) {
byte tmpd[] = (byte[]) null;
if (n == null) {
return null;
}
if (n.toByteArray().length == 33) {
tmpd = new byte[32];
System.arraycopy(n.toByteArray(), 1, tmpd, 0, 32);
} else if (n.toByteArray().length == 32) {
tmpd = n.toByteArray();
} else {
tmpd = new byte[32];
for (int i = 0; i < 32 - n.toByteArray().length; i++) {
tmpd[i] = 0;
}
System.arraycopy(n.toByteArray(), 0, tmpd, 32 - n.toByteArray().length, n.toByteArray().length);
}
return tmpd;
}
/**
* 换字节流字节数组型数据转大数字
*
* @param b
* @return
*/
public static BigInteger byteConvertInteger(byte[] b) {
if (b[0] < 0) {
byte[] temp = new byte[b.length + 1];
temp[0] = 0;
System.arraycopy(b, 0, temp, 1, b.length);
return new BigInteger(temp);
}
return new BigInteger(b);
}
/**
* 根据字节数组获得值(十六进制数字)
*
* @param bytes
* @return
*/
public static String getHexString(byte[] bytes) {
return getHexString(bytes, true);
}
/**
* 根据字节数组获得值(十六进制数字)
*
* @param bytes
* @param upperCase
* @return
*/
public static String getHexString(byte[] bytes, boolean upperCase) {
String ret = "";
for (int i = 0; i < bytes.length; i++) {
ret += Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1);
}
return upperCase ? ret.toUpperCase() : ret;
}
/**
* 打印十六进制字符串
*
* @param bytes
*/
public static void printHexString(byte[] bytes) {
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
}
}
/**
* Convert hex string to byte[]
*
* @param hexString the hex string
* @return byte[]
*/
public static byte[] hexStringToBytes(String hexString) {
if (hexString == null || hexString.equals("")) {
return null;
}
hexString = hexString.toUpperCase();
int length = hexString.length() / 2;
char[] hexChars = hexString.toCharArray();
byte[] d = new byte[length];
for (int i = 0; i < length; i++) {
int pos = i * 2;
d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
}
return d;
}
/**
* Convert char to byte
*
* @param c char
* @return byte
*/
public static byte charToByte(char c) {
return (byte) "0123456789ABCDEF".indexOf(c);
}
/**
* 用于建立十六进制字符的输出的小写字符数组
*/
private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f' };
/**
* 用于建立十六进制字符的输出的大写字符数组
*/
private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F' };
/**
* 将字节数组转换为十六进制字符数组
*
* @param data byte[]
* @return 十六进制char[]
*/
public static char[] encodeHex(byte[] data) {
return encodeHex(data, true);
}
/**
* 将字节数组转换为十六进制字符数组
*
* @param data byte[]
* @param toLowerCase <code>true</code> 传换成小写格式 <code>false</code> 传换成大写格式
* @return 十六进制char[]
*/
public static char[] encodeHex(byte[] data, boolean toLowerCase) {
return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
/**
* 将字节数组转换为十六进制字符数组
*
* @param data byte[]
* @param toDigits 用于控制输出的char[]
* @return 十六进制char[]
*/
protected static char[] encodeHex(byte[] data, char[] toDigits) {
int l = data.length;
char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
out[j++] = toDigits[0x0F & data[i]];
}
return out;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data byte[]
* @return 十六进制String
*/
public static String encodeHexString(byte[] data) {
return encodeHexString(data, true);
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data byte[]
* @param toLowerCase <code>true</code> 传换成小写格式 <code>false</code> 传换成大写格式
* @return 十六进制String
*/
public static String encodeHexString(byte[] data, boolean toLowerCase) {
return encodeHexString(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
}
/**
* 将字节数组转换为十六进制字符串
*
* @param data byte[]
* @param toDigits 用于控制输出的char[]
* @return 十六进制String
*/
protected static String encodeHexString(byte[] data, char[] toDigits) {
return new String(encodeHex(data, toDigits));
}
/**
* 将十六进制字符数组转换为字节数组
*
* @param data 十六进制char[]
* @return byte[]
* @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度将抛出运行时异常
*/
public static byte[] decodeHex(char[] data) {
int len = data.length;
if ((len & 0x01) != 0) {
throw new RuntimeException("Odd number of characters.");
}
byte[] out = new byte[len >> 1];
// two characters form the hex value.
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f = f | toDigit(data[j], j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
/**
* 将十六进制字符转换成一个整数
*
* @param ch 十六进制char
* @param index 十六进制字符在字符数组中的位置
* @return 一个整数
* @throws RuntimeException 当ch不是一个合法的十六进制字符时抛出运行时异常
*/
protected static int toDigit(char ch, int index) {
int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index);
}
return digit;
}
/**
* 数字字符串转ASCII码字符串
*
* @param String 字符串
* @return ASCII字符串
*/
public static String StringToAsciiString(String content) {
String result = "";
int max = content.length();
for (int i = 0; i < max; i++) {
char c = content.charAt(i);
String b = Integer.toHexString(c);
result = result + b;
}
return result;
}
/**
* 十六进制转字符串
*
* @param hexString 十六进制字符串
* @param encodeType 编码类型4Unicode2普通编码
* @return 字符串
*/
public static String hexStringToString(String hexString, int encodeType) {
String result = "";
int max = hexString.length() / encodeType;
for (int i = 0; i < max; i++) {
char c = (char) hexStringToAlgorism(hexString.substring(i * encodeType, (i + 1) * encodeType));
result += c;
}
return result;
}
/**
* 十六进制字符串装十进制
*
* @param hex 十六进制字符串
* @return 十进制数值
*/
public static int hexStringToAlgorism(String hex) {
hex = hex.toUpperCase();
int max = hex.length();
int result = 0;
for (int i = max; i > 0; i--) {
char c = hex.charAt(i - 1);
int algorism = 0;
if (c >= '0' && c <= '9') {
algorism = c - '0';
} else {
algorism = c - 55;
}
result += Math.pow(16, max - i) * algorism;
}
return result;
}
/**
* 十六转二进制
*
* @param hex 十六进制字符串
* @return 二进制字符串
*/
public static String hexStringToBinary(String hex) {
hex = hex.toUpperCase();
String result = "";
int max = hex.length();
for (int i = 0; i < max; i++) {
char c = hex.charAt(i);
switch (c) {
case '0':
result += "0000";
break;
case '1':
result += "0001";
break;
case '2':
result += "0010";
break;
case '3':
result += "0011";
break;
case '4':
result += "0100";
break;
case '5':
result += "0101";
break;
case '6':
result += "0110";
break;
case '7':
result += "0111";
break;
case '8':
result += "1000";
break;
case '9':
result += "1001";
break;
case 'A':
result += "1010";
break;
case 'B':
result += "1011";
break;
case 'C':
result += "1100";
break;
case 'D':
result += "1101";
break;
case 'E':
result += "1110";
break;
case 'F':
result += "1111";
break;
}
}
return result;
}
/**
* ASCII码字符串转数字字符串
*
* @param String ASCII字符串
* @return 字符串
*/
public static String AsciiStringToString(String content) {
String result = "";
int length = content.length() / 2;
for (int i = 0; i < length; i++) {
String c = content.substring(i * 2, i * 2 + 2);
int a = hexStringToAlgorism(c);
char b = (char) a;
String d = String.valueOf(b);
result += d;
}
return result;
}
/**
* 将十进制转换为指定长度的十六进制字符串
*
* @param algorism int 十进制数字
* @param maxLength int 转换后的十六进制字符串长度
* @return String 转换后的十六进制字符串
*/
public static String algorismToHexString(int algorism, int maxLength) {
String result = "";
result = Integer.toHexString(algorism);
if (result.length() % 2 == 1) {
result = "0" + result;
}
return patchHexString(result.toUpperCase(), maxLength);
}
/**
* 字节数组转为普通字符串ASCII对应的字符
*
* @param bytearray byte[]
* @return String
*/
public static String byteToString(byte[] bytearray) {
String result = "";
char temp;
int length = bytearray.length;
for (int i = 0; i < length; i++) {
temp = (char) bytearray[i];
result += temp;
}
return result;
}
/**
* 二进制字符串转十进制
*
* @param binary 二进制字符串
* @return 十进制数值
*/
public static int binaryToAlgorism(String binary) {
int max = binary.length();
int result = 0;
for (int i = max; i > 0; i--) {
char c = binary.charAt(i - 1);
int algorism = c - '0';
result += Math.pow(2, max - i) * algorism;
}
return result;
}
/**
* 十进制转换为十六进制字符串
*
* @param algorism int 十进制的数字
* @return String 对应的十六进制字符串
*/
public static String algorismToHEXString(int algorism) {
String result = "";
result = Integer.toHexString(algorism);
if (result.length() % 2 == 1) {
result = "0" + result;
}
result = result.toUpperCase();
return result;
}
/**
* HEX字符串前补0主要用于长度位数不足
*
* @param str String 需要补充长度的十六进制字符串
* @param maxLength int 补充后十六进制字符串的长度
* @return 补充结果
*/
static public String patchHexString(String str, int maxLength) {
String temp = "";
for (int i = 0; i < maxLength - str.length(); i++) {
temp = "0" + temp;
}
str = (temp + str).substring(0, maxLength);
return str;
}
/**
* 将一个字符串转换为int
*
* @param s String 要转换的字符串
* @param defaultInt int 如果出现异常,默认返回的数字
* @param radix int 要转换的字符串是什么进制的,如16 8 10.
* @return int 转换后的数字
*/
public static int parseToInt(String s, int defaultInt, int radix) {
int i = 0;
try {
i = Integer.parseInt(s, radix);
} catch (NumberFormatException ex) {
i = defaultInt;
}
return i;
}
/**
* 将一个十进制形式的数字字符串转换为int
*
* @param s String 要转换的字符串
* @param defaultInt int 如果出现异常,默认返回的数字
* @return int 转换后的数字
*/
public static int parseToInt(String s, int defaultInt) {
int i = 0;
try {
i = Integer.parseInt(s);
} catch (NumberFormatException ex) {
i = defaultInt;
}
return i;
}
/**
* 十六进制串转化为byte数组
*
* @return the array of byte
*/
public static byte[] hexToByte(String hex) throws IllegalArgumentException {
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException();
}
char[] arr = hex.toCharArray();
byte[] b = new byte[hex.length() / 2];
for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
String swap = "" + arr[i++] + arr[i];
int byteint = Integer.parseInt(swap, 16) & 0xFF;
b[j] = new Integer(byteint).byteValue();
}
return b;
}
/**
* 字节数组转换为十六进制字符串
*
* @param b byte[] 需要转换的字节数组
* @return String 十六进制字符串
*/
public static String byteToHex(byte b[]) {
if (b == null) {
throw new IllegalArgumentException("Argument b ( byte array ) is null! ");
}
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0xff);
if (stmp.length() == 1) {
hs = hs + "0" + stmp;
} else {
hs = hs + stmp;
}
}
return hs.toUpperCase();
}
public static byte[] subByte(byte[] input, int startIndex, int length) {
byte[] bt = new byte[length];
for (int i = 0; i < length; i++) {
bt[i] = input[i + startIndex];
}
return bt;
}
}

View File

@ -0,0 +1,258 @@
package com.hzya.frame.finance.utils.pufabank;
/**
* SM3
*
*/
public class SM3 {
public static final byte[] iv = { 0x73, (byte) 0x80, 0x16, 0x6f, 0x49, 0x14, (byte) 0xb2, (byte) 0xb9,
0x17, 0x24, 0x42, (byte) 0xd7, (byte) 0xda, (byte) 0x8a, 0x06, 0x00, (byte) 0xa9, 0x6f, 0x30,
(byte) 0xbc, (byte) 0x16, 0x31, 0x38, (byte) 0xaa, (byte) 0xe3, (byte) 0x8d, (byte) 0xee, 0x4d,
(byte) 0xb0, (byte) 0xfb, 0x0e, 0x4e };
public static int[] Tj = new int[64];
static {
for (int i = 0; i < 16; i++) {
Tj[i] = 0x79cc4519;
}
for (int i = 16; i < 64; i++) {
Tj[i] = 0x7a879d8a;
}
}
public static byte[] CF(byte[] V, byte[] B) {
int[] v, b;
v = convert(V);
b = convert(B);
return convert(CF(v, b));
}
private static int[] convert(byte[] arr) {
int[] out = new int[arr.length / 4];
byte[] tmp = new byte[4];
for (int i = 0; i < arr.length; i += 4) {
System.arraycopy(arr, i, tmp, 0, 4);
out[i / 4] = bigEndianByteToInt(tmp);
}
return out;
}
private static byte[] convert(int[] arr) {
byte[] out = new byte[arr.length * 4];
byte[] tmp = null;
for (int i = 0; i < arr.length; i++) {
tmp = bigEndianIntToByte(arr[i]);
System.arraycopy(tmp, 0, out, i * 4, 4);
}
return out;
}
public static int[] CF(int[] V, int[] B) {
int a, b, c, d, e, f, g, h;
int ss1, ss2, tt1, tt2;
a = V[0];
b = V[1];
c = V[2];
d = V[3];
e = V[4];
f = V[5];
g = V[6];
h = V[7];
int[][] arr = expand(B);
int[] w = arr[0];
int[] w1 = arr[1];
for (int j = 0; j < 64; j++) {
ss1 = (bitCycleLeft(a, 12) + e + bitCycleLeft(Tj[j], j));
ss1 = bitCycleLeft(ss1, 7);
ss2 = ss1 ^ bitCycleLeft(a, 12);
tt1 = FFj(a, b, c, j) + d + ss2 + w1[j];
tt2 = GGj(e, f, g, j) + h + ss1 + w[j];
d = c;
c = bitCycleLeft(b, 9);
b = a;
a = tt1;
h = g;
g = bitCycleLeft(f, 19);
f = e;
e = P0(tt2);
}
int[] out = new int[8];
out[0] = a ^ V[0];
out[1] = b ^ V[1];
out[2] = c ^ V[2];
out[3] = d ^ V[3];
out[4] = e ^ V[4];
out[5] = f ^ V[5];
out[6] = g ^ V[6];
out[7] = h ^ V[7];
return out;
}
private static int[][] expand(int[] B) {
int W[] = new int[68];
int W1[] = new int[64];
for (int i = 0; i < B.length; i++) {
W[i] = B[i];
}
for (int i = 16; i < 68; i++) {
W[i] = P1(W[i - 16] ^ W[i - 9] ^ bitCycleLeft(W[i - 3], 15)) ^ bitCycleLeft(W[i - 13], 7)
^ W[i - 6];
}
for (int i = 0; i < 64; i++) {
W1[i] = W[i] ^ W[i + 4];
}
int arr[][] = new int[][] { W, W1 };
return arr;
}
private static byte[] bigEndianIntToByte(int num) {
return back(SM2Util.intToBytes(num));
}
private static int bigEndianByteToInt(byte[] bytes) {
return SM2Util.byteToInt(back(bytes));
}
private static int FFj(int X, int Y, int Z, int j) {
if (j >= 0 && j <= 15) {
return FF1j(X, Y, Z);
} else {
return FF2j(X, Y, Z);
}
}
private static int GGj(int X, int Y, int Z, int j) {
if (j >= 0 && j <= 15) {
return GG1j(X, Y, Z);
} else {
return GG2j(X, Y, Z);
}
}
// 逻辑位运算函数
private static int FF1j(int X, int Y, int Z) {
int tmp = X ^ Y ^ Z;
return tmp;
}
private static int FF2j(int X, int Y, int Z) {
int tmp = ((X & Y) | (X & Z) | (Y & Z));
return tmp;
}
private static int GG1j(int X, int Y, int Z) {
int tmp = X ^ Y ^ Z;
return tmp;
}
private static int GG2j(int X, int Y, int Z) {
int tmp = (X & Y) | (~X & Z);
return tmp;
}
private static int P0(int X) {
int y = rotateLeft(X, 9);
y = bitCycleLeft(X, 9);
int z = rotateLeft(X, 17);
z = bitCycleLeft(X, 17);
int t = X ^ y ^ z;
return t;
}
private static int P1(int X) {
int t = X ^ bitCycleLeft(X, 15) ^ bitCycleLeft(X, 23);
return t;
}
/**
* 对最后一个分组字节数据padding
*
* @param in
* @param bLen 分组个数
* @return
*/
public static byte[] padding(byte[] in, int bLen) {
int k = 448 - (8 * in.length + 1) % 512;
if (k < 0) {
k = 960 - (8 * in.length + 1) % 512;
}
k += 1;
byte[] padd = new byte[k / 8];
padd[0] = (byte) 0x80;
long n = in.length * 8 + bLen * 512;
byte[] out = new byte[in.length + k / 8 + 64 / 8];
int pos = 0;
System.arraycopy(in, 0, out, 0, in.length);
pos += in.length;
System.arraycopy(padd, 0, out, pos, padd.length);
pos += padd.length;
byte[] tmp = back(SM2Util.longToBytes(n));
System.arraycopy(tmp, 0, out, pos, tmp.length);
return out;
}
/**
* 字节数组逆序
*
* @param in
* @return
*/
private static byte[] back(byte[] in) {
byte[] out = new byte[in.length];
for (int i = 0; i < out.length; i++) {
out[i] = in[out.length - i - 1];
}
return out;
}
public static int rotateLeft(int x, int n) {
return (x << n) | (x >> (32 - n));
}
private static int bitCycleLeft(int n, int bitLen) {
bitLen %= 32;
byte[] tmp = bigEndianIntToByte(n);
int byteLen = bitLen / 8;
int len = bitLen % 8;
if (byteLen > 0) {
tmp = byteCycleLeft(tmp, byteLen);
}
if (len > 0) {
tmp = bitSmall8CycleLeft(tmp, len);
}
return bigEndianByteToInt(tmp);
}
private static byte[] bitSmall8CycleLeft(byte[] in, int len) {
byte[] tmp = new byte[in.length];
int t1, t2, t3;
for (int i = 0; i < tmp.length; i++) {
t1 = (byte) ((in[i] & 0x000000ff) << len);
t2 = (byte) ((in[(i + 1) % tmp.length] & 0x000000ff) >> (8 - len));
t3 = (byte) (t1 | t2);
tmp[i] = (byte) t3;
}
return tmp;
}
private static byte[] byteCycleLeft(byte[] in, int byteLen) {
byte[] tmp = new byte[in.length];
System.arraycopy(in, byteLen, tmp, 0, in.length - byteLen);
System.arraycopy(in, 0, tmp, in.length - byteLen, byteLen);
return tmp;
}
}

View File

@ -0,0 +1,128 @@
package com.hzya.frame.finance.utils.pufabank;
import org.bouncycastle.util.encoders.Hex;
public class SM3Digest {
/** SM3值的长度 */
private static final int BYTE_LENGTH = 32;
/** SM3分组长度 */
private static final int BLOCK_LENGTH = 64;
/** 缓冲区长度 */
private static final int BUFFER_LENGTH = BLOCK_LENGTH * 1;
/** 缓冲区 */
private byte[] xBuf = new byte[BUFFER_LENGTH];
/** 缓冲区偏移量 */
private int xBufOff;
/** 初始向量 */
private byte[] V = SM3.iv.clone();
private int cntBlock = 0;
public SM3Digest() {
}
public SM3Digest(SM3Digest t) {
System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length);
this.xBufOff = t.xBufOff;
System.arraycopy(t.V, 0, this.V, 0, t.V.length);
}
/**
* SM3结果输出
*
* @param out 保存SM3结构的缓冲区
* @param outOff 缓冲区偏移量
* @return
*/
public int doFinal(byte[] out, int outOff) {
byte[] tmp = doFinal();
System.arraycopy(tmp, 0, out, 0, tmp.length);
return BYTE_LENGTH;
}
public void reset() {
xBufOff = 0;
cntBlock = 0;
V = SM3.iv.clone();
}
/**
* 明文输入
*
* @param in 明文输入缓冲区
* @param inOff 缓冲区偏移量
* @param len 明文长度
*/
public void update(byte[] in, int inOff, int len) {
int partLen = BUFFER_LENGTH - xBufOff;
int inputLen = len;
int dPos = inOff;
if (partLen < inputLen) {
System.arraycopy(in, dPos, xBuf, xBufOff, partLen);
inputLen -= partLen;
dPos += partLen;
doUpdate();
while (inputLen > BUFFER_LENGTH) {
System.arraycopy(in, dPos, xBuf, 0, BUFFER_LENGTH);
inputLen -= BUFFER_LENGTH;
dPos += BUFFER_LENGTH;
doUpdate();
}
}
System.arraycopy(in, dPos, xBuf, xBufOff, inputLen);
xBufOff += inputLen;
}
private void doUpdate() {
byte[] B = new byte[BLOCK_LENGTH];
for (int i = 0; i < BUFFER_LENGTH; i += BLOCK_LENGTH) {
System.arraycopy(xBuf, i, B, 0, B.length);
doHash(B);
}
xBufOff = 0;
}
private void doHash(byte[] B) {
byte[] tmp = SM3.CF(V, B);
System.arraycopy(tmp, 0, V, 0, V.length);
cntBlock++;
}
private byte[] doFinal() {
byte[] B = new byte[BLOCK_LENGTH];
byte[] buffer = new byte[xBufOff];
System.arraycopy(xBuf, 0, buffer, 0, buffer.length);
byte[] tmp = SM3.padding(buffer, cntBlock);
for (int i = 0; i < tmp.length; i += BLOCK_LENGTH) {
System.arraycopy(tmp, i, B, 0, B.length);
doHash(B);
}
return V;
}
public void update(byte in) {
byte[] buffer = new byte[] { in };
update(buffer, 0, 1);
}
public int getDigestSize() {
return BYTE_LENGTH;
}
public static void main(String[] args) {
byte[] md = new byte[32];
byte[] msg1 = "ererfeiisgod".getBytes();
SM3Digest sm3 = new SM3Digest();
sm3.update(msg1, 0, msg1.length);
sm3.doFinal(md, 0);
String s = new String(Hex.encode(md));
System.out.println(s);
}
}

View File

@ -0,0 +1,171 @@
package com.hzya.frame.finance.utils.pufabank;
import okhttp3.*;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import javax.xml.bind.DatatypeConverter;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;
public class SPDBSMEncryptor {
/*ClientID和密钥X-SPDB-ClientIDX-SPDB-ClientID-Secret
* 浦发API开发平台上创建APP给您邮件返回的或我们提供给您测试使用的
* 生产请使用API开发平台上创建APP给您邮件返回的生产环境密钥
*/
private static final String clientId = "f42d86e9-debd-4423-84af-74549a51c7c2";
//加解密样例仅供参考秘钥有过期时间建议在生产环境不要将秘钥硬编码在代码中
private static final String secret = "NmZkMC00ZDMpLWFyYzgtN2JwN2ZmY2J1NmF4MC40NzQ0ODA5MzA2MzE4OTg2NzAu";
/*
*请求头ContentType,一般为application/json,API对外接口文档中有报文体格式
*图文上传接口为form-data,如果是图文信息上传,请参考国密普通验签图片上传接口测试工程.zip
*/
private static final String contentType = "application/json;charset=utf-8";
/*您的私钥测试环境中请将您对应的公钥请提供给我们我们需要在门户配置否则无法通过验签
* 公私钥生成可进入本项目下nodejs文件夹下运行 node getKeyPaireHex.js 即可生成公私密钥
* 加解密样例仅供参考秘钥有过期时间建议在生产环境不要将秘钥硬编码在代码中
*/
private static final String privatekey = "ae14b3717d6b44b55e500a807248aab425e1d1677df1718410c453f3fe257541";
/*
*浦发公钥在用户中心我的APP开发者公钥中查看
*加解密样例仅供参考秘钥有过期时间建议在生产环境不要将秘钥硬编码在代码中
*/
private static final String spdb_publicKey = "04e63345389de1dbc822881f80c2a82e95f638dd9881ebea8bb63a00ba5580559f77db5cc08adc087428fdbab81fe4dc580d48735cd111bdc9ae898f7b57628f09";
/**
* 请求编码格式
*/
private static final String charset = "UTF-8";
private static final OkHttpClient client = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(500, 5, TimeUnit.MINUTES)).connectTimeout(10000, TimeUnit.MILLISECONDS)
.readTimeout(60000, TimeUnit.MILLISECONDS).build();
/**
* 获取防重放参数
* @param forbidden 是否需使用防重放参数
* @param data 报文体
* @return
*/
public static String getNonceParam( boolean forbidden ,String data){
//防重放参数毫秒级的时间戳与浦发服务器北京时间时间误差不能超过15分钟样例1543461367533
long timestamp = 0L;
//防重放参数X-SPDB-Nonce为交易序号需保证唯一样例TRANS10145581
String nonce = "";
/*
* 防重放参数
* 如需使用防重放参数需要对时间戳防重放参数加签
*/
String newBodyData = data;
if (forbidden) {//如需使用防重放参数
//对应请求报文头参数 X-SPDB-Timestamp
timestamp = System.currentTimeMillis();// X-SPDB-Timestamp
//对应请求报文头参数 X-SPDB-Nonce
nonce = "TRAN10145581";// X-SPDB-Nonce
//注意签名原文需要拼接防重放参数 {"name":"zhangshan"}1591077686410TRAN10145581
newBodyData = data + timestamp + nonce;
System.out.println("签名原文:" + newBodyData);
}
return newBodyData;
}
public static Request createRequest(String requestMethod,String url,String data,String signature){
Request request =null;
String encrypted = SM2Cryptor.encrypt(secret, data);
if("GET".equals(requestMethod) || "get".equals(requestMethod)){
url = url + "?encryptBody=" + encrypted;
request = new Request.Builder().url(url).get()
.addHeader("Content-Type", contentType) //Content-Type
.addHeader("X-SPDB-Client-ID", clientId) //Client-ID
.addHeader("X-SPDB-SIGNATURE", signature) //签名结果
.addHeader("X-SPDB-SM", "true") //使用国密
.addHeader("X-SPDB-Encryption", "true") //使用全报文加密
//X-SPDB-TimestampX-SPDB-Nonce两个参数为防重放参数不需要时不传
//.addHeader("X-SPDB-Timestamp", String.valueOf(timestamp)) //防重放参数-毫秒级时间戳
//.addHeader("X-SPDB-Nonce", nonce) //防重放参数
.build();
}else if("POST".equals(requestMethod) || "post".equals(requestMethod)){
//发送HTTP请求
MediaType mediaType = MediaType.parse(contentType);
// 数据加密-使用国密SM2算法加密 注意加密数据不添加防重放参数
RequestBody body = RequestBody.create(mediaType, encrypted);
request = new Request.Builder().url(url).post(body)
.addHeader("Content-Type", contentType) //Content-Type
.addHeader("X-SPDB-Client-ID", clientId) //Client-ID
.addHeader("X-SPDB-SIGNATURE", signature) //签名结果
.addHeader("X-SPDB-SM", "true") //使用国密
.addHeader("X-SPDB-Encryption", "true") //使用全报文加密
//X-SPDB-TimestampX-SPDB-Nonce两个参数为防重放参数不需要时不传
//.addHeader("X-SPDB-Timestamp", String.valueOf(timestamp)) //防重放参数-毫秒级时间戳
//.addHeader("X-SPDB-Nonce", nonce) //防重放参数
.build();
}
return request;
}
public static void main(String[] args) throws Exception {
try {
/**
* 请求的URL,请使用对API外接口文档中提供的URL
*/
String url = "https://etest4.spdb.com.cn/spdb/uat/api/corporateAccounts/banks/transDetails";
/*
* 请求报文体 {"name":"zhangshan"} 注意换行符和空格
* 如果是图文信息上传请参考国密普通验签图片上传接口测试工程.zip
*/
String data = "{\"clientNo\": \"2678987519\",\"pyAcctNo\": \"88010078801000025122\",\"startDate\":\"20250801\",\"endDate\": \"20250831\",\"acptNum\": \"1\",\"queryNum\": \"10\"}";
System.out.println("原报文体:" + data);
/**
* 是否获取防重放参数
*/
String newBodyData = getNonceParam(false, data);
// 数据加密-使用国密SM2算法加密 注意加密数据不添加防重放参数
String encrypted = SM2Cryptor.encrypt(secret, data);
System.out.println("加密报文体:" + encrypted);
// 数据解密
String decrypted = SM2Cryptor.decrypt(secret, encrypted);
System.out.println("解密报文体:" + decrypted);
byte[] dataBytes = newBodyData.getBytes(charset);
SM2Sign sign = SM2SignVerify.sign(ByteUtils.fromHexString(privatekey), dataBytes);
byte[] signBytes = sign.getSm2_sign().getBytes(charset);
String signature = DatatypeConverter.printBase64Binary(signBytes);
System.out.println("signature:" + signature);
//发送HTTP请求
Request request = createRequest("post", url, data, signature);
Response response = client.newCall(request).execute();
String resBody = new String(response.body().bytes(), "UTF-8");
System.out.println("加密响应报文:" + resBody);
String decryptBody = SM2Cryptor.decrypt(secret, resBody);
System.out.println("明文响应报文:" + decryptBody);
String sg = new String(response.header("X-SPDB-SIGNATURE").getBytes(), "UTF-8");
String digest = SHA1.digest(decryptBody);
byte[] bytes = digest.getBytes(charset);
byte[] signatureBytes = ByteUtils.fromHexString(new String(DatatypeConverter.parseBase64Binary(sg)));
SM2Sign sm2Sign = SM2SignVerify.validateSign(ByteUtils.fromHexString(spdb_publicKey), bytes, signatureBytes);
boolean validateSign = sm2Sign.isVerify();
System.out.println("验签结果:" + validateSign);
}catch (Exception e){
e.printStackTrace();
}
}
}

View File

@ -36,7 +36,9 @@ public class MdmDBQueryVO extends BaseEntity {
private String pkentityorg; private String pkentityorg;
private String billstatus; private String billstatus;
private String claimstatus; private String claimstatus;
private String id;
private String ids; private String ids;
private String outFlag; private String outFlag;
private String billDate;
} }