首页 > 编程语言 >一个基于 Java 接口参数加密框架,让接口参数加密变得简单、优雅!

一个基于 Java 接口参数加密框架,让接口参数加密变得简单、优雅!

时间:2024-08-13 17:54:53浏览次数:17  
标签:公钥 私钥 解密 接口 参数 key 加密 客户端

logo

SecurityApi v1.0.1

一个基于 Java 接口参数加密框架,让接口参数加密变得简单、优雅!

文章目录


一、SecurityApi 介绍

SecurityApi 是一个基于 Java 接口参数加密框架,可以让请求参数解密,响应参数加密,目前支持AES、RSA加密模式,RSA采用分段加密的方式。

核心功能示例:

在这里插入图片描述

无感接收明文数据,并且可以使用 @Validated 校验参数,响应数据无需额外操作,自动加密返回。

在这里插入图片描述

二、SecurityApi 依赖

<dependencies>
    <!-- SpringBoot2.x 依赖 -->
    <!-- https://mvnrepository.com/artifact/io.github.chenhanhui/security-api-spring-boot-starter -->
    <dependency>
        <groupId>io.github.chenhanhui</groupId>
        <artifactId>security-api-spring-boot-starter</artifactId>
        <version>1.0.1</version>
    </dependency>

    <!-- SpringBoot3.x 依赖 -->
    <!-- https://mvnrepository.com/artifact/io.github.chenhanhui/security-api-spring-boot3-starter -->
    <dependency>
        <groupId>io.github.chenhanhui</groupId>
        <artifactId>security-api-spring-boot3-starter</artifactId>
        <version>1.0.1</version>
    </dependency>
</dependencies>

三、使用

1. RSA加密(非对称加密)

1.1 简单示例

在启动类中添加 @EnableSecurityParameter 注解启动 SecurityApi 功能:

@SpringBootApplication
@EnableSecurityParameter
public class SecurityApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityApiApplication.class, args);
    }

}

如果使用RSA加密,需要在 application.yml 添加以下代码:

security:
  encrypt:
    mode: rsa
    rsa:
      private-key: 'MIIEvAIBADAN...PIUg=='
      client-public-key: 'MIIBIjAN...37zAEwIDAQAB'

注意:private-key 是服务器私钥,client-public-key 是客户端公钥,默认为 2048 位。

密钥是有两对,服务器公钥和私钥,客户端公钥和私钥。

公钥双方都会有(包括对方的),私钥只有自己拥有自己的,不会服务器有客户端私钥,或者客户端有服务器私钥。

  1. 当客户端向服务器发送数据请求时:

客户端用服务器的公钥进行数据加密,用客户端的私钥进行签名。

  1. 服务器接收数据后:

服务器用客户端的公钥进行验签,用服务器私钥进行数据的解密。

  1. 当服务器响应客户端数据结果时:

服务器是用客户端的公钥进行数据加密,用服务器私钥进行签名。

  1. 客户端接收数据后:

客户端就用服务器公钥进行验签,用客户端的私钥进行解密。

这一切不需要开发者关心,SecurityApi 框架已经帮你做好了。

1.2 生成RSA密钥

我们只需使用以下代码生成 RSA 公钥和私钥,需要生成两对,分别是客户端公钥和私钥,服务器公钥和私钥:

import com.chh.util.RSAUtils;
import java.util.Map;

public class RSAGenerate {

    public static void main(String[] args) {
        int bits = 2048;
        Map<String, String> keyMap = RSAUtils.generateKeyPair(bits);
        String publicKeyStr = keyMap.get("publicKey");
        String privateKeyStr = keyMap.get("privateKey");
        System.out.println("=======================================");
        System.out.println("bits:" + bits);
        System.out.println("publicKey:" + publicKeyStr);
        System.out.println("privateKey:" + privateKeyStr);
        System.out.println("=======================================");
    }

}

在 SecurityApi 中,一行代码解决参数解密加密,只需在类或者方法上添加 @SecurityParameter 注解, 如下:

@RestController
@RequestMapping("/author")
@SecurityParameter
public class AuthorController implements SecurityBuilder {
    
    /**
     * 请求解密,响应加密
     *
     * @param author Author对象
     * @return 返回加密后的数据 ResponseBody<SecurityResult>格式
     */
    @PostMapping("/inDecodeOutEncode")
    public ResponseEntity<SecurityResult> inDecodeOutEncode(@RequestBody @Validated Author author) {
        author.setUrl("https://blog.csdn.net/xiaohuihui1400");
        return success(author);
    }
    
}

