Http请求加密规则(3DES、Base64、HMAC SHA256)
如果使用了Https请求,那么大多数情况下就无需双方再制定一套加密规则,所以本人讲述的是使用Http请求时,对于一些安全性较高的业务场景,需要对请求进行加密的实现方式。
-
首先总结一下http请求的密钥和参数加密规则,可以根据自己项目进行修改:
-关键词解释:- app-key和app-token:两个请求头,app-key是用户自己定义的,主要是用来进行请求权限控制,app-token采用JWT实现,不了解的可以去查一下JWT。
- 参数密钥、签名密钥、AppKey密钥:参数密钥是对请求参数进行加密的;签名密钥是在生成JWT通讯令牌(即请求头中的app-token)时采用HMAC SHA256散列算法所用到的;App-Key密钥是对请求头中的app-key进行加密的。
-
下面开始讲述具体加密的实现方式:
- 如第一步中所讲,参数加密是采用先3DES加密,然后再进行Base64编码,代码实现如下:
/**
*
* @Description:先3des加密,然后base64编码
* @param key
* @param data
* @return
*/
public static String encrypt3DESAndBase64(String key, String data){
byte[] result3Des = encrypt3DES(build3DesKey(key),data,"GBK");
String result = encryptBASE64(result3Des);
return result;
}
/**
* 不满足24位的数据补充到24位
* @param key
* @return
*/
private static String build3DesKey(String key) {
return String.format("%-24s", key).replace(' ', '0');
}
/**
* @Description:使用3DES算法加密
* @param key 密钥
* @param data 待加密数据
* @param charset 字符编码
* @return
*/
public static byte[] encrypt3DES(String key, String data, String charset){
try {
byte[] keyByte = key.getBytes(charset);
byte[] dataByte = data.getBytes(charset);
//生成密钥
SecretKey deskey = new SecretKeySpec(keyByte, "DESede");
//加密
Cipher c1 = Cipher.getInstance("DESede");
c1.init(Cipher.ENCRYPT_MODE, deskey);
return c1.doFinal(dataByte);
} catch (Exception e) {
logger.error("encrypt3DES occur error",e);
}
return null;
}
/**
*
* @Description:对数据进行base64编码
* @param data
* @return
*/
public static String encryptBASE64(byte[] data) {
try {
return Base64Utils.encodeToUrlSafeString(data);
} catch (Exception e) {
logger.error("encryptBASE64 occur error",e);
return null;
}
}
- 对应的解密实现方式如下:
/**
*
* @Description:先base64解码,然后再3des解密
* @param key
* @param data
* @return
*/
public static String decrypt3DESAndBase64(String key, String data) {
String result = "";
if(null != data && !data.isEmpty()) {
byte[] resultDecryBase64 = decryptBASE64(data);
result = decrypt3DES(build3DesKey(key),resultDecryBase64,"GBK");
}
return result;
}
/**
*
* @Description:使用3DES算法解密
* @param key 密钥
* @param data 待解密数据
* @return
*/
public static String decrypt3DES(String key, byte[] data, String charset){
try {
byte[] keyByte = key.getBytes(charset);
//生成密钥
SecretKey deskey = new SecretKeySpec(keyByte, "DESede");
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.DECRYPT_MODE,deskey);
byte[] resultByte = cipher.doFinal(data);
return new String(resultByte, charset);
} catch (Exception e) {
logger.error("decrypt3DES occur error",e);
}
return null;
}
/**
-
- @Description:对数据进行base64解码
- @param data 待解码数据
- @return
*/
public static byte[] decryptBASE64(String data) {
try {
return Base64Utils.decodeFromUrlSafeString(data);
} catch (Exception e) {
logger.error("decryptBASE64 occur error");
return null;
}
}
- 生成app-token即通讯令牌:生成app-token采用的是JWT提供的jar包来生成的,该jar包的maven依赖为:
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>4.23</version>
</dependency>
对于JWT的原理下面简单介绍一下:
jwt主要由三部分构成,第一部分为头部(header),第二部分为载荷(payload,该次请求承载的一些非敏感数据),第三部分是签证(signature)。
jwt的头部承载两部分信息:
声明类型,我们采用jwt
声明加密的算法,HMAC SHA256,
jwt的载荷构成:
iss: jwt签发者,根据自己的项目进行定义
sub: jwt所面向的用户,双方约定的地方三方平台代码
exp: jwt的过期时间,这个过期时间必须要大于签发时间(毫秒),两边服务器时间需要同步,我们约定为3秒
iat: jwt的签发时间(毫秒)
jwt的签名:
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret(双方共同约定的密钥)
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行secret组合加密,然后就构成了jwt的第三部分。
示例:
Jwt Header头:
{“type”: “JWT”,“alg”: “HS256”}
Jwt payload:
{
“iss”:“epay”,
“sub”: “sinodata”,
“exp”: 1516239028,
“iat”: 1516239023
}
Jwt signature:
QowRE42QP3pMc-4fXzM1xouTnsGBYBw4EE0LNq-Dop0
生成Token :
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlcGF5Iiwic3ViIjoic2lub2RhdGEiLCJleHAiOjE1MTYyMzkwMjIsImlhdCI6MTUxNjIzOTAyOH0.QowRE42QP3pMc-4fXzM1xouTnsGBYBw4EE0LNq-Dop0
生成jwt 的app-token代码如下:
/**
*
* @Description: 生成jwt token
* @param secret
* @param header
* @param payload
* @return
*/
public static String createToken(byte[] secret,JWSHeader header,Payload payload) {
String tokenString=null;
// 创建一个 JWS object
JWSObject jwsObject = new JWSObject(header,payload);
try {
// 将 jwsObject 进行 HMAC签名
jwsObject.sign(new MACSigner(secret));
tokenString=jwsObject.serialize();
} catch (JOSEException e) {
logger.error("jwt签名失败 !" ,e);
}
return tokenString;
}
/**
*/
public static String generateToken() {
try {
Map<String,Object> headerMap=new HashMap<>();
headerMap.put("type","jwt");
headerMap.put("alg","HS256");
Map<String,Object> payloadMap=new HashMap<>();
payloadMap.put("iss","zhaoyi");
payloadMap.put("sub","testCom");
payloadMap.put("iat",System.currentTimeMillis());
payloadMap.put("exp",((Long)payloadMap.get("iat"))+1800*1000);
JWSHeader h = JWSHeader.parse(JSONObject.toJSONString(headerMap));
Payload p = new Payload(JSONObject.toJSONString(payloadMap));
return createToken("secret".getBytes("GBK"),h,p);
} catch(Exception e) {
logger.error("generateQfbToken occur error", e);
return null;
}
}
- 在收到http请求后,校验token的有效性:
public static Map<String,Object> valid(String token, String secret){
Map<String, Object> resultMap = new HashMap<>();
try {
//解析token
JWSObject jwsObject = JWSObject.parse(token);
//获取到载荷
Payload payload=jwsObject.getPayload();
//建立一个解锁密匙
JWSVerifier jwsVerifier = new MACVerifier(secret);
//判断token
if (jwsObject.verify(jwsVerifier)) {
resultMap.put("Result", 0);
//把载荷的数据解析成json对象。
JSONObject payLoadJsonStr = payload.toJSONObject();
resultMap.put("data", payLoadJsonStr);
System.out.println("---payLoadJsonStr--" + payLoadJsonStr);
//判断token是否过期
if (payLoadJsonStr.containsKey("exp")) {
Long expTime = Long.valueOf(payLoadJsonStr.get("exp").toString());
Long nowTime = System.currentTimeMillis();
//判断是否过期
if (nowTime > expTime) {
//已经过期
resultMap.clear();
resultMap.put("Result", 2);
return resultMap;
}
}
//验证签发者
if(payLoadJsonStr.containsKey("iss")) {
if(!"zhaoyi".equals(payLoadJsonStr.get("iss"))) {
resultMap.clear();
resultMap.put("Result", 3);
return resultMap;
}
}
//验证面向的用户
if(payLoadJsonStr.containsKey("sub")) {
if(!"testCom".equals(payLoadJsonStr.get("sub"))) {
resultMap.clear();
resultMap.put("Result", 4);
return resultMap;
}
}
}else {
resultMap.put("Result", 1);
}
} catch (Exception e) {
log.error("valid token occur error",e);
}
return resultMap;
}
- 最后,给大家额外提供HMAC SHA256的实现方式,因为生成app-token的时候是用到这种散列算法的,只不过我们用的是已封装好的jar包。
/**
*
* @Description: 使用hmacSHA256进行加密
* @param data 加密数据
* @param key 密钥
* @return
* @throws Exception
*/
public static String encryptHmacSHA256(String key, String data) throws Exception {
SecretKey secretKey = new SecretKeySpec(key.getBytes(), "HmacSHA256");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return new String(mac.doFinal(data.getBytes()));
}
最后,上述实现方式里的3DES为一种对称加密算法,当然也可以换成其它的对称加密算法,比如安全性更高的AES。如果大家实现过程中有什么问题,欢迎留言咨询。