简介
本文介绍了基于国密sm2+sm4加密算法的接口鉴权demo
demo是java8 + SpringBoot
demo地址:https://github.com/destinyol/sm2sm4-interface-auth
背景介绍
SM2与SM4作为我国自主研发的商用密码算法,在接口安全领域形成了优势互补的加密体系。其中SM2作为非对称加密算法,基于椭圆曲线密码学(ECC)实现数字签名和密钥协商,具有安全性高、密钥短的特点,但加解密效率较低;SM4作为对称加密算法,采用128位分组加密机制,具备运算速度快、适合大数据量处理的优势,但存在密钥分发安全隐患。
在接口鉴权场景中,采用SM2进行身份认证与密钥协商,结合SM4实现业务数据加密,既能通过非对称加密解决密钥传输难题,又能利用对称加密保障传输效率,符合国密标准的同时有效防范中间人攻击和数据篡改风险。
加密解密流程
加密流程
首先给每一个服务分配一个AppId和AppKey作为服务id和私钥key,每个服务的AppId和AppKey是不可重复,会用于验证数据完整性,防止篡改。然后生成一个64字符长度的随机字符串作为私钥privateKey,由privateKey在圆锥曲线上计算得到公钥坐标字符串(x,y),x和y都是长度为64的字符串,作为公钥publicKey,公钥和私钥只生成一次,作为系统级配置变量,交由服务配置中心托管代理。

其中前两位为标记位,后128位是公钥坐标拼接而成,这样一个130长度字符串就作为SM2算法的加密公钥。在每一次发送请求时,由服务请求者随机生成一个16位随机字符串random16Key,并把这个字符串通过SM2算法和publicKey其中的坐标加密生成keyA。

然后再将服务请求者的AppId、当前时间戳timestamp和keyA拼接起来,借助签名算法和AppKey生成完整性数字签名,用于服务接收者鉴权。

最后将请求的数据部分序列化为Json字符串cipherData,将random16Key作为密钥,使用SM4算法生成加密数据decryptStr。

最终发送请求的请求体就是字符串decryptStr,请求头需要包含以下参数。
参数名称 |
类型 |
是否必须 |
描述 |
AppId |
String |
是 |
服务请求者Appld,用于标识请求来源的应用程序。 |
timestamp |
String |
是 |
当前时间的时间戳,以字符串格式提供,用于确保请求的时效性。 |
keyA |
String |
是 |
经过SM2加密的SM4的16位密钥,用于加密和解密通信过程中的数据。 |
encode |
String |
是 |
鉴权字符串,用于验证请求的合法性。 |
解密流程
首先与数据库中服务AppId匹配,若不存在则抛出服务不存在异常。接着用同样的encode生成方法拼接请求头中的参数与服务私钥AppKey,经过数字签名算法生成encode,比对请求中encode与生成的是否一致,若不一致则请求涉嫌伪造篡改数据,抛出异常。最后就可以用本地privateKey解密SM2加密的16位随机字符串random16Key,再使用random16Key解密SM4算法加密的数据decryptStr字符串,拿到最终数据Json字符串,鉴权流程结束。
对于最后请求的返回体,使用与请求方法同样的鉴权逻辑,对数据进行加密,由服务请求者本地解密拿到返回数据。
时序图