1.3 加签名说明

「第一个场景」B要给A传递一条加密消息

RSA的加密过程如下:

A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。

A传递自己的公钥给B,B用A的公钥对消息进行加密。

A接收到B加密的消息,利用A自己的私钥对消息进行解密。

「第二个场景」B要给A传递一条加密消息,A验证B发的加密消息没有被篡改

RSA签名的过程如下:

A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。

B生成一对密钥(公钥和私钥),私钥不公开,B自己保留。公钥为公开的,任何人可以获取。

A传递自己的公钥给B,B传递自己的公钥给A。

B用A的公钥对消息进行加密,再用自己的私钥对加密消息加签,形成签名,并将加签的消息和加密消息本身一起传递给A。

A收到消息后,获取B的公钥进行验签,如果验签出来的内容与消息本身一致,证明消息是B回复的。

A再用自己的私钥对加密消息解密。

综合两个场景你会发现,第一个场景主要用于防止用户抓包,虽然被截获的消息没有泄露,但是可以利用截获的公钥,将假指令进行加密,然后传递给A。

第二个场景同时使用加密和签名,A和B都有一套自己的公钥和私钥,当A要给B发送消息时,先用B的公钥对消息加密,再对加密的消息使用A的私钥加签名,达到既不泄露也不被篡改,更能保证消息的安全性。

开启和关闭RSA签名,需要在 application.yml 修改 security.encrypt.rsa.sign 的值:

security:
  encrypt:
    mode: rsa
    rsa:
      private-key: 'MIIEvAIBADAN...PIUg=='
      client-public-key: 'MIIBIjAN...37zAEwIDAQAB'
      sign: true # 默认开启签名

1.4 密钥传递说明

在前后端通信过程中,实现双向加密通常采用HTTPS协议来保证数据传输的安全性。HTTPS协议基于SSL/TLS协议,能够确保数据在传输过程中的加密,从而保护数据不被第三方窃取或篡改。在这种情况下,公钥的传递通常是通过SSL/TLS握手过程自动完成的,不需要开发者手动干预。

然而,如果你的场景是指在应用层面上,前后端之间需要实现额外的加密通信(例如,使用RSA加密算法),并且你想要手动控制公钥的传递过程,那么可以考虑以下几种方式:

  1. 通过HTTPS安全通道传递公钥:即使是应用层面的加密,也强烈建议在HTTPS协议的基础上进行。在建立了安全的通信通道后,可以将公钥以普通数据的形式发送给客户端。这种方式的优点是实现简单,缺点是每次会话都需要传递公钥。

  2. 公钥嵌入到客户端:在客户端(如Web前端或移动应用)发布时,可以将公钥直接嵌入到客户端代码或配置中。这样,客户端在需要加密数据时,可以直接使用嵌入的公钥进行加密。这种方式的优点是减少了公钥传递的次数,提高了效率,但缺点是更新公钥时需要更新客户端。

  3. 通过API接口动态获取公钥:后端提供一个API接口,用于动态获取公钥。客户端在需要进行加密通信之前,先调用这个API获取公钥。这种方式相比直接嵌入公钥,更加灵活,便于公钥的更新和管理,但会增加一次网络请求。

  4. 使用证书:类似于HTTPS使用的机制,可以为后端服务创建一个证书,并将公钥包含在证书中。客户端通过验证证书的合法性来获取公钥。这种方式增加了安全性,但实现起来相对复杂。

无论采用哪种方式,都需要确保公钥的传递和存储过程安全,避免公钥在传递过程中被截获或在客户端被篡改。同时,还需要注意公钥的更新和管理策略,确保加密机制的安全性和可靠性。

在 SecurityApi 框架中,主要使用以上第二种方式,并且保留了公钥的传递方式,例如服务端始终只有一对密钥,而每个不同的用户都有自己的密钥,那么服务端需要获取用户的公钥才能完成加密,在application.yml 中将 client-public-key 的删除:

security:
  encrypt:
    mode: rsa
    rsa:
      private-key: 'MIIEvAIBADAN...PIUg=='

在 Controller 中,通过查询数据库、缓存或者客户端传递的公钥在 @ModelAttribute 注解的方法中进行填充: request.setAttribute(SecurityConstant.CLIENT_PUBLIC_KEY, clientPublicKey),如下所示:

