【Jasypt】給你的配置加把鎖

  • 2020 年 2 月 13 日
  • 筆記

前言

前幾天,有個前同事向我吐槽,他們公司有個大神把公司的項目程式碼全部上傳到了 github,並且是公開項目,所有人都可以瀏覽。更加恐怖的是項目裡面包含配置文件,資料庫資訊、redis 配置、各種公鑰私鑰密碼全在項目裡面,也一同上傳了。

如果只是單純的業務程式碼泄露,情況倒還好,因為別人知道你程式碼,要想搞你,他必須要把源碼看一遍,分析漏洞。又因為程式碼上線,經過了層層測試,漏洞也不好找,至少短時間內不好找。但是別人拿到你的資料庫資訊,那就開啟了上帝模式,想怎麼玩就怎麼玩,刪庫都不用跑路的。

不過,還好他們發現的及時,第一時間刪除了 github 上的項目,但是不能保證當時的項目沒有人拉到本地,所以第二就是把配置文件內的各項配置都更改一遍,改配置聽起來簡單,但是要知道有些配置是不能熱更新的。很多配置要把前一個配置修改後才能使用,新老配置不能共存,你改的瞬間運行的項目就崩了,必須要停機維護才可以。為了變更配置他們花了大量的人力物力與精力。

其實這種慘痛的教訓本可以避免的,防止配置泄露,通用的有兩種形式。一種是使用配置中心,本地不保存配置,啟動的時候從配置中心獲取,這應該是最優解了。但是很多時候你所做的項目並沒有使用配置中心,配置就在項目裡面裸奔。這個時候就需要本地加密的形式防止配置泄露了,常用框架是 jasypt。同時它也是本文的主題,話不多說,直接開始,看看如果使用 jasypt 進行配置加密。

依賴

pom.xml

<dependency>          <groupId>com.github.ulisesbocchio</groupId>          <artifactId>jasypt-spring-boot-starter</artifactId>          <version>3.0.1</version>  </dependency>    <build>    <plugins>      <plugin>        <groupId>com.github.ulisesbocchio</groupId>        <artifactId>jasypt-maven-plugin</artifactId>        <version>3.0.0</version>      </plugin>    </plugins>  </build>

如果你使用了 spring boot 那麼使用 jasypt 很簡單,只要依賴一個 jasypt-spring-boot-starter 包就可以了。

至於 jasypt-maven-plugin 是方便我們加密解密配置的 maven 插件,後面會說用法。

配置

application.properties

my.conf.test1=123  my.conf.test2=DEC(123)  # 記得看最佳實踐  jasypt.encryptor.password=lE1rl5K$

總共有三個配置,第一個配置 my.conf.test1 是不需要加密的配置,第二個配置 my.conf.test2 是需要加密的配置,要加密的內容是 123。注意他的格式的是 DEC(待加密內容)。第三個 jasypt.encryptor.password 配置是我們的加密私鑰,默認使用的加密演算法是 PBEWITHHMACSHA512ANDAES_256 ,這個密鑰可以是任意字元串,而 lE1rl5K$ 只是我隨機生成的,你可以自由發揮。

生成加密內容

好了,到目前為止,我們的配置還是明文的。my.conf.test2 是我們想加密的配置,他與 my.conf.test1 唯一的區別就是多了一個 DEC() 包裹,這算哪門子加密,其實我們還差一步。還記得我們上面加依賴的時候,配置了一個 Maven 插件嗎?現在就是用到他的時候,在我們的項目目錄路徑下執行如下命令:

mvn jasypt:encrypt -Djasypt.encryptor.password="lE1rl5K$"

注意在執行的時候,password 要換成你自己在上文配置的密鑰。執行完後,看到終端輸出了一大堆日誌,然後就沒有然後了。但是真的是這樣嗎?

你再打開 application.properties 看一下,有什麼不一樣的地方。

my.conf.test1=123  my.conf.test2=ENC(0ZWzuD2DH0BZ8ANGMZxQyC6wv84sQLJtE6u7bcRjU+DntbMgkBvE2Z4fSzKKhYN8)  jasypt.encryptor.password=lE1rl5K$

我們發現,三個配置中其它兩個是原來的樣子,但是 my.conf.test2 變了,首先格式從之前 DEC(xxx) 變成了 ENC(xxx) 。另外括弧的 123 變成了 0ZWzuD2DH0BZ8ANGMZxQyC6wv84sQLJtE6u7bcRjU+DntbMgkBvE2Z4fSzKKhYN8

