基于MD5+RSA算法实现接口调用防扯皮级鉴权
概述
最近项目中需要对第三方开发接口调用,考虑了一下,准备采用MD5+RSA算对请求数据进行签名,来达到请求鉴权,过滤非法请求的目标。
数字签名采用MD5+RSA算法实现。RSA私钥要严格保密并提供安全存储介质,数字签名使用java.security.Signature 包中规定的“MD5withRSA”算法实现。私钥签名,公钥验签即接口调用方存储私钥并用私钥对请求数据进行签名,平台方存储调用方提供的公钥,对于调用方的签名进行验签,验签通过才会接收调用方请求的数据。
简易流程
1、从平台获取32位businessId,备用
2、本地生成keyPair,其中privateKey自行保存,需要将publicKey提供给平台
3、signature字段为businessId + signature结果
4、签名数据根据以接口限定为准
KeyPair生成、签名及验签
keyPair生成
private static final String KEY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
private static final String CHARSET = "UTF-8";
private ThreadLocal<String> publicKey = new ThreadLocal<>();
private ThreadLocal<String> privateKey = new ThreadLocal<>();
public CustomKeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keygen = java.security.KeyPairGenerator
.getInstance(KEY_ALGORITHM);
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed("remote".getBytes()); // 初始化随机产生器
keygen.initialize(1024);
KeyPair keys = keygen.genKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keys.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keys.getPrivate();
CustomKeyPair customKeyPair = CustomKeyPair.builder()
.privateKey(Base64Utils.encodeToString(privateKey.getEncoded()))
.publicKey(Base64Utils.encodeToString(publicKey.getEncoded()))
.build();
log.info("privateKey:{}", customKeyPair.getPrivateKey());
log.info("publicKey:{}", customKeyPair.getPublicKey());
return customKeyPair;
}
@Data
@Builder
private static class CustomKeyPair {
private String privateKey;
private String publicKey;
}
签名调用示例
SignatureTool.I.putPrivateKey(customKeyPair.privateKey).signature("测试签名数据")
签名代码实现:
public String signature(String data) {
try {
return signature(data.getBytes(CHARSET));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("加密算法不存在");
} catch (SignatureException e) {
e.printStackTrace();
throw new RuntimeException("数据签名不存在");
} catch (InvalidKeyException | InvalidKeySpecException e) {
e.printStackTrace();
throw new RuntimeException("数字签名key异常");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException("不支持的字符编码");
}
}
/**
* 数字签名算法
*
* @param data 签名数据
* @return 签名结果
* @throws NoSuchAlgorithmException 没有此种加密算法异常
* @throws SignatureException 签名异常
* @throws InvalidKeyException 不可用的私钥
*/
private String signature(byte[] data) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, InvalidKeySpecException {
byte[] keyBytes = Base64Utils.decodeFromString(this.privateKey.get());
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(data);
return Base64Utils.encodeToString(Base64Utils.encodeToString(signature.sign()).getBytes());
}
验签调用示例
SignatureTool.I.putPublicKey(customKeyPair.publicKey).verifySignature("测试签名数据", signature);
验签代码实现:
public boolean verifySignature(String data, String signature) {
try {
return verifySignature(data.getBytes(CHARSET), signature);
} catch (NoSuchAlgorithmException e) {
log.warn("加密算法不存在");
} catch (SignatureException e) {
log.warn("数据签名不存在");
} catch (InvalidKeyException | InvalidKeySpecException e) {
log.warn("数字签名key异常");
} catch (UnsupportedEncodingException e) {
log.warn("不支持的字符编码");
}
return false;
}
/**
* 数字签名验证
*
* @param data 验签数据
* @param sign 签名
* @return 验签结果
* @throws NoSuchAlgorithmException 没有此种加密算法异常
* @throws SignatureException 签名异常
* @throws InvalidKeyException 不可用的私钥
*/
private boolean verifySignature(byte[] data, String sign) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, InvalidKeySpecException {
byte[] signs = Base64Utils.decode(Base64Utils.decodeFromString(sign));
byte[] publicKey = Base64Utils.decodeFromString(this.publicKey.get());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
return signature.verify(signs);
}
调用示例
签名的数据可以根据实际的业务接口不同,单独设置,双方统一标准
public static void main(String[] args) throws NoSuchAlgorithmException {
//生成秘钥对
CustomKeyPair customKeyPair = SignatureTool.I.generateKeyPair();
//私钥签名
String signature = SignatureTool.I.putPrivateKey(customKeyPair.privateKey).signature("测试签名数据");
//公钥验签
SignatureTool.I.putPublicKey(customKeyPair.publicKey).verifySignature("测试签名数据", signature);
}
这样,我们只要给每个调用方分配一个businessId,由调用提供公钥并与映射到businessId,私钥自始至终一直由调用方存储,我们可以直接判定数据的提交来自哪个业务方,接口调用的安全性可以得到有效的保证,后续可以通过增加诸如ip白名单之类的限制,进一步加强接口的安全性,尤其在多方调用的场景下,防扯皮效果显著,另外被调用方也可以为业务方生成单独的keypair,实现双向验签的双保险机制。
SignatureTool 完整代码如下:
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Base64Utils;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Locale;
@Slf4j
public enum SignatureTool {
I;
private static final String KEY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
private static final String CHARSET = "UTF-8";
private ThreadLocal<String> publicKey = new ThreadLocal<>();
private ThreadLocal<String> privateKey = new ThreadLocal<>();
/**
* 数字签名
*
* @param data 签名内容
* @return 签名
*/
public String signature(String data) {
try {
return signature(data.getBytes(CHARSET));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("加密算法不存在");
} catch (SignatureException e) {
e.printStackTrace();
throw new RuntimeException("数据签名不存在");
} catch (InvalidKeyException | InvalidKeySpecException e) {
e.printStackTrace();
throw new RuntimeException("数字签名key异常");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException("不支持的字符编码");
}
}
public boolean verifySignature(String data, String signature) {
try {
return verifySignature(data.getBytes(CHARSET), signature);
} catch (NoSuchAlgorithmException e) {
log.warn("加密算法不存在");
} catch (SignatureException e) {
log.warn("数据签名不存在");
} catch (InvalidKeyException | InvalidKeySpecException e) {
log.warn("数字签名key异常");
} catch (UnsupportedEncodingException e) {
log.warn("不支持的字符编码");
}
return false;
}
/**
* 初始化公钥、私钥
*/
public SignatureTool initKeys(String privateKey, String publicKey) {
this.publicKey.set(publicKey);
this.privateKey.set(privateKey);
return this;
}
public SignatureTool putPublicKey(String publicKey) {
this.publicKey.set(publicKey);
return this;
}
public SignatureTool putPrivateKey(String privateKey) {
this.privateKey.set(privateKey);
return this;
}
public CustomKeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keygen = KeyPairGenerator
.getInstance(KEY_ALGORITHM);
SecureRandom secureRandom = new SecureRandom();
secureRandom.setSeed("ainoteRemote".getBytes()); // 初始化随机产生器
keygen.initialize(1024);
KeyPair keys = keygen.genKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keys.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keys.getPrivate();
CustomKeyPair customKeyPair = CustomKeyPair.builder()
.privateKey(Base64Utils.encodeToString(privateKey.getEncoded()))
.publicKey(Base64Utils.encodeToString(publicKey.getEncoded()))
.build();
log.info("privateKey:{}", customKeyPair.getPrivateKey());
log.info("publicKey:{}", customKeyPair.getPublicKey());
return customKeyPair;
}
/**
* 数字签名算法
*
* @param data 签名数据
* @return 签名结果
* @throws NoSuchAlgorithmException 没有此种加密算法异常
* @throws SignatureException 签名异常
* @throws InvalidKeyException 不可用的私钥
*/
private String signature(byte[] data) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, InvalidKeySpecException {
byte[] keyBytes = Base64Utils.decodeFromString(this.privateKey.get());
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(data);
return Base64Utils.encodeToString(Base64Utils.encodeToString(signature.sign()).getBytes());
}
/**
* 数字签名验证
*
* @param data 验签数据
* @param sign 签名
* @return 验签结果
* @throws NoSuchAlgorithmException 没有此种加密算法异常
* @throws SignatureException 签名异常
* @throws InvalidKeyException 不可用的私钥
*/
private boolean verifySignature(byte[] data, String sign) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, InvalidKeySpecException {
byte[] signs = Base64Utils.decode(Base64Utils.decodeFromString(sign));
byte[] publicKey = Base64Utils.decodeFromString(this.publicKey.get());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(pubKey);
signature.update(data);
return signature.verify(signs);
}
@Data
@Builder
private static class CustomKeyPair {
private String privateKey;
private String publicKey;
}
public static void main(String[] args) throws NoSuchAlgorithmException {
System.out.println(CommonUtil.newUUID().toUpperCase(Locale.ROOT));
CustomKeyPair customKeyPair = SignatureTool.I.generateKeyPair();
Long timeStamp = System.currentTimeMillis();
String signature = SignatureTool.I.putPrivateKey(customKeyPair.privateKey).signature(timeStamp+"");
System.out.println(timeStamp);
log.debug(signature);
SignatureTool.I.putPublicKey(customKeyPair.publicKey).verifySignature(timeStamp+"", signature);
}
}
基于MD5+RSA算法实现接口调用防扯皮级鉴权的更多相关文章
- spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法
spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法 前言 本篇接着<spring boot / cloud ...
- 基于JAVA的全国天气预报接口调用示例
step1:选择本文所示例的接口"全国天气预报接口" url:https://www.juhe.cn/docs/api/id/39/aid/87step2:每个接口都需要传入一个参 ...
- python接口自动化22-签名(signature)鉴权(authentication)之加密(HEX、MD5、HMAC-SHA256)
前言 开放的接口为了避免被别人乱调用,浪费服务器资源,这就涉及到签名(Signature)加密了 API 使用签名方法(Signature)对接口进行鉴权(Authentication).每一次请求都 ...
- 基于apache httpclient的常用接口调用方法
现在的接口开发,大部分是基于http的请求和处理,现在整理了一份常用的调用方式工具类 package com.xh.oms.common.util; import java.io.BufferedRe ...
- 基于php的银行卡实名认证接口调用代码实例
银行卡二元素检测,检测输入的姓名.银行卡号是否一致. 银行卡实名认证接口:https://www.juhe.cn/docs/api/id/188 <?php // +-------------- ...
- 基于EasyDSS流媒体服务器实现的直播流管理与鉴权的后台方案
本文转自EasyDSS团队Marvin的博客:http://blog.csdn.net/marvin1311/article/details/73548929 最新版本的EasyDSS流媒体解决方案, ...
- SpringBoot27 JDK动态代理详解、获取指定的类类型、动态注册Bean、接口调用框架
1 JDK动态代理详解 静态代理.JDK动态代理.Cglib动态代理的简单实现方式和区别请参见我的另外一篇博文. 1.1 JDK代理的基本步骤 >通过实现InvocationHandler接口来 ...
- 身份证实名认证接口调用实例(PHP)
基于php的身份证实名认证接口调用代码实例,身份证实名认证接口申请:https://www.juhe.cn/docs/api/id/103 <!--?php // +-------------- ...
- Spring Security 接口认证鉴权入门实践指南
目录 前言 SpringBoot 示例 SpringBoot pom.xml SpringBoot application.yml SpringBoot IndexController SpringB ...
随机推荐
- csp-s模拟测试42「世界线·时间机器·密码」
$t3$不会 世界线 题解 题目让求的就是每个点能到点的数量$-$出度 设每个点能到的点为$f[x]$ 则$f[x]=x \sum\limits_{y}^{y\in son[x]} U f[y]$ 用 ...
- 注册中心ZooKeeper,Eureka,Consul,Nacos对比
简介 服务注册中心本质上是为了解耦服务提供者和服务消费者.对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的.更进一步,为了支持弹性扩缩容特性,一个微服务的提供者 ...
- 《电容应用分析精粹:从充放电到高速PCB设计》最新勘误表
最新勘误表百度云盘下载 链接: https://pan.baidu.com/s/18yqwnJrCu9oWvFcPiwRWvA 提取码: x3e3 (本勘误表仅包含错误相关部分,不包含对语句的 ...
- gomod使用小结
gomod使用小结 使用方法 把工程拷贝到$GOPATH/src之外 在工程目录下执行:go mod init {module name}该命令会创建一个go.mod文件 然后在该目录下执行 go b ...
- Zabbix5.0微信报警
3.1.注测企业微信: 3.2.企业微信注册成功后进入后台管理: 3.3.添加一个部门,并记住部门id: #我这里添加的子部门ID为2 3.4.添加一个用户到上面创建的部门里面(这里采取直接将管理员添 ...
- 8、linux常用命令
8.1.pwd: 显示当前的路径: -L:显示逻辑路径,即快捷方式的路径(默认的参数): -P :显示物理路径,真实的路径: 8.2.man: 命令的查看: 8.3.help: 命令的查看: 8.4. ...
- 35、mysql数据库(ddl)
35.1.数据库之库操作: 1.创建数据库(在磁盘上创建一个对应的文件夹): create database [if not exists] db_name [character set xxx]; ...
- 集合类线程安全吗?ConcurrentModification异常遇到过吗?如何解决?
集合类不安全的问题 1. ArrayList的线程不安全问题 1.1 首先回顾ArrayList底层 ArrayList的底层数据结构是数组 底层是一个Object[] elementData的数组, ...
- ESCMScript(2)Module语法
严格模式 ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";. 严格模式的限制如下 变量必须声明后再使用 函数的参数不能有同名属性,否则报错 不能 ...
- 面试题三:MySQL
MySQL有哪些存储引擎? MyISAM.InnoDB.CSV.Memory等 MyISAM和InnoDB比较: InnoDB MyISAM 事务 支持 不支持 存储限制 64TB 无 锁粒度 行锁 ...