@RestController
@RequestMapping("/author")
public class AuthorController implements SecurityBuilder {

    @ModelAttribute
    public void addAttributes(HttpServletRequest request) {
        String clientPublicKey = "MIIBIjAN...37zAEwIDAQAB";
        request.setAttribute(SecurityConstant.CLIENT_PUBLIC_KEY, clientPublicKey);
    }

    /**
     * 请求解密,响应加密
     *
     * @param author Author对象
     * @return 返回加密后的数据 ResponseBody<SecurityResult>格式
     */
    @PostMapping("/inDecodeOutEncode")
    @SecurityParameter
    public ResponseEntity<SecurityResult> inDecodeOutEncode(@RequestBody @Validated Author author) {
        author.setUrl("https://blog.csdn.net/xiaohuihui1400");
        return success(author);
    }

}

1.5 分段加密

RSA密钥位数默认 2048 位,若使用 1024 位密钥,需要修改 key-size,SecurityApi 框架会自动根据该值进行分段加密,分段数据使用 ; 分割。

security:
  encrypt:
    mode: rsa
    rsa:
      private-key: 'MIIEvAIBADAN...PIUg=='
      client-public-key: 'MIIBIjAN...37zAEwIDAQAB'
      key-size: 1024 # 未填写默认RSA密钥位数2048

2. AES加密(对称加密)

2.1 生成AES秘钥、偏移量

import com.chh.util.AESUtils;

public class AESGenerate {

    public static void main(String[] args) throws Exception {
        int bit = 256;
        String key = AESUtils.generateKey(bit);
        String iv = AESUtils.generateIv();
        System.out.println("AES Key: " + key);
        System.out.println("AES Iv: " + iv);
        System.out.println("'123456' encrypt: " + AESUtils.encrypt("123456", key, iv));
        System.out.println("'123456' encrypt not iv: " + AESUtils.encrypt("123456", key));
    }

}

使用AES加密,需要在 application.yml 添加以下代码:

security:
  encrypt:
    mode: AES
    aes:
      key: 'dBV5Awv8Ji6kIE/QledA1+MHnF3up8ur'
      iv: 'kYw1hVScyQOV2LO/'

iv 表示偏移量,可以使加密更加安全可靠,不易破解,如果不需要 iv 的可以将其属性删除。

3. 全局和局部解密、加密、日志打印控制

application.yml 可以控制全局请求参数解密、响应参数加密、解密加密日志打印:

security:
  encrypt:
    in-decode: true    # 默认请求参数解密
    out-encode: true   # 默认响应参数加密
    show-log: true     # 默认打印请求参数、响应参数解密加密前后的数据

控制局部需要在 @SecurityParameter 注解中设置参数,如下:

@SecurityParameter(inDecode = false, outEncode = false, showLog = false)

@SecurityParameter 注解可以加在类上或者方法上,方法的优先级更高。

4. 全局和局部解密前、签名、解密后数据缓存控制

application.yml 可以控制全局解密前、签名、解密后数据缓存:

security:
  encrypt:
    cache-data: true   # 默认缓存请求加密、签名数据,以及解密之后的数据到 request

控制局部需要在 @SecurityParameter 注解中设置参数,如下:

@SecurityParameter(cacheData = false)

关闭了数据缓存,未关闭日志打印,结果如下所示:

缓存数据

四、常见问题

1. 为什么未填写密钥而会自动加密?

在启动类中添加 @EnableSecurityParameter 注解启动加密功能之后,SecurityApi 框架会自动帮我们做初始化,如果未填写服务端私钥,会生成随机密钥并自动填充。

在这里插入图片描述

2. 异常:Data must not be longer than 117 bytes

security.encrypt.rsa.key-size 的值默认为 2048,当密钥的位数不等于(小于)该值,则会抛出异常,表示分段加密失败,将 security.encrypt.rsa.key-size 的值修改成和密钥位数一致即可。

在这里插入图片描述

3. 异常:Bad signature length: got 255 but was expecting 256

该异常非 SecurityApi 框架原因,而是前端使用了 jsencrypt 3.3.2 依赖,jsencrypt 的签名存在Bug,导致签名结果偶现异常,所以 SecurityApi 框架验签失败。

签名有时错误的原因:https://github.com/travist/jsencrypt/pull/280

