前言
相信大家都有這樣一個(gè)煩惱,就是經(jīng)常會(huì)接到各種推銷、廣告的電話和短信,如果你沒(méi)有在他那里留下過(guò)聯(lián)系方式,他又是如何得到了你的聯(lián)系方式呢?毫無(wú)疑問(wèn),是個(gè)人信息被泄漏了。個(gè)人信息的泄漏有人為不合法謀利的因素,也有系統(tǒng)不合理的安全設(shè)計(jì)造成泄漏的因素。當(dāng)然系統(tǒng)設(shè)計(jì)的角度出發(fā),敏感信息需要加密存儲(chǔ)的,數(shù)據(jù)展示的時(shí)候也要進(jìn)行相應(yīng)的脫敏處理,但是從一些關(guān)于個(gè)信息泄漏的新聞報(bào)道來(lái)看,有好多的網(wǎng)站后臺(tái)竟然是“裸奔”狀態(tài),簡(jiǎn)直太可怕了。其實(shí)敏感數(shù)據(jù)的處理也不復(fù)雜,說(shuō)到底是安全意識(shí)不強(qiáng)。當(dāng)然,這篇文章和大家分享的重點(diǎn)是加密和解密的方法,不是數(shù)據(jù)安全的重要性。
基本概念
敏感數(shù)據(jù)
敏感數(shù)據(jù)是指那些泄漏后可能會(huì)給社會(huì)或個(gè)人造成嚴(yán)重危害的數(shù)據(jù),以個(gè)人隱私信息為例,如手機(jī)號(hào)碼、家庭住址、郵箱、身份證號(hào)、銀行卡帳號(hào)、購(gòu)物網(wǎng)站的支付密碼、登陸密碼等等。另外從社會(huì)的角度出發(fā),也有很多數(shù)據(jù)是屬于敏感數(shù)據(jù),如:居民的生物基因信息等等。
數(shù)據(jù)加密
數(shù)據(jù)加密是指對(duì)數(shù)據(jù)重新編碼來(lái)保護(hù)數(shù)據(jù),獲取實(shí)際數(shù)據(jù)的唯一辦法就是使用密鑰解密數(shù)據(jù);
數(shù)據(jù)解密
數(shù)據(jù)解密與數(shù)據(jù)加密是相對(duì)的,即使用密鑰對(duì)加密的數(shù)據(jù)進(jìn)行解密的過(guò)程;
加密方式
加密的方式,一般是兩種:對(duì)稱加密和非對(duì)稱加密;
對(duì)稱加密只有一個(gè)秘鑰,加密和解密都是用同一個(gè)秘鑰,如AES、DES等;
非對(duì)稱加密有兩個(gè)秘鑰,一個(gè)是公鑰,一個(gè)是私鑰。使用公鑰對(duì)數(shù)據(jù)進(jìn)行加密,加密后的數(shù)據(jù)只有私鑰可以解密,一般公鑰是公開的,私鑰是不公開的;如RSA、DSA等;
實(shí)現(xiàn)原理
Springboot項(xiàng)目中,客戶端通過(guò)接口向服務(wù)端讀取或?qū)懭朊舾袛?shù)據(jù)時(shí),常會(huì)有這樣的業(yè)務(wù)需求:
1、在客戶端向服務(wù)器端發(fā)起寫入請(qǐng)求,服務(wù)端需要對(duì)寫入的敏感數(shù)據(jù)進(jìn)行加密后存儲(chǔ);
2、在客戶端從服務(wù)器端向外讀取數(shù)據(jù)的時(shí)候,需要對(duì)輸出的敏感數(shù)據(jù)進(jìn)行解密;
顯然這種場(chǎng)景,對(duì)于加密的方式的選擇,對(duì)稱加密是最好的選擇;那么如何實(shí)現(xiàn)對(duì)寫入請(qǐng)求、讀取請(qǐng)求的敏感數(shù)據(jù)的加密、解密處理呢?解決方案如下:
1、自定義兩個(gè)切面注解,分別是加密切面注解、解密切面注解,作用于需要加密或解密的敏感數(shù)據(jù)處理的業(yè)務(wù)處理類的具體業(yè)務(wù)處理方法上;
2、自定義兩個(gè)敏感字段處理注解,分別是加密字段注解、解密字段注解,作用于需要輸入或輸出的對(duì)象的敏感字段上;如果輸入對(duì)象上標(biāo)記了加密字段注解,則表示該字段在對(duì)內(nèi)寫入數(shù)據(jù)庫(kù)的時(shí)候,需要加密處理;同理,如果輸出對(duì)象上標(biāo)記了解密字段注解,則表示該字段在對(duì)外輸出的時(shí)候,需要進(jìn)行解密;
3、使用面向切面編程,定義兩個(gè)切面類,分別是加密切面類和解密切面類,選擇Spring AOP的環(huán)繞通知來(lái)具體實(shí)現(xiàn);加密切面類中,以注解的方式定義切入點(diǎn),用到的注解就是自定義的加密切面注解;
4、如果新增、編輯等寫入類的業(yè)務(wù)請(qǐng)求處理方法上標(biāo)記了加密切面注解,那么寫入請(qǐng)求在正式被業(yè)務(wù)處理方法處理前,會(huì)命中加密切面類,加密切面類的環(huán)繞通知方法被觸發(fā),然后根據(jù)輸入的參數(shù)對(duì)象中的字段是否標(biāo)記了自定義的加密字段注解,來(lái)決定是否對(duì)當(dāng)前字段進(jìn)行加密處理;
5、同理,如果是查詢等讀取類的業(yè)務(wù)請(qǐng)求處理方法上標(biāo)記了解密切面注解,那么讀取請(qǐng)求被業(yè)務(wù)處理類處理完之后,會(huì)命中解密切面類,解密切面類的環(huán)繞通知方法被觸發(fā),然后根據(jù)返回對(duì)象的字段是否標(biāo)記了解密字段注解,來(lái)決定是否對(duì)當(dāng)前字段進(jìn)行解密處理。
實(shí)現(xiàn)方案
環(huán)境配置
jdk版本:1.8開發(fā)工具:Intellij iDEA 2020.1
springboot:2.3.9.RELEASE
mybatis-spring-boot-starter:2.1.4
依賴配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
示例時(shí)序圖
圖片
示例代碼
1、自定義四個(gè)注解:@DecryptField(解密字段注解)、@EncryptField(加密字段注解)、@NeedEncrypt(解密切面注解)、@NeedEncrypt(加密切面注解),其中@DecryptField作用于需要解密的字段上;@EncryptField作用于需要加密的字段上;@NeedEncrypt作用于需要對(duì)入?yún)?shù)進(jìn)行加密處理的方法上;@NeedDecrypt作用于需要對(duì)返回值進(jìn)行解密處理的方法上;
//解密字段注解
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptField {
}
//加密字段注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {
}
//作用于對(duì)返回值進(jìn)行解密處理的方法上
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedDecrypt {
}
//作用于需要對(duì)入?yún)?shù)進(jìn)行加密處理的方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedEncrypt {
}
2、把自定義的加密字段注解、解密字段注解標(biāo)記在需要加密或者解密的字段上;這里表示在寫入人員的手機(jī)號(hào)碼、身份證號(hào)碼、家庭住址門牌號(hào)碼時(shí),要進(jìn)行加密處理;在讀取人員的手機(jī)號(hào)碼、身份證號(hào)碼、家庭住址門牌號(hào)碼時(shí),要進(jìn)行解密處理;
@Slf4j
@Data
public class Person {
private Integer id;
private String userName;
private String loginNo;
@EncryptField
@DecryptField
private String phoneNumber;
private String sex;
@DecryptField
@EncryptField
private String IDCard;
private String address;
@EncryptField
@DecryptField
private String houseNumber;
}
3、把@NeedEncrypt和@NeedDecrypt標(biāo)記在需要對(duì)入?yún)?shù)、返回值中的敏感字段進(jìn)行加密、解密處理的業(yè)務(wù)處理方法上;
@RestController
@RequestMapping("/person")
@Slf4j
public class PersonController {
@Autowired
private IPersonService personService;
//添加人員信息
@PostMapping("/add")
@NeedEncrypt
public Person add(@RequestBody Person person, Model model) {
Person result = this.personService.registe(person);
log.info("http://增加person執(zhí)行完成");
return result;
}
//人員信息列表查詢
@GetMapping("/list")
@NeedDecrypt
public List<Person> getPerson() {
List<Person> persons = this.personService.getPersonList();
log.info("http://查詢person列表執(zhí)行完成");
return persons;
}
//人員信息詳情查詢
@GetMapping("/{id}")
@NeedDecrypt
public Person get(@PathVariable Integer id) {
Person persnotallow= this.personService.get(id);
log.info("http://查詢person詳情執(zhí)行完成");
return person;
}
}
4、自定義加密切面類(EncryptAop)和解密切面類(DecryptAop):用@NeedEncrypt注解定義加密切點(diǎn),在加密切點(diǎn)的環(huán)繞通知方法里執(zhí)行到具體的業(yè)務(wù)處理方法之前,判斷輸入對(duì)象的參數(shù)字段是否標(biāo)記了@EncryptField(加密字段注解),如果判斷結(jié)果為true,則使用java反射對(duì)該字段進(jìn)行加密處理,注意這里引用了hutool的工具包,使用了工具包里的加密和解密方法,這里也可以替換成其他的方式;用@NeedDecrypt注解定義解密切點(diǎn),在解密切點(diǎn)的環(huán)繞通知方法里執(zhí)行完具體的業(yè)務(wù)處理方法之后,判斷輸出對(duì)象的參數(shù)字段是否標(biāo)記了@DecryptField(解密字段注解),如果判斷結(jié)果為true,則使用java反射對(duì)該 字段進(jìn)行解密處理;
@Component
@Aspect
@Slf4j
public class EncryptAop {
/**
* 定義加密切入點(diǎn)
*/
@Pointcut(value = "@annotation(com.fanfu.anno.NeedEncrypt)")
public void pointcut() {
}
/**
* 命中加密切入點(diǎn)的環(huán)繞通知
*
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("http://環(huán)繞通知 start");
//獲取命中目標(biāo)方法的入?yún)?shù)
Object[] args = proceedingJoinPoint.getArgs();
if (args.length > 0) {
for (Object arg : args) {
//按參數(shù)的類型進(jìn)行判斷,如果業(yè)務(wù)中還有其他的類型,可酌情增加
if (arg != null) {
if (arg instanceof List) {
for (Object tmp : ((List) arg)) {
//加密處理
this.deepProcess(tmp);
}
} else {
this.deepProcess(arg);
}
}
}
}
//對(duì)敏感數(shù)據(jù)加密后執(zhí)行目標(biāo)方法
Object result = proceedingJoinPoint.proceed();
log.info("http://環(huán)繞通知 end");
return result;
}
public void deepProcess(Object obj) throws IllegalAccessException {
if (obj != null) {
//獲取對(duì)象的所有字段屬性并遍歷
Field[] declaredFields = obj.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//判斷字段屬性上是否標(biāo)記了@EncryptField注解
if (declaredField.isAnnotationPresent(EncryptField.class)) {
//如果判斷結(jié)果為真,則取出字段屬性值,進(jìn)行加密、重新賦值
declaredField.setAccessible(true);
Object valObj = declaredField.get(obj);
if (valObj != null) {
String value = valObj.toString();
//開始敏感字段屬性值加密
String decrypt = this.encrypt(value);
//把加密后的字段屬性值重新賦值
declaredField.set(obj, decrypt);
}
}
}
}
}
private String encrypt(String value) {
//這里特別注意一下,對(duì)稱加密是根據(jù)密鑰進(jìn)行加密和解密的,加密和解密的密鑰是相同的,一旦泄漏,就無(wú)秘密可言,
//“fanfu-csdn”就是我自定義的密鑰,這里僅作演示使用,實(shí)際業(yè)務(wù)中,這個(gè)密鑰要以安全的方式存儲(chǔ);
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), "fanfu-csdn".getBytes()).getEncoded();
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
String encryptValue = aes.encryptBase64(value);
return encryptValue;
}
}
@Component
@Aspect
@Slf4j
public class DecryptAop {
/**
* 定義需要解密的切入點(diǎn)
*/
@Pointcut(value = "@annotation(com.fanfu.anno.NeedDecrypt)")
public void pointcut() {
}
/**
* 命中的切入點(diǎn)時(shí)的環(huán)繞通知
*
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("http://環(huán)繞通知 start");
//執(zhí)行目標(biāo)方法
Object result = proceedingJoinPoint.proceed();
//判斷目標(biāo)方法的返回值類型
if (result instanceof List) {
for (Object tmp : ((List) result)) {
//數(shù)據(jù)脫敏處理邏輯
this.deepProcess(tmp);
}
} else {
this.deepProcess(result);
}
log.info("http://環(huán)繞通知 end");
return result;
}
public void deepProcess(Object obj) throws IllegalAccessException {
if (obj != null) {
//取出輸出對(duì)象的所有字段屬性,并遍歷
Field[] declaredFields = obj.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//判斷字段屬性上是否標(biāo)記DecryptField注解
if (declaredField.isAnnotationPresent(DecryptField.class)) {
//如果判斷結(jié)果為真,則取出字段屬性數(shù)據(jù)進(jìn)行解密處理
declaredField.setAccessible(true);
Object valObj = declaredField.get(obj);
if (valObj != null) {
String value = valObj.toString();
//加密數(shù)據(jù)的解密處理
value = this.decrypt(value);
DecryptField annotation = declaredField.getAnnotation(DecryptField.class);
boolean open = annotation.open();
//把解密后的數(shù)據(jù)重新賦值
declaredField.set(obj, value);
}
}
}
}
}
private String decrypt(String value) {
//這里特別注意一下,對(duì)稱加密是根據(jù)密鑰進(jìn)行加密和解密的,加密和解密的密鑰是相同的,一旦泄漏,就無(wú)秘密可言,
//“fanfu-csdn”就是我自定義的密鑰,這里僅作演示使用,實(shí)際業(yè)務(wù)中,這個(gè)密鑰要以安全的方式存儲(chǔ);
byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES.getValue(), "fanfu-csdn".getBytes()).getEncoded();
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.DES, key);
String decryptStr = aes.decryptStr(value);
return decryptStr;
}
}
加密結(jié)果
圖片
解密結(jié)果
圖片
總結(jié)
這篇著重和大家分享的內(nèi)容如下:
1、敏感數(shù)據(jù)的一些基礎(chǔ)概念;
2、敏感數(shù)據(jù)處理的解決思路;
3、敏感數(shù)據(jù)處理的具體實(shí)現(xiàn)方式;