概述

最近项目中需要对第三方开发接口调用,考虑了一下,准备采用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算法实现接口调用防扯皮级鉴权的更多相关文章

  1. spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法

    spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法 前言 本篇接着<spring boot / cloud ...

  2. 基于JAVA的全国天气预报接口调用示例

    step1:选择本文所示例的接口"全国天气预报接口" url:https://www.juhe.cn/docs/api/id/39/aid/87step2:每个接口都需要传入一个参 ...

  3. python接口自动化22-签名(signature)鉴权(authentication)之加密(HEX、MD5、HMAC-SHA256)

    前言 开放的接口为了避免被别人乱调用,浪费服务器资源,这就涉及到签名(Signature)加密了 API 使用签名方法(Signature)对接口进行鉴权(Authentication).每一次请求都 ...

  4. 基于apache httpclient的常用接口调用方法

    现在的接口开发,大部分是基于http的请求和处理,现在整理了一份常用的调用方式工具类 package com.xh.oms.common.util; import java.io.BufferedRe ...

  5. 基于php的银行卡实名认证接口调用代码实例

    银行卡二元素检测,检测输入的姓名.银行卡号是否一致. 银行卡实名认证接口:https://www.juhe.cn/docs/api/id/188 <?php // +-------------- ...

  6. 基于EasyDSS流媒体服务器实现的直播流管理与鉴权的后台方案

    本文转自EasyDSS团队Marvin的博客:http://blog.csdn.net/marvin1311/article/details/73548929 最新版本的EasyDSS流媒体解决方案, ...

  7. SpringBoot27 JDK动态代理详解、获取指定的类类型、动态注册Bean、接口调用框架

    1 JDK动态代理详解 静态代理.JDK动态代理.Cglib动态代理的简单实现方式和区别请参见我的另外一篇博文. 1.1 JDK代理的基本步骤 >通过实现InvocationHandler接口来 ...

  8. 身份证实名认证接口调用实例(PHP)

    基于php的身份证实名认证接口调用代码实例,身份证实名认证接口申请:https://www.juhe.cn/docs/api/id/103 <!--?php // +-------------- ...

  9. Spring Security 接口认证鉴权入门实践指南

    目录 前言 SpringBoot 示例 SpringBoot pom.xml SpringBoot application.yml SpringBoot IndexController SpringB ...

随机推荐

  1. 面试官:为什么Mysql中Innodb的索引结构采取B+树?

    前言 如果面试官问的是,为什么Mysql中Innodb的索引结构采取B+树?这个问题时,给自己留一条后路,不要把B树喷的一文不值.因为网上有些答案是说,B树不适合做文件存储系统的索引结构.如果按照那种 ...

  2. 「是时候升级Java11了」 JDK11优势和JDK选择

    Java8 商用收费 从2019年1月份开始,Oracle JDK 开始对 Java SE 8 之后的版本开始进行商用收费,确切的说是 8u201/202 之后的版本.如果你用 Java 开发的功能如 ...

  3. 关于Mysql事务,你必须知道的几个知识点!

    Transaction事务 上期我们讲到了jpa的常用操作,查询.更新.删除等,但是如果在操作数据库事务时发生异常,数据会回滚吗?下面我们来看个例子 UserController新增如下代码: @Ge ...

  4. go语言结构体内存对齐

    cpu要想从内存读取数据,需要通过地址总线,把地址传输给内存,内存准备好数据,输出到数据总线,交给cpu,如果地址总线只有8根,那这个地址就只有8位可以表示[0,255]256个地址,因为表示不了更多 ...

  5. 17、lnmp_php编译安装

    17.1.FastCGI介绍: 1.什么是CGI: CGI的全称为"通用网关接口",为http服务器与其他机器上的程序服务通信交流的一种工具,CGI程序 必须运行在网络服务器上:传 ...

  6. 利用C语言输出一个二元一次函数

    #include<stdio.h> #include<math.h> int main(void)//其实void我认为在这可有可无 {  double a,b,c,x1,x2 ...

  7. ios多线程开发基础

    多线程编程:下载数据时,开辟子线程,减少阻塞时间,和主线程并发运行,提升用户体验 1.Thread 1>新建Thread对象,带一selector方法,调用start方法,开启子线程 2> ...

  8. FirstDay

    昨天心血来潮,想着注册一博客,没想到今天再登时,审阅就通过了,多少有点庆辛.从今天起,我也算是有博客的人了! 为什么选博客园开通?好多IT论坛里都允许有博文,CSDN感觉过于高大上,其他系列论坛大多内 ...

  9. ESP32-默认事件循环

    默认的事件循环是一个事件循环的系统应用发布和处理事件(例如,Wi-Fi无线事件). 基于ESP-IDF4.1 1 #include "esp_log.h" 2 #include & ...

  10. DHCP部署与安全

    1.DHCP作用 (Dynamic Host Configure Protocol)自动分配ip地址 2.DHCP相关概念 地址池/作用域:(IP.子网掩码.网关.DNS.租期),DHCP协议端口是U ...