天天看點

Java String加解密踩坑

背景

最近在做一款資料産品,涉及到資料源。既然是資料源,肯定有URL(含port資訊),使用者名和密碼。頁面上面,雖然有前端元件mask處理,不能複制出來。但是對于稍微懂點技術的同學,都知道去檢視控制台。在我們的産品設計裡面,産品同學沒有考慮到這種安全機制問題;也就是說,在控制台,可以看到明文密碼。

思路

前端隻能做mask處理,控制台看到的資料是後端接口傳回的,故而需要後端來解決這個問題。

不難想到,後端接口在傳回密碼等私密資訊前,加密處理一下;前端拿到什麼資料,就給後端傳輸什麼資料;後續需要使用此密碼資料時,後端需要解密一下。即:加密,再解密。

解決

資料源管理菜單的功能大緻如下,針對每個資料源可以驗證其連通性:

Java String加解密踩坑

點選連接配接測試時,報錯:

datasource test error
java.lang.Exception: java.lang.Exception: ru.yandex.clickhouse.except.ClickHouseException: ClickHouse exception, code: 193, host: 10.20.30.40, port: 8123; Code: 193, e.displayText() = DB::Exception: Wrong password for user default (version 19.9.5.36)
  at com.xy.cloudiview.common.dataprovider.impl.JdbcDataProvider.check(JdbcDataProvider.java:324)
  at com.xy.cloudiview.common.services.DataProviderService.testConnection(DataProviderService.java:98)
  at com.xy.cloudiview.web.controller.datasource.DataSourceController.checkDatasource(DataSourceController.java:248)      

實在是莫名其妙,木有辦法,隻能斷點調試,傳回給前端2個字段:password,encryptedPassword,然後對比一下encryptedPassword解密後的password和最原始的password是否相同。

好家夥,截圖來了:

Java String加解密踩坑

我們看到斷點調試得到的密碼都是​

​root​

​​;但,使用​

​String.equals()​

​對比發現兩者不相等。簡單來說,就是如下的截圖:

Java String加解密踩坑

使用​

​String.contentEquals()​

​對比,發現兩者依舊不相等。

Java String加解密踩坑

測試代碼如下:

public static void main(String[] args) {
    String s1 = "root";
    String s2 = DecodeUtil.encrypt(s1);
    String s3 = DecodeUtil.desEncrypt(s2);
    System.out.println("s2: " + s2 + "\ns3: " + s3 + "\ns1 == s3 ?: " + s1.equals(s3));
}      

見了鬼了。

如果足夠仔細的話,會發現第二個截圖裡面,左邊的加密後再解密的密碼的長度是16,即:​

​char[16]​

​​;右邊的原始密碼的長度是4,即:​

​char[4]​

​。

驗證如下:

Java String加解密踩坑

實際上隻需要占用4位,​

​trim()​

​一下呢?

Java String加解密踩坑

再對比一下:

Java String加解密踩坑

解決問題。

後續

在我們的業務場景中,資料源是可以查詢,新增,編輯,複制,删除的。如果是新增一個資料源,此時使用者輸入的密碼就是明文密碼。

  1. 前端增加一個标志字段:
const [modalType, setModalType] = useState<'update' | 'add' | 'copy' | 'select'>('add');

const testClick = () => {
  formModal.validateFields().then((values: any) => {
    const {config, sourceName, sourceType} = values;
    setTestLoading(true)
    test({
      name: sourceName,
      type: sourceType,
      // 新增入參
      modalType,
      config,
    }).then((res: any) => {
      message[res.status](`${res.msg}`, 1)
      setTestLoading(false)
    }).catch(() => {
      setTestLoading(false)
    })
  });
};      
  1. 後端判斷一下:
// 新增資料源時無需解密
if (!dataSource.getString("modalType").equals("add")) {
    // 解密一定要trim()
    config.put("password", DecodeUtil.desEncrypt(config.getString("password")).trim());
}