示例代码
1 2 3 4
| public static final String appid = "ed777143e9102d0058c11f7498f4cb0bcvi2rrrdoyojx8by";
public static final String privateKey = "eqwetwqyetwqyegqwetqvcsvafsagfsqrwqwgqwhvsnc0";
|
发送请求端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public static void login(){ HashMap<String,String> headerMap = new HashMap<String,String>(); headerMap.put("appid",appid); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); String nowdate = sdf.format(new Date()); headerMap.put("timestamp",nowdate); String random = RandomStringGenerator.generateRandomString(); String sm4key = sm2EncryptData_CBC(random); headerMap.put("keyA",sm4key); String encode = Signature.getSignature(headerMap, appKey); headerMap.put("encode",encode); headerMap.put("content-type", "application/json");
JSONObject jsonObject = new JSONObject();
jsonObject.put("password","xxxxxxxxxx111"); jsonObject.put("account","datadatadata");
String data = encryptData_CBC(com.alibaba.fastjson.JSON.toJSONString(jsonObject),random);
String result = HttpClientUtil.doPost(url+"/test/login", data, headerMap); System.out.println("result:" + result);
JSONObject object = JSON.parseObject(result);
System.out.println("解密result-datas:" + decryptData_CBC((String) object.get("keyA"), (String) object.get("datas"))); }
|
接收请求端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
|
@PostMapping("/login") public RetResponse login(HttpServletRequest request, @RequestBody String requestData) { RetResponse response = new RetResponse(); int msgCode = 1; OpenInterfaceParamDto openInterfaceParamDto = new OpenInterfaceParamDto(); openInterfaceParamDto.setSm4key(request.getHeader("keyA")); openInterfaceParamDto.setData(requestData); openInterfaceParamDto.setAppid(request.getHeader("appid")); openInterfaceParamDto.setEncode(request.getHeader("encode")); openInterfaceParamDto.setTimestamp(request.getHeader("timestamp")); try { ObjectMapper objectMapper = new ObjectMapper(); String jsonData = judgeDecryption(openInterfaceParamDto); System.out.println("请求接收成功,请求解密后数据为 = "+jsonData); Object data = objectMapper.readValue(jsonData, Object.class);
HashMap<String,String> res = new HashMap<>(); res.put("data","业务处理结果"); res.put("data2","业务处理结果2");
String dataResJson = com.alibaba.fastjson2.JSON.toJSONString(res);
OpenInterfaceParamDto openInterfaceResDto = new OpenInterfaceParamDto(); openInterfaceResDto.setData(dataResJson); openInterfaceResDto = judgeEncryption(openInterfaceResDto);
response.setAny("keyA",openInterfaceResDto.getSm4key()); response.setDatas(openInterfaceResDto.getData()); }catch (JsonProcessingException e){ e.printStackTrace(); } catch (IOException e) { throw new RuntimeException(e); } response.ret(response, msgCode, "返回结果提示语句"); return response; }
private String judgeDecryption(OpenInterfaceParamDto openInterfaceParamDto) { if (StringUtils.isBlank(openInterfaceParamDto.getSm4key()) || StringUtils.isBlank(openInterfaceParamDto.getTimestamp()) || StringUtils.isBlank(openInterfaceParamDto.getAppid()) || StringUtils.isBlank(openInterfaceParamDto.getEncode())){ throw new RuntimeException("参数不能为空"); }
if (!openInterfaceParamDto.getAppid().equals(appid)){ throw new RuntimeException("appid服务id不存在"); } String appKey1 = appKey;
HashMap<String,String> encodeMap = new HashMap<>(); encodeMap.put("appid",openInterfaceParamDto.getAppid()); encodeMap.put("timestamp",openInterfaceParamDto.getTimestamp()); encodeMap.put("keyA",openInterfaceParamDto.getSm4key()); String checkEncode = Signature.getSignature(encodeMap,appKey1);
if (!checkEncode.equals(openInterfaceParamDto.getEncode())) throw new RuntimeException("请求鉴权失败");
String decryptData_CBC = decryptData_CBC(openInterfaceParamDto.getSm4key(),openInterfaceParamDto.getData()); return decryptData_CBC; }
private OpenInterfaceParamDto judgeEncryption(OpenInterfaceParamDto openInterfaceParamDto) { String random = RandomStringGenerator.generateRandomString(); String sm4EncryptData = encryptData_CBC(openInterfaceParamDto.getData(),random); String sm2e = sm2EncryptData_CBC(random);
openInterfaceParamDto.setSm4key(sm2e); openInterfaceParamDto.setData(sm4EncryptData); return openInterfaceParamDto; }
|
相关工具包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
|
public class SmUtil {
private final static String secretKey = "JKK12H6QXIL0N6GG";
private final static String privateKey = "E1D0EFA0BA6A370652B2E278E19D204E2D21244AD8D18E1EDCF82A9E24EB47AF"; private final static String publicKey = "0487DC37BB8CC8CD4E4A16FFFDE98C854121CCA026B33BAAF0A2171CC23693BC4AD9F9657D9B4098DFB8D730B74336D3B9603D9325714D9FFFF52D10B103B4F9A3";
public static String decryptData_CBC(String key, String cipherText) {
SM2 sm2 = getSM2(privateKey, null);
String decryptStr = StrUtil.utf8Str(sm2.decryptFromBcd(key, KeyType.PrivateKey)); System.out.println("sm2解密:" + decryptStr);
SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding,decryptStr.getBytes(),decryptStr.getBytes()); String sm41 = sm4.decryptStr(cipherText);
System.out.println("sm4解密:" + sm41);
return sm41; }
public static String sm2EncryptData_CBC(String key) {
SM2 sm2 = getSM2(null, publicKey);
String encryptStr = sm2.encryptBcd(key, KeyType.PublicKey); System.out.println("sm2加密:" + encryptStr);
return encryptStr; }
public static String decryptData_CBC(String cipherText) {
SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding,secretKey.getBytes(),secretKey.getBytes()); String sm41 = sm4.decryptStr(cipherText);
System.out.println("sm4解密:" + sm41);
return sm41; }
public static String encryptData_CBC(String cipherText) { SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding,secretKey.getBytes(),secretKey.getBytes()); String sm41 = sm4.encryptHex(cipherText);
System.out.println("sm4加密:" + sm41);
return sm41; }
public static String encryptData_CBC(String cipherText,String sm4key) { SM4 sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding,sm4key.getBytes(),sm4key.getBytes()); String sm41 = sm4.encryptHex(cipherText);
System.out.println("sm4加密:" + sm41);
return sm41; }
private static SM2 getSM2(String privateKey, String publicKey) { ECPrivateKeyParameters ecPrivateKeyParameters = null; ECPublicKeyParameters ecPublicKeyParameters = null; if (StringUtils.isNotBlank(privateKey)) { ecPrivateKeyParameters = BCUtil.toSm2Params(privateKey); } if (StringUtils.isNotBlank(publicKey)) { if (publicKey.length() == 130) { publicKey = publicKey.substring(2); } String xhex = publicKey.substring(0, 64); String yhex = publicKey.substring(64, 128); ecPublicKeyParameters = BCUtil.toSm2Params(xhex, yhex); } SM2 sm2 = new SM2(ecPrivateKeyParameters, ecPublicKeyParameters); sm2.usePlainEncoding(); sm2.setMode(SM2Engine.Mode.C1C2C3); return sm2; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public class Signature {
public static String getSignature(HashMap<String,String> textMap,String authkey){
List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>(textMap.entrySet()); Collections.sort(list, new Comparator<Map.Entry<String, String>>() { public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } });
String str = ""; for (Map.Entry<String, String> atr : list) { str = str + atr.getKey() + "=" + atr.getValue() + "&"; } str = str.substring(0, str.length() - 1); String temp = str+authkey;
String return_newstr = Md5Utils.MD5(temp); String return_bigstr = return_newstr.toUpperCase();
return return_bigstr; } }
|