這其實就是配置加密後的樣子。這條命令的功能其實很簡單:

  1. 從配置文件中載入配置
  2. 從配置中找到有 DEC(xxx) 格式並且不是 jasypt 開頭的配置
  3. 使用配置的密鑰加密並覆蓋配置為 ENC(加密後的值)

另外通過插件也可以解密,使用

mvn jasypt:decrypt -Djasypt.encryptor.password="lE1rl5K$"

執行這條命令會反過來,把 ENC(xxx) 內容的配置解密成 DEC(明文) 列印在控制台,注意是控制台,而不是把配置文件變回去,作者說這樣是為了安全

驗證

TestController.java

/*   *   *  * *   *  *  * blog.coder4j.cn   *  *  * Copyright (C) 2016-2019 All Rights Reserved.   *  *   *   */  package cn.coder4j.study.example.jasypt;    import org.springframework.beans.factory.annotation.Value;  import org.springframework.web.bind.annotation.GetMapping;  import org.springframework.web.bind.annotation.PathVariable;  import org.springframework.web.bind.annotation.RequestMapping;  import org.springframework.web.bind.annotation.ResponseBody;  import org.springframework.web.bind.annotation.RestController;    /**   * @author buhao   * @version TestController.java, v 0.1 2019-12-26 10:55 buhao   */  @RestController  @RequestMapping("/test")  public class TestController {        @Value("${my.conf.test1}")      private String confTest1;      @Value("${my.conf.test2}")      private String confTest2;        @GetMapping("/getConf/{type}")      @ResponseBody      public Object getConfTest(@PathVariable Integer type) {          if (type == 1) {              return confTest1;          } else {              return confTest2;          }      }  }

程式碼其實很簡單,首先通過 @Value[1] 的方式讀取配置,同時把沒有加密的配置與加密的配置都讀出來,然後通過介面,當路徑參數為 1 的時候返回沒有經過加密的參數,當路徑參數為 2 的時候返回加密過的參數。要是都返回 123 說明我們成功了。

為了方便驗證,直接用 IDEA 的內置工具,下面是驗證結果:

未加密的參數

image.png

經過加密的參數

image.png

結果如我們所料,加密成功。

獲取配置的大致流程其中跟上面加密配置的流程大致反過來:

  1. 攔截獲取配置的操作
  2. 如果攔截到的配置是 ENC(xxx) 格式
  3. 讀取 jasypt.encryptor.password 密鑰
  4. 通過密鑰解密配置 密鑰與配置分開保存

可以看到,通過 jasypt 十分的方便,第一依賴,第二配置,其中配置除加密內容外還有一個 jasypt.encryptor.password 。這個前文也說了是用於加密與解密的密碼,通過它可以加解密配置。

image.png

回到開頭,我們加密的目的是為了防止程式碼泄露的時候把配置一起給泄露出去了。配置是沒有問題了,我們加密了,但是我們同時把密鑰也放在配置文件中了。這相當於什麼呢?就像你把門給鎖了,但是鑰匙還插在鎖上。

所以密鑰一定要跟配置分開保存,通常是通過啟動命令傳給應用,比如下面這種:

java -Djasypt.encryptor.password="password" -jar my-application.jar

如果再保險一點,可以把密鑰放在環境變數中,再通過命令傳給應用。

非對稱加密

默認使用的加密演算法為對稱加密 HHMAC ,既然有對稱那肯定也有非對稱。

這裡的對稱與非對稱指的是密鑰的保存方式,對稱加密是指的是加密與解密共用一個密鑰,也就是說我用這個密鑰即可以用來加密也可以用來解密。上一條說為了安全我們要把配置跟密鑰分開保存,一般保存在兩個地方,一個是線上伺服器,一個是項目負責人的電腦上了,因為他要把配置從明文變成密文。為什麼是項目負責人的電腦上,因為密鑰不可能人手一份,那樣又會增大泄露風險。

但是這樣的話又會出來一種問題,一個項目涉及了太多配置,我加一個配置找下項目負責人幫我生成個密文,加一個生成一個,項目負責人變成工具人了。

這個時候我們可以通過非對稱加密的方式來解決,這種方式的好處就是有一對密碼,分別稱為公鑰與私鑰,公鑰用來生成加密數據,可以放心大膽人手一份,而私鑰放在伺服器上進行運行時候的解密工作,因篇幅有限,具體使用方式可以通過文末的鏈接查看官方文檔。

環境隔離

配置肯定是區分環境的,有些環境安全等級沒有那麼高,比如開發與測試環境,沒有必要加密。而預發及生產環境就需要加密,並且推薦使用不同的密鑰,這樣最大程度的避免安全問題。