Http接口安全整理
2020-11-14 22:37
标签:sem 简单 rtt code 手机 auth 两种 eal v2x 1.Http接口安全概述: 1.1、Http接口是互联网各系统之间对接的重要方式之一,使用http接口,开发和调用都很方便,也是被大量采用的方式,它可以让不同系统之间实现数据的交换和共享,但由于http接口开放在互联网上,那么我们就需要有一定的安全措施来保证不能是随随便便就可以调用; 1.2、目前国内互联网公司主要采用两种做法实现接口的安全: 一种是以支付宝等支付公司为代表的私钥公钥签名验证机制; 一种是大量互联网企业都常采用的参数签名验证机制; 2. Http接口安全演进: 2.1.完全开放的接口(完全开放) 2.2.接口参数签名(基本安全) 2.3.接口参数签名+时效性验证(更加安全) 2.4.接口参数私钥签名公钥验签(固若金汤) 2.5.接口参数签名+Https(金钟罩) 2.6.口参数私钥签名公钥验签+Https(金钟罩) 总之:安全是相对的,只有相对的安全,没有绝对的安全! 3.Http接口安全设计及应用 3.1 接口参数私钥签名公钥验签 先来看看私钥+公钥的安全模式,这是一种更为安全的方式,它通过私钥和公钥实现接口的安全,目前互联网中主要是以支付宝 为代表的公司采用这种机制;(有很多开放平台也是采用这种机制) . 具体业务流所示: 该签名是通过4个秘钥来实现的,分别是: 客户端应用私钥 , 客户端公钥 , 服务端应用私钥 , 服务端公钥. 私钥都是用来生成签名的,公钥都是用来解密的,客户端的公钥解密客户端的私钥生成的签名,服务端的公钥解密服务端的私钥生 成的签名,相信这样解释应该会比较好理解的. 好了,下面就来看看是如何具体操作的,我的具体操作步骤: 首先,4把密钥都是通过OpenSSL工具生成,你需要先获得这个工具:官方网站:https://www.openssl.org/ 介绍一下这个工具吧,OpenSSL 是一个开源的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序测试或其它目的使用;OpenSSL整个软件包大概可以分成三个主要的功能部分:SSL协议库、应用程序以及密码算法库; 3.1.1确保Linux已经安装openssl : 使用命令检查Linux是否已经安装openssl:yum list installed | grep openssl 如果没有安装,则执行命令进行安装:yum install openssl openssl-devel -y 3.1.2创建秘钥生成的目的地(文件夹): ps : 我是在根目录中的soft文件夹下创建的 mkdir server , mkdir client 先创建这两个文件,然后Linux会默认将生成的秘钥放入其中. 3.1.3 生成秘钥 进入server文件,输入openssl 进入Openssl命令行; 3.2 Demo 好了,现在就可以使用生成的秘钥来做一个简单的Demo来检验一下了. 首先这里提供一个签名处理工具类: 说明:该工具类依赖的包是java.security,该包JDK8才有,所以,如果使用下面这个Demo的话,建议检查自己的JDK是不是JDK8,如果是JDK8以下的版本,以下的Demo是用不了的.解决方法就是,使用第三方提供的依赖包,效果是相同的,依赖包如下: 以下是签名工具类: package com.kinglong.http.utils; import java.security.KeyFactory; /** public static final String CHARSET = "utf-8"; /** Signature sigEng = Signature.getInstance("SHA1withRSA"); byte[] signature = sigEng.sign(); } catch (Exception e) { /** //将公钥变为一个字节数组 //使用"SHA1WithRSA"算法,生成签名对象signature boolean bverify = signature.verify(Base64.getDecoder().decode(sign)); package com.kinglong.http.HttpUtils; import java.util.Iterator; Map while (iterator.hasNext()){ Map.Entry } } } package com.kinglong.http.controller; import com.alibaba.fastjson.JSONObject; import java.util.Date; public class HttpClient { demo(); } public static void demo(){ //这里仅做演示使用,所以随手写了几个信息 //封装参数 //生成客户端签名,注意看,客户端这里生成签名的时候是使用的客户端私钥Constans.CLIENT_PRIVATE_KEY //发送请求时注意:因为肯定会不可避免的要输出中文字符,所以记得在使用HttpClient通信的时候,设置编码格式为utf-8 //解析json字符串 //封装参数准备进行验证该返回信息是否是由服务端发回的 //调用签名验证工具类进行签名验证 if (isTrue){ } 下面是客户端使用的公共常量类: package com.kinglong.http.constans; public class Constans { //服务端公钥 服务端使用的是springboot框架,以下是pom文件 说明:服务端的签名生成类,签名生成的辅助类都与客户端相同,所以,这里就不写了. 服务端的公共常量类: package com.kinglong.http.constans; public class Constans { } 服务端接口: package com.kinglong.http.controller; import com.kinglong.http.constans.Constans; import java.util.Date; @Controller @RequestMapping("/api/verifydemo") ResponseObject responseObject = new ResponseObject(); //签名参数验证 responseObject.setErroMessage("真实姓名不能为空"); }else if (StringUtils.isEmpty(phone)){ responseObject.setErroMessage("手机号不能为空"); }else if (StringUtils.isEmpty(idCard)){ responseObject.setErroMessage("身份证不能为空"); }else if (StringUtils.isEmpty(bankCard)){ responseObject.setErroMessage("银行卡不能为空"); }else if (StringUtils.isEmpty(sign)){ responseObject.setErroMessage("签名不能为空"); }else { //注意看,这里使用的就是客户端的公钥进行签名的解密 if (!isTrue){ responseObject.setErroMessage("签名有误,请核查后再次尝试"); //0000是失败,1111是成功 map.clear(); //使用服务端私钥生成返回的签名 }else { map.clear(); //使用服务端私钥生成返回的签名 } return responseObject; } over,以上就是使用私钥+公钥进行http接口安全设计的简单上手示例. 个人觉得,能把这个私钥公钥的搞明白了,其他的几个接口安全设计也就不在话下了,基本看一下流程图就能了然于心了.这里就放出参数安全设计的流程图,具体代码就不写了,有兴趣的可以尝试着做一下. 为了方便练习的盆友快速理解,那就顺带提几个需要注意的点吧. 在做:"接口参数签名+时效性验证(更加安全)"时,注意服务端返回的签名就不需要再传时间戳了,理应是要传的,但是一般很少有人这么做.不过,要不要这么做,也得看公司的要求嘛. 网址摘要地址:https://blog.csdn.net/hjl021/article/details/79286830 Http接口安全整理 标签:sem 简单 rtt code 手机 auth 两种 eal v2x 原文地址:https://www.cnblogs.com/yelanggu/p/10002507.html
使用openssl生成私钥,执行如下命令:
genrsa -out rsa_private_key.pem 2048
注意一点Java开发者需要将私钥转换成PKCS8格式,其他语言不用这一步操作,执行如下命令:
pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out rsa_private_key_pkcs8.pem
使用openssl生成公钥,执行如下命令:
rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
退出openssl命令行:exit
经过以上步骤,我们可以在当前目录中(server)看到三个文件:
rsa_private_key.pem(RSA私钥)
rsa_private_key_pkcs8.pem(pkcs8格式RSA私钥)(我们java要使用的私钥是这一个)
rsa_public_key.pem(对应RSA公钥)
client端的秘钥与server端的秘钥生成一样,这里就不叙述了,想体验的朋友自己按照上一步操作再做一遍即可.
commons-codec
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
* 签名处理工具类
*
* @author haojinlong
*
*/
public class MyRSAUtils {
* RSA私钥签名
*
* @param src 客户端传过来的原始参数
* @param priKey 我们的客户端私钥
* @return
* @throws Exception
*/
public static String sign (String src, String priKey) {
try {
KeyFactory fac = KeyFactory.getInstance("RSA");
byte[] pribyte = Base64.getDecoder().decode(priKey);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pribyte);
RSAPrivateKey privateKey = (RSAPrivateKey) fac.generatePrivate(keySpec);
sigEng.initSign(privateKey);
sigEng.update(src.getBytes(MyRSAUtils.CHARSET));
return Base64.getEncoder().encodeToString(signature);
e.printStackTrace();
}
return null;
}
* RSA公钥验证签名
*
* @param src 客户端穿过来的原始数据
* @param sign 签名
* @param publicKey 我们的客户端公钥
* @return
*/
public static boolean signVerify (String sign, String src, String publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] encodedKey = Base64.getDecoder().decode(publicKey);
//使用秘钥工厂生成一个公钥对象pubKey
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initVerify(pubKey);
signature.update(src.getBytes(MyRSAUtils.CHARSET));
return bverify;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
下面是签名时需要的辅助工具类:
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
*生成签名需要的辅助工具类
*/
public class SignUtils {
/**
*将传进来的无序参数,转换为有序的字符串输出
*/
public static String generateSortSign(Map
Set
Iterator
StringBuffer stringBuffer = new StringBuffer();
String keys = entry.getKey();
String value = (String) entry.getValue();
stringBuffer.append(keys).append(value);
return stringBuffer.toString();
客户端代码:
import com.kinglong.http.HttpUtils.HttpClientUtils;
import com.kinglong.http.HttpUtils.SignUtils;
import com.kinglong.http.constans.Constans;
import com.kinglong.http.utils.MyRSAUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public static void main(String[] args) {
String url ="http://localhost:8080/api/verifydemo";
String realName = "我是验证信息";
String phone = "17000000000";
String idCard = "6403021992120511111";
String bankCard = "5555555555555555555555";
Map
paramMap.put("realName",realName);
paramMap.put("phone",phone);
paramMap.put("idCard",idCard);
paramMap.put("bankCard",bankCard);
String sign = MyRSAUtils.sign(SignUtils.generateSortSign(paramMap),Constans.CLIENT_PRIVATE_KEY);
paramMap.put("sign",sign);
//否则,我想你的签名验证成功的概率基本等于让两条平行线相交成功的概率
String json = HttpClientUtils.doPostByEncode(url,paramMap,"utf-8");
JSONObject jsonObject = JSONObject.parseObject(json);
String code = jsonObject.getString("code");
String erroMessage = jsonObject.getString("erroMessage");
JSONObject object = jsonObject.getJSONObject("object");
String result = object.getString("result");
String ret_sign = object.getString("ret_sign");//服务端发回的签名
String resultDesc = object.getString("resultDesc");
//验证时的参数可以根据自己公司的规范去选择,我这里就选了这两个参数,因为偷懒没有生成新的map,所以这里需要clear一下.
paramMap.clear();
paramMap.put("result",result);
paramMap.put("resultDesc",resultDesc);
//重点!!!敲黑板啦!!注意看,这里使用的是服务端的公钥来解密服务端发送的签名的,很多人肯定使用了客户端公钥来解密,必然是失败的
//同理,在服务端要是用客户端的公钥解密客户端发送的签名,这个前面的流程图我觉得已经说得很明确了
boolean isTrue = MyRSAUtils.signVerify(ret_sign,SignUtils.generateSortSign(paramMap),Constans.SERVER_PUBLIC_KEY);
System.out.println("返回正确信息,可以进行下一步操作");
}else {
System.out.println("返回错误信息,不可以进行下一步操作");
}
}
//客户端私钥
public static final String CLIENT_PRIVATE_KEY="MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClEAp0NDHtb9w5iJfyNOh6DeCRv0RjGFA1CIQ6ZxpfIc65h03hUGsDjcZtWQQZf7d30hiVCcQLylJYJidHQbPcDWULRhObgUxFIFQ37UW8c8DHDiHPNVRjH47ePi9sZIYbVuaWOJkS9NAoSDTRDA1vS7ewWosA9WyUSEuWSEm6eQQV02nlhf/cIsu3biDmq8Y9ffg9lLgEYbH4VQu6bRXgqpy90OxFh3Jh16nbAZXqAJMKCZyJYo5B9ZN4No8Q/EMZe98DNFybOod7WTuQmrS5FM0vCsjQpczBwn+dny5grWl4YgUYgAWOnvPr2iMXVXLqbQjcVEMFKwr/71K9yVehAgMBAAECggEBAJi6nvGm2gu41SznFrEmA3XsIT66m6yVcqGfn7nqbJxZy84fRBCXOG2xYUkMdJ6jbj+QRu6geqXuLwMhSnbEdIfIXRZxYPMiUFAl+cdF5KDa+iU1DlOMJOkS6j75iyfgW7YwUmvtMrY3j+O17CkB3ex9QxoKrVPVwwHxYv9LI+1FT0Fd3157gIbkBTdXnKUc4O4Z4/FTcvPYNR2h9O79xQbcx0clUbj61yQxkxVyN25plxxAVoUW7mKNleH6nFAkeb/gYxLdwlUHm53cYowSDtbzo4udB6qPWj/PvCVgu7UatnEhcyX9ZKCBmrX3+EvWTw9A9dTDSMu4D9CX2QsUq4ECgYEA2pTWhw6ulzWlORu12WcUxDzCVuKV7dGXCQ2MRnGELLm7HzAmRIjd0LubGWAKwUNa6NhWVNsupI8/hDh3hyjGAiTVn0gowDVQj4s/vIx5aYMsg6nxzUl3KrjBJyORHnY8+fFp5V1SHr0G6jOTynsqX0ONMpNZ/zyxiOQG5vGs7A8CgYEAwVHJAe0Eqz2jwLHA10Afsh3vS2Otcjj1cj9jxz1ZFoYIbD64Mc77bem+9x7xpI4w6/WezUOO4gZ8RaAh7PpyMWbTwzq0QHS7bSzKMfpRALyVf7HyIS/QuTUA0oLZXx9vk4wAe0rZBNFuuEtyU3XgHz8OSY/uHeTXJWYtJHTIkU8CgYAD6YgRcMTVNgOYCxPtKTgo7wF3dqTCVe8DHXf2Rs/b0RM1UrJMpbp6ovD6ukpW/TKiWkTpTeb+0QWNA0m4ZJVusmQUbsEz94BSoWZppIYDynJAhQkr6HW2kQn7/ln5lpouyxBfJ5VxsWZvSK8Lf7rZa6caUaLZu6dd0N8CwS6cJwKBgD74B9RTwtiQXF1wyNKUNX7MF1zkG+P/v5s2IKcOSY13nRi9GTxIIke8ApL2BlnGYxMIz3Am2EyxNhtrvIE3VqjWyJVn8ryoCUDXfQjocygdRUjxyl+a9o7NP/ZR3sIIOEzEJogCakwSd9EZ6iRbWeRzopC9jB86ogWxkXS1gXsrAoGAEOvVsv8lpY+TCs3Q78pnedwFIXXkrOknrXq2gb+WHmSSDCELOaqWsX73rdbW4IcmJ6kT2d5bn6hH+/2Zw/+Xpy6maBeLxxscXBfLHwT/85YG5z21LuyikB7ht/tZCNxQOjEfvo5MdCwNN6GhpPSVwNjLAvtiImJYnlDuGPaG+RU=";
public static final String SERVER_PUBLIC_KEY="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqlG1v814kyQIEpLQnyxo/4RUku8PE+csGzH073XoW8xPdXAH8C7Y3isyDKSClTEGeS/SaqioYPFI+YlD0Eag1DEoVPkDcJeWsPv4FZr2ZO2wCenwqUrH5BM7ZapSBLenkgdhTF3SIUJMrTfJPaRJM1wb75SPawHO1zueM0cvcbeGNJOyCG69XPhour0Cei/HcflY3bXVx9kKH8vvAmbosAOrIwwdGSnV1YqSlApBmoobGYcaFPv5Clntghq+6P1ut2RSOrKinr0Q4wc4kIpnSY+j2oQ20OZ7rZXuLyGmMSsglvGwgTIoxHqkXpWP3qGFmYHSQgGCMFWxlT1t3e1AawIDAQAB";
}
ok,客户端就齐活了,下面是服务端:
http-server
spring-boot-starter-parent
spring-boot-starter-web
spring-boot-starter-test
commons-lang
commons-codec
fastjson
spring-boot-maven-plugin
//服务端私钥
public static final String SERVER_PRIVATE_KEY="MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCqUbW/zXiTJAgSktCfLGj/hFSS7w8T5ywbMfTvdehbzE91cAfwLtjeKzIMpIKVMQZ5L9JqqKhg8Uj5iUPQRqDUMShU+QNwl5aw+/gVmvZk7bAJ6fCpSsfkEztlqlIEt6eSB2FMXdIhQkytN8k9pEkzXBvvlI9rAc7XO54zRy9xt4Y0k7IIbr1c+Gi6vQJ6L8dx+VjdtdXH2Qofy+8CZuiwA6sjDB0ZKdXVipKUCkGaihsZhxoU+/kKWe2CGr7o/W63ZFI6sqKevRDjBziQimdJj6PahDbQ5nutle4vIaYxKyCW8bCBMijEeqRelY/eoYWZgdJCAYIwVbGVPW3d7UBrAgMBAAECggEBAJtfkxf4T4iblCmteVfb4aVHiQfJwc18VEYy2qkgvOoRhmMx4mv/sKNscGoMIXwMj0U6lQ/r8D8PnmzWBeEYrVslxQ9PYw3xm+y0z+qVxTTpiHBi08L8j0HHMaZbLBtVly6mQOKzrB/fJafXfmQXXRfXbTywH+2UZqb+oiFRTTzEnFMyku5HquA27Mp+K4KNFTVaKiCSadwz+XyFOf1cmUn4oRlYnhgbMKgn2JSyQLfJ5SSgYhnxat1Qbg1HDhOzo6L/NQwCkTzo3B52X56EQXZkk2p1pVRZiwaP/3FCHEenOv9jy+ZffdUFECRCv0Aw2isWqZ4zuVrgWCI801W/AVECgYEA0gwkMSHWlJaGtWhYifgWcOSr/v9l/SAiD0VN57j4THWIAq30WtOVE/ta4XhbSasxcHJIqNckq2inEm2b3jwcS7YKzlfKGl9xw8uL3yVM1YXtKwGrJr1xk8pcWOVBmFaAd8BaOHwsMkE8EhgiwZSctTbmRNeiOz6lIOj2aDu6fE0CgYEAz5SPKeNay00LIGk4Os+Zev1JPhwW4tfwTJxXV97TjvgYoRwUwV6XNjiXoxQD5iOXieK7Cy0GDMzJWxXpmEpI5BVv2X6GMkkH5iXz4rGtzsMNWZ5lF3d2PpRLvRIrSi3btevTohN7UkogyjEPhuEnV47Emsev36lB+bcJwgLBK5cCgYAuEaemdwt/T3yAMUCqEhWp8R2gMhgGapPN0Z+CoVkkO+r223xqp1ldJpYKOcGb6MZRKV+yWG2cgrmSGyRCm+CA4o6AL1UOb7yd+vjUmnO9qUAZXKZTOt28Unfqr22xoddPbIrdNK7k3tX0CgMlfhjYzg+3LaxRXi4Nh8rzlZYTSQKBgBahgq41bEun3aOt9QRsZ7ZB8P9FfrVCh59CmD8rOvNmVwERl62xS1kM+HM+FmK71KSixHOmd/djSDyW+f2xc5ryP1x979GBpsvPrXQ0nNdi6oyvuSPC0XBnKI63cWLH9yExUcRkzVgeXs7MZH33BBwGo6agSKtgv6Gi8/xj4n2HAoGBALD55ZzDDrB17ZOQSuEnn45R3FA62BGipCM8HFdZySeEOf9IPoOxy2xmzQtMc2VESRQeviw92E5gxNxrRgIHSQvPJryaHOjoMKI9+kDBejZvBEHWi23E4tobPUOUH/jBJJQ7Ue5aRB6IS/jlMzmVhan8vefnEXRDT5EnSfxV3Pa6";
//客户端公钥
public static final String CLIENT_PUBLIC_KEY="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApRAKdDQx7W/cOYiX8jToeg3gkb9EYxhQNQiEOmcaXyHOuYdN4VBrA43GbVkEGX+3d9IYlQnEC8pSWCYnR0Gz3A1lC0YTm4FMRSBUN+1FvHPAxw4hzzVUYx+O3j4vbGSGG1bmljiZEvTQKEg00QwNb0u3sFqLAPVslEhLlkhJunkEFdNp5YX/3CLLt24g5qvGPX34PZS4BGGx+FULum0V4KqcvdDsRYdyYdep2wGV6gCTCgmciWKOQfWTeDaPEPxDGXvfAzRcmzqHe1k7kJq0uRTNLwrI0KXMwcJ/nZ8uYK1peGIFGIAFjp7z69ojF1Vy6m0I3FRDBSsK/+9SvclXoQIDAQAB";
import com.kinglong.http.rto.ResponseObject;
import com.kinglong.http.utils.MyRSAUtils;
import com.kinglong.http.utils.SignUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class HttpController {
@ResponseBody
public Object demo(@RequestParam(value = "realName")String realName,
@RequestParam(value = "phone")String phone,
@RequestParam(value = "idCard")String idCard,
@RequestParam(value = "bankCard")String bankCard,
@RequestParam(value = "sign")String sign){
Map
if (StringUtils.isEmpty(realName)){
responseObject.setCode("0000");
responseObject.setCode("0000");
responseObject.setCode("0000");
responseObject.setCode("0000");
responseObject.setCode("0000");
//封装参数进行验证
map.put("realName",realName);
map.put("phone",phone);
map.put("idCard",idCard);
map.put("bankCard",bankCard);
boolean isTrue = MyRSAUtils.signVerify(sign,SignUtils.generateSortSign(map),Constans.CLIENT_PUBLIC_KEY);
responseObject.setCode("0000");
}
}
String code = responseObject.getCode();
if(code!=null && code.equals("0000")){
map.put("result","0000");
map.put("resultDesc",responseObject.getErroMessage());
String ret_sign = MyRSAUtils.sign(SignUtils.generateSortSign(map),Constans.SERVER_PRIVATE_KEY);
map.put("ret_sign",ret_sign);
responseObject.setObject(map);
map.put("result","ok");
map.put("resultDesc","验证通过");
String ret_sign = MyRSAUtils.sign(SignUtils.generateSortSign(map),Constans.SERVER_PRIVATE_KEY);
map.put("ret_sign",ret_sign);
responseObject.setObject(map);
responseObject.setCode("1111");
responseObject.setErroMessage("验证成功");
}
在拼装秘钥的时候,注意字符串首先得进行排序,不管是升序还是降序,亦或是其他的顺序(比如公司要求的顺序),如果不先按照一个约定的顺序进行排序,那么势必会造成客户端与服务端参数的字符串排列顺序不同,致使无法验证成功.这里就提供一个简便的排序工具,并且使用MD5进行16进制加密
上一篇:HTML特殊字符编码对照表