天天看点

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());
}