概述

最近项目中需要对第三方开发接口调用,考虑了一下,准备采用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. 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑

    目录 系列文章 领域逻辑和应用逻辑 多应用层 示例:正确区分应用逻辑和领域逻辑 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落 ...

  2. Docker与k8s的恩怨情仇(三)—后浪Docker来势汹汹

    转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 上一节我们为大家介绍了Cloud Foundry等最初的PaaS平台如何解决容器问题,本文将为大家展示Doc ...

  3. CapsuleAO实现的学习

    正是一个炎夏,又到了整活的好时候.最近抽些时间研究下CapsuleAO,记述实践体会. 1.简介 这是一个通过在角色骨骼上绑定虚拟胶囊体并以数学方法实现胶囊近似的AO环境光遮蔽效果的方法, 当角色处于 ...

  4. python画图库及函数,绘制图片从文件提取出来的数据集转化为int,不然作为坐标轴的时候因为是字符串而无法排序

    转化int:  

  5. Windows10 准备/安装Flutter研发环境

    Flutter简介 Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动.Web.桌面和嵌入式平台. Flutter 中国 安装Flutte ...

  6. 日常BUG-01 之 @Sl4j

    问题描述: 需要打印日志,使用的是lombok包中的sl4j,lombok依赖如下: <dependency> <groupId>org.projectlombok</g ...

  7. 8、mysql乱码问题及字符集实战

    8.1.mysql插入中文数据乱码案例: mysql建库的字符集为latin1,客户端的字符集为utf8; use lc; 1.查看库的编码: mysql> show create databa ...

  8. Redis 底层数据结构之链表

    文章参考:<Redis设计与实现>黄建宏 链表 链表提供了高效的节点重排能力,以及可以顺序访问,也可以通过增删节点灵活调整链表长度,Redis中的列表.发布订阅.慢查询.监视器等功能均用到 ...

  9. 【玩转 WordPress】基于 Serverless 搭建个人博客图文教程,学生党首选!

    以下内容来自「玩转腾讯云」用户原创文章,已获得授权. 01. 什么是 Serverless? 1. Serverless 官方定义 Serverless 中的 Server是服务器的意思,less 是 ...

  10. Linux指令手册 (一)

    指令格式 指令主体 [选项] [操作对象] 一个完整的指令是由"指令主体"."选项"和"操作对象"组成的,其中指令主体只能有一个,选项有零个 ...