一个有争议的话题:客户端密码加密有必要吗?

和小伙伴们讨论一个有争议的话题:

  • 密码加密需要在客户端进行吗?

有的人觉得密码加密直接在服务端进行就可以了,没必要在客户端做;当然也有人觉得可以在客户端进行密码加密,降低服务器的压力。

你要问松哥项目中是怎么做的,我会告诉你我们在客户端就对密码加密了。

为什么这么做呢?

我来和大家聊聊我们是怎么考虑的。

一 烫手山芋

首先我们有一个共识,就是明文密码是一个烫手山芋,是一个定时炸弹,应该尽早处理。

把这样一个炸弹从前端运到后端再进行加密处理,似乎并非一个好的办法。应该尽早对炸弹进行处理,然后再进行运输。

那么在客户端就对密码进行加密的目的是什么?防止密码在传输过程中泄露吗?

显然不是!

对于重放攻击来说,人家压根不 care 你是否在客户端加密密码,对于重放攻击来说,密码是否在客户端进行加密,问题都不大。

什么是重放攻击?
重放攻击(Replay Attack)是一种网络安全攻击,攻击者通过截获并重新发送之前捕获的数据包,以实现对系统的欺诈或破坏。这种攻击通常发生在没有正确实现消息认证和完整性保护的系统中。
一般来说,重放攻击有三个特点:

  1. 无需破解加密:攻击者不需要知道加密算法或密钥,只需复制和重放数据包。
  2. 依赖于协议的缺陷:如果协议没有设计好,没有实现时间戳、序列号或一次性令牌等机制,就容易被重放攻击。
  3. 可重复性:攻击者可以多次重放相同的数据包,尝试获取相同的结果。

一般来说,解决重放攻击比较有效并且省事的办法就是上 HTTPS。

客户端对密码进行加密的目的是为了防止明文密码在到达后端之后被滥用。这是最主要的目的。

为了增加破解成本,在前端进行加密的时候,可以使用慢 hash 函数。

二 什么是慢 hash

慢哈希函数(Slow Hash Function)是一种密码学上的哈希函数,它被设计成故意减慢处理速度,以增加破解的难度。

这种函数通常用于密码存储、密钥派生和某些加密算法中,目的是提供额外的安全层,防止暴力破解攻击。

一般来说,慢哈希函数主要有下面一些特点:

  1. 计算成本高:故意设计成需要更多计算资源(CPU 时间或内存),使得暴力破解这种方式变得不切实际。
  2. 内存密集型:许多慢哈希函数需要大量的内存,这增加了并行处理的难度,因为每个哈希计算实例都需要独立的内存空间。
  3. 抵抗暴力破解:由于计算成本高,慢哈希函数使得暴力破解(尝试所有可能的密码直到找到匹配)变得非常困难。
  4. 盐值(Salt):慢哈希函数通常与盐值结合使用,盐值是随机生成的数据,用于与密码组合,确保即使两个用户使用相同的密码,他们的哈希值也会不同。
  5. 密钥延展(Key Stretching):通过多次迭代哈希过程,进一步增加破解难度。

常见的慢哈希函数主要有如下几种,这几种也是我们在 Spring Security 中比较常见的:

  • PBKDF2(Password-Based Key Derivation Function 2):使用 HMAC 作为伪随机函数,可以配置迭代次数来增加计算时间。
  • BCrypt:专为密码存储设计,包含盐值和多轮哈希计算。
  • scrypt:设计用于加密货币和密码存储,特别强调内存密集型,以抵抗 GPU 和 ASIC 硬件加速的暴力破解。
  • Argon2:是当前最推荐使用的慢哈希函数之一,它赢得了密码哈希竞赛(Password Hashing Competition),并被设计为抵抗各种类型的攻击,包括定制硬件攻击。

以 BCrypt 为例,如果我们控制 BCrypt 的执行时间大概是 0.1 秒完成一次哈希计算的话,按照 1 秒生成 10 个哈希值的速度,算完所有的 10 位大小写字母和数字组成的弱密码大概需要 P(62,10)/(3600×24×365x10)=1,237,204,169 年时间。

三 一次用户注册

了解了慢哈希之后,我们来看一次完整的用户注册应该是什么样子的。

首先,用户在客户端输入明文密码 123。

接下来,客户端对密码进行加密,可以使用 SHA256 散列函数,如果想要谨慎一些,也可以使用用户名等信息作为盐,以防止彩虹表的攻击。

现在就可以由客户端发送密码到服务端了。

服务端收到客户端发来的消息之后,在服务端眼里可以将客户端发来的密码就当成明文,按照明文的处理流程进行加密并存储,这块松哥在 Spring Security 中和大家讲过多次了,不赘述。

这就是用户注册的流程。

将来用户登录其实也是一样的。

用户在客户端输入密码之后,现在客户端进行加密,然后再上传到服务端,服务端再按照我们在 Spring Security 中讲的方式进行处理就可以了。