# EnDecryption
**Repository Path**: fd-a/EnDecryption
## Basic Information
- **Project Name**: EnDecryption
- **Description**: 基于cglib,zookeeper,netty的加解密框架
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-04-03
- **Last Updated**: 2024-09-05
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# EnDecryption框架
## 1. 介绍
EnDecryption框架采用多种设计模式实现高度可定制化,支持本地和远程两种加解密方式的加解密框架
## 2. 软件架构
### 2.1 模块介绍
该框架分为五个模块:
1. EnDecryption-algorithm
算法模块,该模块为其余四个模块的基础
实现其中的IEnDecryption接口或继承AbstractEnDecryptionWithKeyPair抽象类即可定制私人加解密算法
2. EnDecryption-common
通用基础模块,该模块封装了大量通用类和工具类
3. EnDecryption-api
api调用模块,该模块为最重要的模块,采用动态代理,工厂模式,状态链等多种设计模式实现高度可定制化
有两种使用方式,分别为:
1. 使用com.qust.enDecrpytion.annotations包中的注解标记类,再使用com.qust.enDecrpytion.proxy.builder.EnDecryptionProxyBuilder$proxy(T target)方法即可实现加解密调用
2. 直接使用com.qust.enDecrpytion.proxy.builder.EnDecryptionProxyBuilder$proxy(T target, IEnDecryption enDecryption, Class extends AbstractTypeHandle>[] handlers)方法也可以实现加解密调用
4. EnDecryption-netty-server
远程加解密模块,该模块借鉴hadoop,hbase的架构,采用netty+zookeeper搭建加解密集群,该模块扮演从节点
远程集群采用主从架构,将在下章节详细讲解
5. EnDecryption-manager
远程集群管理模块,扮演主节点,使用spring boot搭建
### 2.2 EnDecryption-api模块架构
#### 两次代理操作
原始类在经过一系列操作后会被放在内部加密层中,内部加密层外还有一层解密层
#### 加解密执行顺序
在使用方法时
参数进行加解密的顺序为先进入解密层然后在进入加密层
返回值进行加解密的顺序为先进入加密层然后进入解密层
#### 缓存加解密参数位置
在第一次进行使用被加解密修饰的方法时,会对原始类中需要加解密的参数位置进行进行,并存入到一个中Map>中,方便以后多次调用节省时间
#### TypeHandleContext 加解密状态链
真正的加解密操作并不在加解密层执行,而是委托给了TypeHandleContext,它其中有一个handle链用于处理各种类型的加解密操作,目前只实现了两个类型,string类型和pojo类型,继承AbstractTypeHandle抽象类并在@EnDecryptionConfiguration$customHandlers中配置即可实现特定类型的加解密操作
#### 加解密算法
加解密算法是在解析原始类时装配的,加解密状态链依赖加解密算法,加解密状态链的具体加解密方法为
```java
T handle(T target,boolean cache ,Class extends Annotation> annotation, StatusHandleFunction function) throws IllegalAccessException, Exception;
```
* target :传入需要加解密的参数
* cache :是否需要缓存加解密的结果
* annotation : 执行操作为加密还是解密
* function : 该参数为函数,需要实现StatusHandleFunction$enDecryption(String value)方法
* 该参数的设置是为了简化加解密层的操作
* 在加密层中只需要传入s -> enDecryption.encryption(s)即可
* 在解密层中只需要传入s -> enDecryption.decryption(s)即可
**注意**
```
在使用本地加解密算法时,一定要先使用com.qust.encryptionAndDecryption.KeyPair$generateKeyPair(Class extends IEnDecryption> encryptionAlgorithms, String savePath, String password)方法生成密钥
```
#### 全局缓存

LRU缓存为静态资源,在加解密状态链中可以选择是否缓存加解密结果
**注意:**建议不缓存加解密后的结果,缓存之后相同的原文只会有一种密文,容易遭受攻击
#### 动态代理解析过程
动态代理解析的过程实际上时对@EnDecryptionConfiguration注解解析的过程,该注解相当于配置注解
```java
package com.qust.enDecrpytion.annotations;
import com.qust.enDecrpytion.proxy.status.AbstractTypeHandle;
import com.qust.enDecrpytion.proxy.status.PoJoTypeHandle;
import com.qust.enDecrpytion.proxy.status.StringTypeHandle;
import com.qust.encryptionAndDecryption.IEnDecryption;
import java.lang.annotation.*;
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnDecryptionConfiguration {
Class extends AbstractTypeHandle>[] customHandlers() default {StringTypeHandle.class, PoJoTypeHandle.class}; // 加解密状态链
Local[] local() default {}; // 本地加解密
Remote[] remote() default {}; // 远程加解密
@Documented
@Target({})
@Retention(RetentionPolicy.RUNTIME)
@interface Local { // 该注解为内部注解,用于标注为采用本地加解密方法
Class extends IEnDecryption> enDecryption(); // 加解密算法
String keyPath(); // 密钥存储地址
String password(); // 解析密钥的密码
}
@Documented
@Target({})
@Retention(RetentionPolicy.RUNTIME)
@interface Remote { // 用于表示采用远程加解密方法
String managerAddress() default "localhost:8086"; // 管理节点的地址
int algorithmCode(); // 算法编号
}
// 如果两种注解都配置了,会首先选择本地方法
}
```
#### 总结
EnDecryption-api模块的架构如下图:
该模块提供了两个可拓展点,加解密算法和加解密状态链
接下来的远程加解密就是重写了加解密算法实现,本地架构于该模块相同,只是多了远程加解密集群
### 2.3 远程加解密架构
#### 客户端
##### channel连接池