五、代码托管

六、交流群

QQ交流群:982597743 点击加入

加入群聊的好处:

  • 第一时间收到框架更新通知
  • 第一时间收到框架 bug 通知

标签:公钥,私钥,解密,接口,参数,key,加密,客户端
From: https://blog.csdn.net/xiaohuihui1400/article/details/140759490

相关文章

  • 数据加密存储:重要文件怎么加密?一文全搞懂!
    数据已成为企业和个人最宝贵的资产之一。然而,随着网络攻击和数据泄露事件的频发,如何保护数据安全成为了每个人都需要面对的重要问题。数据加密存储作为保障数据安全的重要手段之一,其重要性不言而喻。本文将详细介绍数据加密存储的基本概念、常见的加密方式以及如何对重要文......
  • 文件保护软件有哪些?8大文件安全加密管理软件大盘点(合集篇)
    文件安全已成为企业和个人不可忽视的重要问题。为了保护敏感数据不被非法访问、泄露或篡改,各种文件保护软件应运而生。本文将为您盘点八款备受推崇的文件安全加密管理软件,帮助您更好地保护数据安全。1.域智盾软件该软件是一款功能强大的文件保护软件,通过先进的加密技术,对......
  • 短信接口-短信API-短信发送
    接口简介:短信验证码api,营销短信群发功能,群发短信可自动过滤无效号码后再发送。节约运营成本,每超过70个字符按一条短信计费。通过后台添加签名,再添加模版接口地址:https://www.wapi.cn/api_detail/86/204.html批量功能:https://www.wapi.cn/batch/detail_86.html网站地址:htt......
  • Windows出现出现身份验证错误。要求的函数不受支持 远程计算机: 10.17.1.2 这可能是由
    Windows出现出现身份验证错误。要求的函数不受支持远程计算机:10.17.1.2这可能是由于CredsSP加密数据库修正。若要了解详细信息,请访问https://go.microsoft.com/fwlink/?linkid=866660解决方案解决方法第一步点开控制面板选择系统与安全第二步选择“允许远程访问......
  • JavaSE基础(5)——抽象类与接口
    目录1、abstract关键字 2、抽象方法3、抽象类4、接口5、接口与抽象类的区别6、JDK8版本接口新特性7、类之间的关系8、UML类图1、abstract关键字 可以修饰类和方法,不可以修饰属性,对应的类和方法就称之为抽象类和抽象方法;2、抽象方法有抽象方法的类一定是抽象类......
  • js逆向md5加密算法获取大学排行榜,页码时间戳影响响sign
    importjsonimportrequestsfromhashlibimportmd5defget_md5(s):m=md5()m.update(s.encode())returnm.hexdigest()n="{app_id=98357f659cf8fb6001cff80f7c6b85f2&diploma_id=7&page=4&page_len=20&platform=desktop&ts=1......
  • apifox进行https接口测试
    SSL双向认证-SpringBoot项目_apifox添加证书-CSDN博客说是SSL证书验证开启,可能验证不通过,根据实际情况来单向认证,客户端证书就不必上传了注:这里我上传的是p12证书,所以选择下图所示上传另外,这里用的是rsa2048,SM2试了,貌似有问题,如果只是简单的get,可以考虑用奇安信等支持......
  • H5 html单页面实现对接接口,获取接口数据
    一、AJAX的一种实现方式,XMLHttpRequestvarxhr=newXMLHttpRequest();xhr.open("POST","你的接口URL",true);xhr.setRequestHeader("Content-Type","application/json;charset=UTF-8");//准备发送的数据vardata=JSON.stringif......
  • CH9141 APP配置参数
    1.CH9141手册第八章节,讲述了蓝牙配置接口,CH9141工作在蓝牙从机模式下,可以通过蓝牙对芯片进行配置操作。配置通道是自定义传输通道,UUID为0XFFF3。传输格式严格按照帧传输,在传输时将一帧拆分成几包蓝牙传输的数据传输。蓝牙读取应答也是需要将一帧数据读完才能结束。一.蓝牙配......
  • ElasticSearch接口
    DSL语法DSL为ES过滤数据时的语法,可用于查询、删除等操作基本构成默认分页查询,size默认为10。ES查询默认最大文档数量限制为10000,可通过index.max_result_window配置来控制,建议考虑通过滚动查询或其他策略实现超过10000限制的查询{"query":{"match":{......