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

56 篇文章 12 订阅 ¥29.90 ¥99.00

logo

SecurityApi v1.0.2

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


一、SecurityApi 介绍

SecurityApi 是一个基于 Java 接口参数加密框架,可以让请求参数解密,响应参数加密,目前支持AES、RSA、SM4加密模式,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.2</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.2</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));
    }

}

2.2 填充AES秘钥

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

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

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

3. SM4加密(对称加密)

2.1 生成SM4秘钥、偏移量

import com.chh.util.SM4Utils;

public class SM4Generate {

    public static void main(String[] args) throws Exception {
        String key = SM4Utils.generateKey();
        System.out.println("Generated Key: " + key);

        String iv = SM4Utils.generateIv();
        System.out.println("Generated IV: " + iv);

        String data = "Hello, this is a test message.";

        String encryptedData = SM4Utils.encrypt(data, key, iv);
        System.out.println("Encrypted data: " + encryptedData);

        String decryptedData = SM4Utils.decrypt(encryptedData, key, iv);
        System.out.println("Decrypted data: " + decryptedData);

        String encryptedDataNotIv = SM4Utils.encrypt(data, key, null);
        System.out.println("Encrypted data not iv: " + encryptedDataNotIv);

        String decryptedDataNotIv = SM4Utils.decrypt(encryptedDataNotIv, key, null);
        System.out.println("Decrypted data not iv: " + decryptedDataNotIv);
    }

}

2.2 填充SM4秘钥

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

security:
  encrypt:
    mode: sm4
    sm4:
      key: 'PysfoQx3kFpL2VDzIswBFw=='
      iv: 'uZCYTh1BPfyNqDjr'

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

四、加密开关控制

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

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

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

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

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

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

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

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 通知
可以使用Spring AOP实现对接口请求和响应的参数加密。具体实现可以参考以下步骤: 1. 定义一个加密工具类,实现参数加密的逻辑。 2. 定义一个切面类,使用@Before和@AfterReturning注解分别在接口请求前和响应后进行参数加密和解密。 3. 在Spring Boot的配置文件中配置切面类。 4. 在接口方法上添加@Encrypt和@Decrypt注解,指定需要加密和解密的参数。 以下是一个简单的示例代码: 加密工具类: ``` public class EncryptUtils { public static String encrypt(String data) { // 实现加密逻辑 } public static String decrypt(String data) { // 实现解密逻辑 } } ``` 切面类: ``` @Aspect @Component public class EncryptAspect { @Before("@annotation(com.example.demo.annotation.Encrypt)") public void encryptRequest(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof String) { String encryptedData = EncryptUtils.encrypt((String) arg); // 替换原始参数 arg = encryptedData; } } } @AfterReturning(value = "@annotation(com.example.demo.annotation.Decrypt)", returning = "result") public void decryptResponse(Object result) { if (result instanceof String) { String decryptedData = EncryptUtils.decrypt((String) result); // 替换返回值 result = decryptedData; } } } ``` 配置文件: ``` @Configuration @EnableAspectJAutoProxy public class AppConfig { @Bean public EncryptAspect encryptAspect() { return new EncryptAspect(); } } ``` 接口方法: ``` @RestController public class UserController { @PostMapping("/user") @Encrypt @Decrypt public User createUser(@RequestBody User user) { // 处理业务逻辑 return user; } } ``` 以上代码仅供参考,实际实现需要根据具体需求进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一碗情深

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值