在该框架中使用的连接池时基于循环队列的连接池,利用循环队列可以提高连接的利用率
可以设置loopool的核心连接数和最大连接数,同时还可以自定义拒绝策略
##### RemoteEnDecryption

RemoteEnDecryption实现了IEnDecryption,是远程加解密的拓展,在该系统中扮演客户端的角色,通过配置RemoteEnDecryption即可实现远程加解密操作
其内部包含LooPool,通过LooPool发送请求
#### 服务端
##### EnDecryption-netty-server
###### 协议
协议内容如下:
* 魔数,4字节 byte[]{'f','a','n','!'}
* 版本号,4字节
* 现在版本为1,为之后更新做准备
* 获取序列化方式,4字节
* 序列化方式有多种,可自定义,该系统使用json
* 获取指令,4字节
* ```java
@Getter
public enum InstructionEnum {
encryption(0), // 加密
decryption(1), // 解密
tokenAuthentication(2), // 权限,还未实现
transmissionAlgorithm(3), // 算法资源传输
response(4), // 回复
algorithmicResourceCatchUp(5), // 算法资源追赶是否成功
@Deprecated
getMD5(6), // 获取metaData的md5码,已弃用
resourceCatchUp(7); // 请求资源追赶
private final int code;
InstructionEnum(int code){
this.code = code;
}
}
```
* 获取序列化对象,4字节
* ```java
@Getter
public enum SerializationEnum {
json(0), // json序列化
java(1), // java序列化
string(2), // string类型序列化
Byte(3); // byte类型序列化
private final int code;
SerializationEnum(int code){
this.code = code;
}
}
```
* 写入长度,4字节 int类型
* 写入内容
* ```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message implements Serializable{
protected int instruction; // 指令,加密,解密,传输算法实例等等
protected int serialization; // 序列化方式,json,java序列化等
protected Object context; // 内容
}
```
###### 集群组建过程
**假设所有子节点的算法资源都是完整的,不需要资源追赶阶段**

每个server开启时会在/EnDecryption/server中注册一个**顺序临时节点** , 并且对/EnDecryption/server节点添加**PathChildrenCache监听器**
* 为什么是**顺序临时节点**,为什么添加**PathChildrenCache监听器**
* 顺序的目的是为了创建一个如图的链式结构,依次连接下一个节点,为**资源追赶**阶段做准备
* 当某一个节点挂掉之后,**临时节点**会消失,此时会触发**PathChildrenCache监听器**,该监听器会重新生成新的链式结构,保障除最后一个节点外的所有节点都有**NextNode**
###### 监控功能
该功能会另起一个线程,开启websocket实时传输此机器的状态
##### EnDecryption-manager
EnDecryption-manager有三个主要功能

