
SM4、SM2加密算法
简介
国家密码局认定的国产密码算法主要有:SM1,SM2,SM3,SM4。
密钥长度和分组长度均为128位。
SM1:对称加密,强度与AES相当,算法不公开,调用此算法需要通过加密芯片的接口进行调用。
SM2:非对称加密,基于ECC,算法已经公开,由于该算法基于ECC,故其签名速度与密钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度比RSA块。
SM3:信息摘要,可以用MD5作为对比理解。算法已公开。校验结果256位。
由于SM1、SM4加解密的分组大小为128bit,故对消息进行加解密时,若消息长度过长,需要进行分组,要消息长度不足,则要进行填充。
补充:
当使用特定的芯片进行SM1或其他国密算法加密时,若用多个线程调用加密卡的API时,要考虑芯片对于多线程的支持情况。
国产密码算法(国密算法)是指国家密码局认定的国产商用密码算法,在金融领域目前主要使用公开的SM2、SM3、SM4三类算法,分别是非对称算法、哈希算法和对称算法。
国产密码算法(国密算法)是指国家密码局认定的国产商用密码算法,在金融领域目前主要使用公开的SM2、SM3、SM4三类算法,分别是非对称算法、哈希算法和对称算法。
-
SM2算法:SM2椭圆曲线公钥密码算法是我国自主设计的公钥密码算法,包括SM2-1椭圆曲线数字签名算法,SM2-2椭圆曲线密钥交换协议,SM2-3椭圆曲线公钥加密算法,分别用于实现数字签名密钥协商和数据加密等功能。SM2算法与RSA算法不同的是,SM2算法是基于椭圆曲线上点群离散对数难题,相对于RSA算法,256位的SM2密码强度已经比2048位的RSA密码强度要高。
-
SM3算法:SM3杂凑算法是我国自主设计的密码杂凑算法,适用于商用密码应用中的数字签名和验证消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。为了保证杂凑算法的安全性,其产生的杂凑值的长度不应太短,例如MD5输出128比特杂凑值,输出长度太短,影响其安全性SHA-1算法的输出长度为160比特,SM3算法的输出长度为256比特,因此SM3算法的安全性要高于MD5算法和SHA-1算法。
-
SM4算法:SM4分组密码算法是我国自主设计的分组对称密码算法,用于实现数据的加密/解密运算,以保证数据和信息的机密性。要保证一个对称密码算法的安全性的基本条件是其具备足够的密钥长度,SM4算法与AES算法具有相同的密钥长度分组长度128比特,因此在安全性上高于3DES算法。
使用
背景
广发金融平台使用国密SM2算法和SM4以及MD5保证报文在互联网环境下的传输安全。
简述
简单来说,使用SM2算法加密报文得到签名,使用SM4算法和SM4密钥字符串加密报文,使用SM2算法加密密钥字符串。
广发平台还使用MD5算法对自身SM2公钥加密。
将上述所有信息添加到报文头中,密钥字符串加密后的为报问题。
POST http://xxx.xxx.xxx.xxx/xxx/xxx HTTP/1.1
charset: utf-8
Proxy-Connection: Keep-Alive
Content-Type: application/json;charset=UTF-8
Host: xxx.xxx.xxx:80
Connection: keep-alive
Content-Length: 664
appId: 01202005220000010001
signature:
MEYCIQDM3xsd/kwuFWOWwA9o51mCAFf0ujG4cVekKKQPg7kSYwIhALPHUCAmj4eTbJfic+fVobrS
gO3oAhRG0bx4cCGVv1rK
signType: SM2
certId: 8DAEC5885489208FCBFA4A2E87F66F13
encryptKey:
MHkCIF09tdkwCs3KmheGNGl0CUt30ridWdzgG/CgiTnibLe1AiEAhiH4UdMurj+/TWqSFEvQdN0VvF
YpKtIjqGL/pIWwUj4EII5XpKdzOLD0S+s94GjpTS0643rRqgKQwvrVX4ViAA9SBBANSmmEtyZ7yY34V
UbDewQP
encryptType: SM4
BCu4+lZelyzECO9DRW/8fmbhb1t1Vpb+jirbRute/TL6f9g4jgm1sASGx2/A9p039XpdQSguy7eBisnFc
dgr5LMAgqxlgNnMb+5Z4PaxG4FZtdLnlqzBfH8ckhp4rFQxQelNzjje/UcD2L0NKZ2NcLEtK/yo0dsL6o
2bmp5+IljIl1Be0JMAjzOLoYooZcQLHrJh643pX8uOM2a0gZPxSlUWPsW5YC4DZk5qG3z8Bjf4rSoNw
FWm2RRPGOg3bBgZdpjbar7olz9ezboVca4dqqQqHWt7fXxaYZXQtOhNcTyX467H4WHcg7e7ZYly1
urAhRATdbQi71x6yEcpPwJv6SabO7pCX5d3rbJ1JR6f0k+nUame+fY5hxS8r3tR5WcIaj88CWr/y+IQYl
4DjEE7xaNj/JdEVQciwRu1UeC0W4oF2JOPwp9tu8tN89Q4dAzfckUJjJ9P8so3Y9VV8BCgOZRqJFjspR
jjY+m7A/VRDMnF0A7G5fUJEY0/4u3cNyeIvsVd15Xp5LjgEYkQIah6VgQsiuk7nwtXHqcq8mZ/N8/hS
3
R2u490ZUoQgdVlIwRreEKSsMDeafYH4aY92CGFhyRi+hRikshi2bqfPJ6Zh0TgoXceTHML5tPhs30m3
KBv2bR/Qccf3thpDcvHPupcxHw==
具体步骤
- 将请求报文转换成json字符串,获得报文
- 使用随机算法生成16位密钥字符串(后续使用SM4算法加密)
- 使用自身私钥和SM2算法对第一步生成的报文字符串加密获得签名
- 使用第二步生成的密钥字符串和SM4加密算法对第一步生成的报文进行加密
- 使用广发公钥和SM2算法对第二步生成的密钥字符串进行加密
- 使用MD5算法对自身私钥进行加密
工具
根据广发demo中工具类修改(异常信息描述)
public class KeyUtil {
private final CopsPropertyConfigure config;
/**
* 获取通知请求接口源文
*
* @param reqStr
* @param signature 签名字符串
* @param encryptKey 加密后的 SM4 密钥串
* @return
* @throws Exception
*/
public CopsResponseEntity getDecryptReqStr(String reqStr, String signature, String encryptKey, String type) throws Exception {
// 对报文加密密钥解密,使用商户私钥进行解密
String keyToEncry = new String(SM2Util.decrypt(Base64.getDecoder().decode(config.getPrivateKey()), Base64.getDecoder().decode(encryptKey)), config.getEncoding());
//log.info("req encrypteKey decrypting:{}", keyToEncry);
//解密开放平台请求报文
String reqDecryptStr = new String(SM4Util.decryptCBC(Base64.getDecoder().decode(reqStr), keyToEncry.getBytes(), keyToEncry.getBytes()), config.getEncoding());
//请求报文验签
boolean veriyfyResult;
switch (type) {
case "cgb":
veriyfyResult = SM2Util.verifySign(Base64.getDecoder().decode(config.getCgbPublicKey()), reqDecryptStr.getBytes(config.getEncoding()), Base64.getDecoder().decode(signature));
break;
case "xx":
veriyfyResult = SM2Util.verifySign(Base64.getDecoder().decode(config.getXxPublicKey()), reqDecryptStr.getBytes(config.getEncoding()), Base64.getDecoder().decode(signature));
break;
default:
veriyfyResult = false;
break;
}
if (!veriyfyResult) {
throw new ApplicationException("验签失败");
}
//存储解密SM4密钥至http响应头header
Map<String, String> responseHeader = new HashMap<>();
responseHeader.put("encryptKey", keyToEncry);
return new CopsResponseEntity(responseHeader, reqDecryptStr);
}
public CopsResponseEntity getCopsResponseEntity(CopsResponseEntity copsResponseEntity, CopsRequestVo copsRequestVo, boolean isSuccess) throws Exception {
CopsNotifyJHResVo copsNotifyJHResVo = new CopsNotifyJHResVo();
CopsHeaderResponseVo copsHeaderResVo = new CopsHeaderResponseVo();
BeanUtils.copyProperties(copsHeaderResVo, copsRequestVo.getHeader());
copsNotifyJHResVo.setHeader(copsHeaderResVo);
copsNotifyJHResVo.getHeader().setResponseTime(DateUtils.format(new Date(), null));
if (isSuccess) {
copsNotifyJHResVo.setBody(new CopsBodyResponseVo("success", ""));
} else {
copsNotifyJHResVo.setBody(new CopsBodyResponseVo("", "fail"));
}
copsResponseEntity.setResponseStr(JSONObject.toJSONString(copsNotifyJHResVo));
//响应广发请求
copsResponseEntity = this.responseCops(copsResponseEntity);
return copsResponseEntity;
}
/**
* 加密,签名响应开放平台通知请求
*
* @param copsResponseEntity
* @return
* @throws Exception
*/
public CopsResponseEntity responseCops(CopsResponseEntity copsResponseEntity) throws Exception {
String encryptKey = copsResponseEntity.getResponseHeader().get("encryptKey");
// 对响应报文加密
String responseEncryptJson = Base64.getEncoder().encodeToString(SM4Util.encryptCBC(copsResponseEntity.getResponseStr().getBytes(config.getEncoding()), encryptKey.getBytes(), encryptKey.getBytes()));
// 对原报文签名
String responseSignature = Base64.getEncoder().encodeToString(SM2Util.sign(Base64.getDecoder().decode(config.getPrivateKey()), copsResponseEntity.getResponseStr().getBytes(config.getEncoding())));
// 对报文加密密钥加密,使用广发公钥进行加密
String keyToEncry = Base64.getEncoder().encodeToString(SM2Util.encrypt(Base64.getDecoder().decode(config.getCgbPublicKey()), encryptKey.getBytes()));
copsResponseEntity.setResponseStr(responseEncryptJson);
copsResponseEntity.getResponseHeader().put("encryptKey", keyToEncry);
copsResponseEntity.getResponseHeader().put("signature", responseSignature);
this.buildRespHttpHeaderMap(copsResponseEntity.getResponseHeader());
return copsResponseEntity;
}
private Map<String, String> buildRespHttpHeaderMap(Map<String, String> headerMap) {
headerMap.put("signType", "SM2");
headerMap.put("Content-Type", "application/json;charset=UTF-8");
headerMap.put("encryptType", "SM4");
return headerMap;
}
/*下面好像是没用到的先留着吧*/
/**
* SM2公钥加密
*
* @param pukName
* @param needEncryptData
* @param encoding
* @return
* @throws Exception
*/
public static String encryptString(String pukName, String needEncryptData, String encoding)
throws Exception {
byte[] encrypt = SM2Util.encrypt(getPublicKey(pukName),
needEncryptData.getBytes(encoding));
return Base64.getEncoder().encodeToString(encrypt);
}
private static byte[] getPublicKey(String pukName) throws Exception {
byte[] pukBytes = read(pukName);
if (pukBytes.length > 64) {
pukBytes = SM2Util.getPublicKey(pukBytes);
}
return pukBytes;
}
public static byte[] read(String filePath) throws IOException {
if (filePath == null) {
throw new IllegalArgumentException("Illegal Argument: filePath");
}
byte[] out;
try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
out = new byte[fileInputStream.available()];
byte[] buffer = new byte[500];
int rLength;
for (int offset = 0; (rLength = fileInputStream.read(buffer, 0, buffer.length)) != -1; offset
+= rLength) {
System.arraycopy(buffer, 0, out, offset, rLength);
}
}
return out;
}
/**
* 对报文解密,验签
*
* @param copsCommonVo
* @param reqStr
* @param signature
* @param encryptKey
* @param cgbService
* @return
*/
public CopsCommonVo decrypt4CGB(CopsCommonVo copsCommonVo, String reqStr, String signature, String encryptKey, CgbService cgbService) {
copsCommonVo.setResult(false);
copsCommonVo.setBase64PrivateKey(config.getPrivateKey());
copsCommonVo.setBase64PublicKey(config.getPublicKey());
copsCommonVo.setBase64CgbPublicKey(config.getCgbPublicKey());
copsCommonVo.setMsg(reqStr);
copsCommonVo.setSignature(signature);
copsCommonVo.setEncryptKey(encryptKey);
try {
// 对报文加密密钥解密,使用商户私钥进行解密
String keyToEncry = new String(SM2Util.decrypt(Base64.getDecoder().decode(copsCommonVo.getBase64PrivateKey()), Base64.getDecoder().decode(copsCommonVo.getEncryptKey())), config.getEncoding());
//解密开放平台请求报文
String reqDecryptStr = new String(SM4Util.decryptCBC(Base64.getDecoder().decode(copsCommonVo.getMsg()), keyToEncry.getBytes(), keyToEncry.getBytes()), config.getEncoding());
//请求报文验签
boolean veriyfyResult = SM2Util.verifySign(Base64.getDecoder().decode(copsCommonVo.getBase64CgbPublicKey()), reqDecryptStr.getBytes(config.getEncoding()), Base64.getDecoder().decode(copsCommonVo.getSignature()));
if (!veriyfyResult) {
throw new ApplicationException("验签失败");
}
copsCommonVo.setResult(true);
copsCommonVo.setEncryptKey(keyToEncry);
copsCommonVo.setMsg(reqDecryptStr);
copsCommonVo.setSignature("");
} catch (Exception e) {
log.info("解密验签there is an exception ", e);
}
return copsCommonVo;
}
public CopsCommonVo encrypt2CGB(CopsCommonVo copsCommonVo, CgbService cgbService) {
copsCommonVo.setResult(false);
copsCommonVo.setBase64PrivateKey(config.getPrivateKey());
copsCommonVo.setBase64PublicKey(config.getPublicKey());
copsCommonVo.setBase64CgbPublicKey(config.getCgbPublicKey());
// 对原报文加密
try {
String requestEncryptJson = Base64.getEncoder().encodeToString(SM4Util.encryptCBC(copsCommonVo.getMsg().getBytes(config.getEncoding()), copsCommonVo.getEncryptKey().getBytes(), config.getEncryptKey().getBytes()));
// 对原报文签名
String requestSignatureJson = Base64.getEncoder().encodeToString(SM2Util.sign(Base64.getDecoder().decode(copsCommonVo.getBase64PrivateKey()), copsCommonVo.getMsg().getBytes(config.getEncoding())));
// 对报文加密密钥加密,使用广发公钥进行加密
String keyToEncry = Base64.getEncoder().encodeToString(SM2Util.encrypt(Base64.getDecoder().decode(copsCommonVo.getBase64CgbPublicKey()), copsCommonVo.getEncryptKey().getBytes()));
copsCommonVo.setMsg(requestEncryptJson);
copsCommonVo.setEncryptKey(keyToEncry);
copsCommonVo.setSignature(requestSignatureJson);
copsCommonVo.setResult(true);
} catch (Exception e) {
log.info("加密there is an exception ", e);
}
return copsCommonVo;
}
public String getStackMsg(Exception e) {
StringBuffer sb = new StringBuffer();
StackTraceElement[] stackArray = e.getStackTrace();
for (int i = 0; i < stackArray.length; i++) {
StackTraceElement element = stackArray[i];
if (element.toString().contains("bobmao")) {
if (i != 0) {
sb.append(" <font color=\"#FF0000\">" + element.toString() + "</font>" + "<br>");
}
} else {
sb.append(" " + element.toString() + "<br>");
}
}
return sb.toString();
}
public String getMessage(Exception e, String reqStr, String signature, String encryptKey, String type) {
try {
String formatTemplate = "%s:<br> %s<br>";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format(formatTemplate, "Message", e.getMessage()));
stringBuilder.append(String.format(formatTemplate, "Date", DateHelper.TIME_FORMAT.format(new Date())));
InetAddress inetAddress = null;
inetAddress = InetAddress.getLocalHost();
stringBuilder.append(String.format(formatTemplate, "Host",
String.format("%s/%s", inetAddress.getHostName(), inetAddress.getHostAddress())));
stringBuilder.append(String.format("%s:<br>%s<br>", "StackTrace", getStackMsg(e)));
String tmp = " %s:%s<br> %s:%s<br> %s:%s<br>";
if (reqStr != null) {
CopsResponseEntity copsResponseEntity = null;
copsResponseEntity = getDecryptReqStr(reqStr, signature, encryptKey, type);
CopsRequestVo copsRequestVo = JSONObject.parseObject(copsResponseEntity.getResponseStr(), CopsRequestVo.class);
String string = new JsonFormatTool().formatJson(copsRequestVo.getBody().toString());
if (signature != null) {
stringBuilder.append("参数<br>").append(String.format(tmp, "reqStr", reqStr, "signature", signature, "encryptKey", encryptKey));
stringBuilder.append(String.format(formatTemplate, "请求json:", string).replaceAll("\n", "<br>").replaceAll(" ", " "));
} else {
stringBuilder.append("参数<br>").append(reqStr);
}
}
return stringBuilder.toString();
} catch (Exception Exception) {
}
return "";
}
}