安全地存储用户密码

2016 Nov 16

在存储密码的数据库被攻破的前提下,破解密码的成本越高则密码的安全性就越高。 一般来说,密码在存储到数据库前都经过hash加密。 需要考虑hash函数的性能对密码安全性的影响。 比如对于SHA256来说,当前的普通计算机能在一秒内计算数百万的hash值,定制的计算机则可以提供每秒数十亿次的hash运算。 在这种情况下,很多密码安全的设计都变得不再安全。

首先,最不安全的方式是明文存储密码。 其次,对密码明文进行sha1加密、对密码明文进行sha1加密时加上固定的salt值、为每个用户分配不同的salt值都是不安全的。

推荐使用bcrypt加密密码。 bcrypt相比sha1等来说更慢,这意味着破解密码的cpu成本更高。 比如log_rounds值设为10时,大概需要100毫秒来计算hash值。 还可以通过增加log_rounds值来抵消未来计算机运算能力的提高。 一些bcrypt实现还原生支持per-user-salt。

Dropbox也主要采用了per-user-salt加bcrypt来存储用户密码。 Dropbox在用bcrypt hash之前,会先用SHA512对密码做预处理——把密码hash成512 bit的定长字符串。 预处理有两个作用:一是对于很短的密码,提高它的entropy(熵);二是防止输入密码过长造成潜在的DoS attacks风险(bcrypt很“慢”)。 Dropbox还引入了global pepper(既有“盐”又有“胡椒粉”)——对bcrypt的hash值再用AES256做进一步的加密。 pepper存储在数据库之外的地方。AES256是对称加密,方便未来更新pepper值。

为什么推荐用char[]而不是String来存储密码

2016 Nov 1

为什么Java Swing用char[]来存储密码,而不是String

因为String是不可变的(immutable)。 这意味着在String对象被垃圾回收前,密码会一直存在于内存中。 (虽然可以通过反射来修改String对象的field的访问权限,进而修改String对象。) 使用char[]来存储密码的好处是使用后可以马上抹掉char[]的内容。

安全总是相对的。一种可能性(取决于JVM实现)是在抹掉char[]之前发生了GC,移动了char[]对象 (比如从Eden移动到Survivor),使得老的char[]还驻留在内存里。

还有一点要注意的是不要在log里(无意地)打印敏感信息,比如密码等。