###### 负载均衡
启动系统时应先启动EnDecryption-manager,之后再启动EnDecryption-netty-server
**EnDecryption-manager先启动原因:**
1. EnDecryption-manager启动时,会对/EnDecryption/server添加**PathChildrenCache监听器**
2. 当有节点在/EnDecryption/server注册时,获取节点的名称和内容,并缓存
* 节点名称格式为: ip:port;001
* 节点内容格式为:
* ```json
{
weigh:1, // 节点的权重,不变值
currentWeight:0, // 节点的currentWeight,可变
address:localhost:8086 // 节点的地址
}
```
3. manager节点会缓存头节点的地址,并不会立马连接,而是等到需要连接时才连接
负载均衡采用WRR[负载均衡-WRR算法 | 胖虎de文库 (exceting.github.io)](https://exceting.github.io/2020/08/07/负载均衡-WRR算法/) , 每个节点的权重为该机器的cpu核心数
**RemoteEnDecryption(客户端)获取server节点过程**
1. EnDecryption-manager启动并且有server节点注册成功
2. 客户端解析@EnDecryptionConfiguration$remote()参数之后,获取managerAddress
3. 访问http://"+managerAddress+"/LoadBalance/"+numberOfCores,获取核心连接节点的地址
4. 采用懒汉模式,获取之后立即创建连接
5. 将连接缓存到LooPool中
###### 资源追赶
**当新节点的算法资源少于最新的算法资源时,会进入资源追赶阶段**
资源追赶部分特点为:
* 采用链式结构
* 此节点的算法资源只能由上一个节点进行传输
* 头节点的资源由manager节点传输
* 算法资源信息保存在mysql中,只能通过manager节点访问获得
* server节点的算法资源信息保存在,metaData.json中
**资源追赶过程:**
1. manager启动后会处理metaDate.json,并存储到/EnDecryption/config/meta中
2. server启动后会查询/EnDecryption/config/meta中的内容,并于自己本地的metaDate.json对比
3. 如果一致,不做任何处理,manager将节点内容缓存到负载均衡器中
4. 如果不一致,server节点计算缺失算法的id,并发送请求
* post请求
* http://+ manager +/algorithmController/catchUp
* ```json
{
missing:set(), // 缺少的算法id
address:"" // 需要追赶的地址
}
```
5. 同时,该节点会拿到zookeeper的分布式锁
* **为什么要使用分布式锁**
* 当有多个需要追赶的server节点注册之后会同时请求,但是上一个节点的资源还处于资源追赶阶段,所以无法传输算法资源,此时就容易导致错误
* 解决方案: 当该节点拿到分布式锁之后才会发送请求,此时它的上一个节点肯定已经到达最新资源位置
6. manager节点连接缓存的头节点,发送资源追赶指令,让其上一个节点发送算法资源
7. 当该节点的算法资源达到最新时,连接下一个节点(如果有的话),释放锁
8. manager将其写入负载均衡器
###### 算法的上传和删除
**上传**:
1. 通过POST http:// + manager + /algorithmController接口上传给manager节点
2. manager节点在将该算法资源传给头节点
3. 通过链式结构挨个传输
4. server节点传输完成之后会回复成功给manager节点
5. manager节点回复完成传输
**删除**:
删除资源只是删除了metaData.json中的信息,并没有实际删除具体的jar包
## 3. 使用教程
```java
package com.qust;
import com.qust.enDecrpytion.annotations.Decryption;
import com.qust.enDecrpytion.annotations.EnDecryptionConfiguration;
import com.qust.enDecrpytion.annotations.EnableEnDecryption;
import com.qust.enDecrpytion.annotations.Encryption;
import com.qust.enDecrpytion.encryptionAndDecryption.local.SM2;
import com.qust.enDecrpytion.proxy.builder.EnDecryptionProxyBuilder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@EnDecryptionConfiguration(
// remote = @EnDecryptionConfiguration.Remote(
// algorithmCode = 17
// ),
local = @EnDecryptionConfiguration.Local(
enDecryption = SM2.class,
keyPath = "EnDecryption-api/src/test/java/com/qust/fan.properties",
password = "fan"
)
)
public class Hello {
private int id;
public Hello(int id) {
this.id = id;
}
public Hello() {
}
@EnableEnDecryption
public @Encryption String hello(@Encryption String name, @Encryption Demo demo) {
System.out.println("name:\n" + name);
System.out.println("demo.id:\n" + demo.getId());
return "1";
}
public static void main(String[] args) throws Exception {
Hello hello = EnDecryptionProxyBuilder.proxy(new Hello(1));
System.out.println("hello返回值:\n" + hello.hello("asd",new Demo("asd", "dasd")));
System.out.println(hello);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Demo {
@Decryption
private String name;
@Encryption
private String id;
}
}
```
运行结果:
```
name:
04cf2201341f2979ff876d03531526b6953ac66556869a2c1b2a1ad8aca58ba8db44251c120cff8cc15e92b8585d12f357074bc7d83dd8f9764330adef3921bec8f8380ad41e2d2b63ffdb7e70772847a9c67e24843f7b700ef415b801b8f963b7c8fccb3c
demo.id:
048092962fb270cefeaf66067781700dafbb2a4de827e389acc3226ec73562e550f88e29d8edc9f81938f3be53f8e48489c58bcc810f7221e3cc37678d8c7ba610e7c0c60d5a52c09b21372bcc45c9fd7a02bbf9e54bd39c9a2975bc295530e0bd529e6d3bbe340edd
hello返回值:
04028171b828b288725cc5b090c03380f341cd18b91434a68058bee9767425f780b0ac792a24fb8af8b74d8651b0bfb2cb9fe4fae28e298241c3235ebb32d18422d7ec868610b03076a19d328844a6a998e2bec5270ed20fdf4336e211060f39433d4c7e12
Hello(id=1)
```
## 使用说明
1. 测试版本,肯定会有许多bug,敬请原谅