<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Springboot 配置文件、隱私數(shù)據(jù)脫敏的最佳實踐(原理+源碼)

          共 14583字,需瀏覽 30分鐘

           ·

          2021-08-11 12:15


          這幾天公司在排查內(nèi)部數(shù)據(jù)賬號泄漏,原因是發(fā)現(xiàn)某些實習(xí)生小可愛居然連帶著賬號、密碼將源碼私傳到GitHub上,導(dǎo)致核心數(shù)據(jù)外漏,孩子還是沒挨過社會毒打,這種事的后果可大可小。

          說起這個我是比較有感觸的,之前我TM被刪庫的經(jīng)歷,到現(xiàn)在想起來心里還難受,我也是把數(shù)據(jù)庫賬號明文密碼誤提交到GitHub,然后被哪個大寶貝給我測試庫刪了,后邊我長記性了把配置文件內(nèi)容都加密了,數(shù)據(jù)安全問題真的不容小覷,不管工作匯還是生活,敏感數(shù)據(jù)一定要做脫敏處理。

          如果對脫敏概念不熟悉,可以看一下我之前寫過的一篇大廠也在用的6種數(shù)據(jù)脫敏方案,里邊對脫敏做了簡單的描述,接下來分享工作中兩個比較常見的脫敏場景。

          配置脫敏

          實現(xiàn)配置的脫敏我使用了Java的一個加解密工具Jasypt,它提供了單密鑰對稱加密非對稱加密兩種脫敏方式。

          單密鑰對稱加密:一個密鑰加鹽,可以同時用作內(nèi)容的加密和解密依據(jù);

          非對稱加密:使用公鑰和私鑰兩個密鑰,才可以對內(nèi)容加密和解密;

          以上兩種加密方式使用都非常簡單,咱們以springboot集成單密鑰對稱加密方式做示例。

          首先引入jasypt-spring-boot-starter jar

           <!--配置文件加密-->
           <dependency>
               <groupId>com.github.ulisesbocchio</groupId>
               <artifactId>jasypt-spring-boot-starter</artifactId>
               <version>2.1.0</version>
           </dependency>

          配置文件加入秘鑰配置項jasypt.encryptor.password,并將需要脫敏的value值替換成預(yù)先經(jīng)過加密的內(nèi)容ENC(mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l)。

          這個格式我們是可以隨意定義的,比如想要abc[mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l]格式,只要配置前綴和后綴即可。

          jasypt:
            encryptor:
              property:
                prefix: "abc["
                suffix: "]"

          ENC(XXX)格式主要為了便于識別該值是否需要解密,如不按照該格式配置,在加載配置項的時候jasypt將保持原值,不進(jìn)行解密。

          spring:
            datasource:
              url: jdbc:mysql://1.2.3.4:3306/xiaofu?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&ze oDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
              username: xiaofu
              password: ENC(mVTvp4IddqdaYGqPl9lCQbzM3H/b0B6l)

          # 秘鑰
          jasypt:
            encryptor:
              password: 程序員內(nèi)點事(然而不支持中文)

          秘鑰是個安全性要求比較高的屬性,所以一般不建議直接放在項目內(nèi),可以通過啟動時-D參數(shù)注入,或者放在配置中心,避免泄露。

          java -jar -Djasypt.encryptor.password=1123  springboot-jasypt-2.3.3.RELEASE.jar

          預(yù)先生成的加密值,可以通過代碼內(nèi)調(diào)用API生成

          @Autowired
          private StringEncryptor stringEncryptor;

          public void encrypt(String content) {
              String encryptStr = stringEncryptor.encrypt(content);
              System.out.println("加密后的內(nèi)容:" + encryptStr);
          }

          或者通過如下Java命令生成,幾個參數(shù)D:\maven_lib\org\jasypt\jasypt\1.9.3\jasypt-1.9.3.jar為jasypt核心jar包,input待加密文本,password秘鑰,algorithm為使用的加密算法。

          java -cp  D:\maven_lib\org\jasypt\jasypt\1.9.3\jasypt-1.9.3.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="root" password=xiaofu  algorithm=PBEWithMD5AndDES

          一頓操作后如果還能正常啟動,說明配置文件脫敏就沒問題了。

          敏感字段脫敏

          生產(chǎn)環(huán)境用戶的隱私數(shù)據(jù),比如手機(jī)號、身份證或者一些賬號配置等信息,入庫時都要進(jìn)行不落地脫敏,也就是在進(jìn)入我們系統(tǒng)時就要實時的脫敏處理。

          用戶數(shù)據(jù)進(jìn)入系統(tǒng),脫敏處理后持久化到數(shù)據(jù)庫,用戶查詢數(shù)據(jù)時還要進(jìn)行反向解密。這種場景一般需要全局處理,那么用AOP切面來實現(xiàn)在適合不過了。

          首先自定義兩個注解@EncryptField、@EncryptMethod分別用在字段屬性和方法上,實現(xiàn)思路很簡單,只要方法上應(yīng)用到@EncryptMethod注解,則檢查入?yún)⒆侄问欠駱?biāo)注@EncryptField注解,有則將對應(yīng)字段內(nèi)容加密。

          @Documented
          @Target({ElementType.FIELD,ElementType.PARAMETER})
          @Retention(RetentionPolicy.RUNTIME)
          public @interface EncryptField {

              String[] value() default "";
          }
          @Documented
          @Target({ElementType.METHOD})
          @Retention(RetentionPolicy.RUNTIME)
          public @interface EncryptMethod {

              String type() default ENCRYPT;
          }

          切面的實現(xiàn)也比較簡單,對入?yún)⒓用?,返回結(jié)果解密。為了方便閱讀這里就只貼出部分代碼,完整案例Github地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-jasypt

          @Slf4j
          @Aspect
          @Component
          public class EncryptHandler {

              @Autowired
              private StringEncryptor stringEncryptor;

              @Pointcut("@annotation(com.xiaofu.annotation.EncryptMethod)")
              public void pointCut() {
              }

              @Around("pointCut()")
              public Object around(ProceedingJoinPoint joinPoint) {
                  /**
                   * 加密
                   */

                  encrypt(joinPoint);
                  /**
                   * 解密
                   */

                  Object decrypt = decrypt(joinPoint);
                  return decrypt;
              }

              public void encrypt(ProceedingJoinPoint joinPoint) {

                  try {
                      Object[] objects = joinPoint.getArgs();
                      if (objects.length != 0) {
                          for (Object o : objects) {
                              if (o instanceof String) {
                                  encryptValue(o);
                              } else {
                                  handler(o, ENCRYPT);
                              }
                              //TODO 其余類型自己看實際情況加
                          }
                      }
                  } catch (IllegalAccessException e) {
                      e.printStackTrace();
                  }
              }

              public Object decrypt(ProceedingJoinPoint joinPoint) {
                  Object result = null;
                  try {
                      Object obj = joinPoint.proceed();
                      if (obj != null) {
                          if (obj instanceof String) {
                              decryptValue(obj);
                          } else {
                              result = handler(obj, DECRYPT);
                          }
                          //TODO 其余類型自己看實際情況加
                      }
                  } catch (Throwable e) {
                      e.printStackTrace();
                  }
                  return result;
              }
              。。。
          }

          緊接著測試一下切面注解的效果,我們對字段mobile、address加上注解@EncryptField做脫敏處理。

          @EncryptMethod
          @PostMapping(value = "test")
          @ResponseBody
          public Object testEncrypt(@RequestBody UserVo user, @EncryptField String name) {

              return insertUser(user, name);
          }

          private UserVo insertUser(UserVo user, String name) {
              System.out.println("加密后的數(shù)據(jù):user" + JSON.toJSONString(user));
              return user;
          }

          @Data
          public class UserVo implements Serializable {

              private Long userId;

              @EncryptField
              private String mobile;

              @EncryptField
              private String address;

              private String age;
          }

          請求這個接口,看到參數(shù)被成功加密,而返回給用戶的數(shù)據(jù)依然是脫敏前的數(shù)據(jù),符合我們的預(yù)期,那到這簡單的脫敏實現(xiàn)就完事了。

          知其然知其所以然

          Jasypt工具雖然簡單好用,但作為程序員我們不能僅滿足于熟練使用,底層實現(xiàn)原理還是有必要了解下的,這對后續(xù)調(diào)試bug、二次開發(fā)擴(kuò)展功能很重要。

          個人認(rèn)為Jasypt配置文件脫敏的原理很簡單,無非就是在具體使用配置信息之前,先攔截獲取配置的操作,將對應(yīng)的加密配置解密后再使用。

          具體是不是如此我們簡單看下源碼的實現(xiàn),既然是以springboot方式集成,那么就先從jasypt-spring-boot-starter源碼開始入手。

          starter代碼很少,主要的工作就是通過SPI機(jī)制注冊服務(wù)和@Import注解來注入需前置處理的類JasyptSpringBootAutoConfiguration。

          在前置加載類EnableEncryptablePropertiesConfiguration中注冊了一個核心處理類EnableEncryptablePropertiesBeanFactoryPostProcessor。

          它的構(gòu)造器有兩個參數(shù),ConfigurableEnvironment用來獲取所有配屬信息,EncryptablePropertySourceConverter對配置信息做解析處理。

          順藤摸瓜發(fā)現(xiàn)具體負(fù)責(zé)解密的處理類EncryptablePropertySourceWrapper,它通過對Spring屬性管理類PropertySource<T>做拓展,重寫了getProperty(String name)方法,在獲取配置時,凡是指定格式如ENC(x) 包裹的值全部解密處理。

          既然知道了原理那么后續(xù)我們二次開發(fā),比如:切換加密算法或者實現(xiàn)自己的脫敏工具就容易的多了。

          案例Github地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot-jasypt

          PBE算法

          再來聊一下Jasypt中用的加密算法,其實它是在JDK的JCE.jar包基礎(chǔ)上做了封裝,本質(zhì)上還是用的JDK提供的算法,默認(rèn)使用的是PBE算法PBEWITHMD5ANDDES,看到這個算法命名很有意思,段個句看看,PBE、WITH、MD5、AND、DES 好像有點故事,繼續(xù)看。

          PBE算法(Password Based Encryption,基于口令(密碼)的加密)是一種基于口令的加密算法,其特點在于口令是由用戶自己掌握,在加上隨機(jī)數(shù)多重加密等方法保證數(shù)據(jù)的安全性。

          PBE算法本質(zhì)上并沒有真正構(gòu)建新的加密、解密算法,而是對我們已知的算法做了包裝。比如:常用的消息摘要算法MD5SHA算法,對稱加密算法DESRC2等,而PBE算法就是將這些算法進(jìn)行合理組合,這也呼應(yīng)上前邊算法的名字。

          既然PBE算法使用我們較為常用的對稱加密算法,那就會涉及密鑰的問題。但它本身又沒有鑰的概念,只有口令密碼,密鑰則是口令經(jīng)過加密算法計算得來的。

          口令本身并不會很長,所以不能用來替代密鑰,只用口令很容易通過窮舉攻擊方式破譯,這時候就得加點了。

          鹽通常會是一些隨機(jī)信息,比如隨機(jī)數(shù)、時間戳,將鹽附加在口令上,通過算法計算加大破譯的難度。

          源碼里的貓膩

          簡單了解PBE算法,回過頭看看Jasypt源碼是如何實現(xiàn)加解密的。

          在加密的時候首先實例化秘鑰工廠SecretKeyFactory,生成八位鹽值,默認(rèn)使用的jasypt.encryptor.RandomSaltGenerator生成器。

          public byte[] encrypt(byte[] message) {
              // 根據(jù)指定算法,初始化秘鑰工廠
              final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm1);
              // 鹽值生成器,只選八位
              byte[] salt = saltGenerator.generateSalt(8);
              // 
              final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations);
              // 鹽值、口令生成秘鑰
              SecretKey key = factory.generateSecret(keySpec);

              // 構(gòu)建加密器
              final Cipher cipherEncrypt = Cipher.getInstance(algorithm1);
              cipherEncrypt.init(Cipher.ENCRYPT_MODE, key);
              // 密文頭部(鹽值)
              byte[] params = cipherEncrypt.getParameters().getEncoded();

              // 調(diào)用底層實現(xiàn)加密
              byte[] encryptedMessage = cipherEncrypt.doFinal(message);

              // 組裝最終密文內(nèi)容并分配內(nèi)存(鹽值+密文)
              return ByteBuffer
                      .allocate(1 + params.length + encryptedMessage.length)
                      .put((byte) params.length)
                      .put(params)
                      .put(encryptedMessage)
                      .array();
          }

          由于默認(rèn)使用的是隨機(jī)鹽值生成器,導(dǎo)致相同內(nèi)容每次加密后的內(nèi)容都是不同的

          那么解密時該怎么對應(yīng)上呢?

          看上邊的源碼發(fā)現(xiàn),最終的加密文本是由兩部分組成的,params消息頭里邊包含口令和隨機(jī)生成的鹽值,encryptedMessage密文。

          加密

          而在解密時會根據(jù)密文encryptedMessage的內(nèi)容拆解出params內(nèi)容解析出鹽值和口令,在調(diào)用JDK底層算法解密出實際內(nèi)容。

          @Override
          @SneakyThrows
          public byte[] decrypt(byte[] encryptedMessage) {
              // 獲取密文頭部內(nèi)容
              int paramsLength = Byte.toUnsignedInt(encryptedMessage[0]);
              // 獲取密文內(nèi)容
              int messageLength = encryptedMessage.length - paramsLength - 1;
              byte[] params = new byte[paramsLength];
              byte[] message = new byte[messageLength];
              System.arraycopy(encryptedMessage, 1, params, 0, paramsLength);
              System.arraycopy(encryptedMessage, paramsLength + 1, message, 0, messageLength);

              // 初始化秘鑰工廠
              final SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm1);
              final PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
              SecretKey key = factory.generateSecret(keySpec);

              // 構(gòu)建頭部鹽值口令參數(shù)
              AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(algorithm1);
              algorithmParameters.init(params);

              // 構(gòu)建加密器,調(diào)用底層算法
              final Cipher cipherDecrypt = Cipher.getInstance(algorithm1);
              cipherDecrypt.init(
                      Cipher.DECRYPT_MODE,
                      key,
                      algorithmParameters
              );
              return cipherDecrypt.doFinal(message);
          }
          解密

          1. List復(fù)制:深拷貝和淺拷貝用法及區(qū)別

          2. Nginx 掛了怎么辦?怎么實現(xiàn)高可用?

          3. 領(lǐng)域驅(qū)動設(shè)計(DDD):領(lǐng)域接口化設(shè)計

          4. 16 條 yyds 的代碼規(guī)范

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點“在看”,關(guān)注公眾號并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩无码视频网站 | 99最新视频在线 | 日日伊人 | 亚洲A V观看 | 人妻123区 |