本节书摘来异步社区《java编码指南:编写安全可靠程序的75条建议》一书中的第1章,第1.13节,作者:【美】fred long(弗雷德•朗), dhruv mohindra(德鲁•莫欣达), robert c.seacord(罗伯特 c.西科德), dean f.sutherland(迪恩 f.萨瑟兰), david svoboda(大卫•斯沃博达),更多章节内容可以访问云栖社区“异步社区”公众号查看。
程序以明文(未加密的文本数据)方式存储密码将会导致密码以多种方式被泄露的风险。尽管程序通常接收到的用户密码是明文的,但是程序应该确保密码不以明文方式存储。
一种可行的限制密码暴露的技术是使用散列函数(hash function),它允许程序间接地比较输入的密码和原始密码,而不需要以明文或可解密的方式存储密码字符串。这种方法最大限度地减少了密码的暴露,而且没有表现出任何实用上的缺点。
散列函数产生的值是散列值(hash value)或消息摘要(message digest)。散列加密过程在计算上可行,而它的逆向过程在计算上是不可行的。在实践中,明文密码可以编码为散列值,但是散列值不能解码为明文。要比较两个密码是否相等,需要对比它们的散列值是否相等。
总是为需要加密的密码添加一个盐(salt)是一个好的实践。盐是一个唯一的(通常是连续的)或随机生成的数据,与散列值存储在一起。使用盐有助于防止对散列值的蛮干攻击,提供的盐需要足够长,这样才能生成足够的熵(盐值过短不能显著缓解蛮干攻击)。每个密码都应该有自己的盐与之关联。如果一个盐被用于多个密码,那么两个用户将能够看到他们的密码是否相同。
对散列函数和盐的长度的选择,需要在安全性和性能之间做出权衡。通过选择一个更强大的散列函数来增加有效蛮干攻击所需努力的同时,也增加了验证密码所需的时间。虽然增加盐的长度可使蛮干攻击更为困难,但是却需要占用额外的存储空间。
java的messagedigest类提供了各种加密散列函数的实现。要避免使用有缺陷的函数,如消息摘要算法(message-digest algorithm,md5)。安全散列算法sha-1和sha-2是由美国国家安全局维护的,目前它们被认为是安全的散列函数。在实践中,许多应用程序使用sha-256,因为这个散列函数在被认为是安全的同时,还具有合理的性能。
下面的违规代码示例使用对称密钥算法来加密和解密存储在password.bin中的密码。
import java.security.messagedigest;
import java.security.nosuchalgorithmexception;
public final class password {
private void setpassword(string pass) throws exception {
byte[] salt = generatesalt(12);
messagedigest msgdigest = messagedigest.getinstance("sha-256");
// encode the string and salt
byte[] hashval = msgdigest.digest((pass+salt).getbytes());
savebytes(salt, "salt.bin");
// save the hash value to password.bin
savebytes(hashval,"password.bin");
}
boolean checkpassword(string pass) throws exception {
byte[] salt = loadbytes("salt.bin");
byte[] hashval1 = msgdigest.digest((pass+salt).getbytes());
// load the hash value stored in password.bin
byte[] hashval2 = loadbytes("password.bin");
return arrays.equals(hashval1, hashval2);
private byte[] generatesalt(int n) {
// generate a random byte array of length n
}<code>`</code>
即使攻击者知道程序在存储密码时使用的是sha-256算法和一个12字节的盐,他也无法从password.bin和salt.bin中获取实际的密码。
尽管这种方式解决了上一个违规代码示例中的解密问题,但是这个程序可能无意中就将密码明文存储在了内存中。java的字符串对象是不可变的,可以被java虚拟机复制和存储在其内部。因此,java缺乏一种用于安全删除存储在字符串中的密码的机制。更多相关信息参见指南1。
下面的合规解决方案通过使用字节数组来存储密码,解决了上一个违规代码示例中的问题。