我继承了一个Web应用程序,我刚刚在SQL Server数据库中以纯文本形式存储了超过300,000个用户名/密码.我意识到这是一件非常糟糕的事情.
知道我必须更新登录和密码更新过程以加密/解密,并且对系统其余部分的影响最小,您会建议从数据库中删除纯文本密码的最佳方法是什么?
任何帮助表示赞赏.
编辑:对不起,如果我不清楚,我打算问你的加密/哈希密码的程序,而不是特定的加密/散列方法.
我应该只是:
备份数据库
更新登录/更新密码代码
几小时后,浏览用户表中记录密码的所有记录并替换每个记录
测试以确保用户仍然可以登录/更新密码
我想我的关注更多来自于大量的用户,所以我想确保我正确地做到这一点.
编辑(2016):按优先顺序使用Argon2,scrypt,bcrypt或PBKDF2.尽可能使用适合您情况的减速因子.使用经审查的现有实施.确保使用适当的盐(尽管您正在使用的库应该为您确保这一点).
当您对密码进行散列时,请使用DO NOT USE PLAIN MD5.
使用PBKDF2,这基本上意味着使用随机盐来防止彩虹表攻击,并且迭代(重新散列)足够的时间来减慢散列 - 不要太多,以至于你的应用程序需要太长时间,但足以让攻击者暴力破解大量不同的密码会注意到
从文件:
迭代至少1000次,最好是更多 - 实现时间,看看有多少次迭代是可行的.
8个字节(64位)的盐就足够了,并且随机不需要是安全的(盐是未加密的,我们不担心有人猜它).
在散列时应用salt的一种好方法是使用HMAC和您喜欢的哈希算法,使用密码作为HMAC密钥,使用salt作为哈希文本(请参阅本文档的这一部分).
Python中的示例实现,使用SHA-256作为安全哈希:
编辑:正如Eli Collins所提到的,这不是PBKDF2的实现.您应该更喜欢坚持标准的实现,例如PassLib.
from hashlib import sha256 from hmac import HMAC import random def random_bytes(num_bytes): return "".join(chr(random.randrange(256)) for i in xrange(num_bytes)) def pbkdf_sha256(password, salt, iterations): result = password for i in xrange(iterations): result = HMAC(result, salt, sha256).digest() # use HMAC to apply the salt return result NUM_ITERATIONS = 5000 def hash_password(plain_password): salt = random_bytes(8) # 64 bits hashed_password = pbkdf_sha256(plain_password, salt, NUM_ITERATIONS) # return the salt and hashed password, encoded in base64 and split with "," return salt.encode("base64").strip() + "," + hashed_password.encode("base64").strip() def check_password(saved_password_entry, plain_password): salt, hashed_password = saved_password_entry.split(",") salt = salt.decode("base64") hashed_password = hashed_password.decode("base64") return hashed_password == pbkdf_sha256(plain_password, salt, NUM_ITERATIONS) password_entry = hash_password("mysecret") print password_entry # will print, for example: 8Y1ZO8Y1pi4=,r7Acg5iRiZ/x4QwFLhPMjASESxesoIcdJRSDkqWYfaA= check_password(password_entry, "mysecret") # returns True
基本策略是使用密钥派生函数用一些盐"哈希"密码.salt和哈希结果存储在数据库中.当用户输入密码时,盐和它们的输入以相同的方式进行散列并与存储的值进行比较.如果匹配,则对用户进行身份验证.
细节决定成败.首先,很大程度上取决于所选择的哈希算法.像PBKDF2这样的密钥派生算法,基于基于散列的消息认证代码,使得在计算上不可行地找到将产生给定输出的输入(在这种情况下,密码)(攻击者在数据库中找到的内容) ).
预先计算的字典攻击使用预先计算的索引或字典,从散列输出到密码.散列很慢(或者它应该是,无论如何),因此攻击者一次性地散列所有可能的密码,并以这样的方式存储索引结果,即给定散列,他可以查找相应的密码.这是时间空间的经典权衡.由于密码列表可能很大,因此有一些方法可以调整权衡(如彩虹表),这样攻击者就可以放弃一点速度来节省大量空间.
使用"加密盐"阻碍了预计算攻击.这是一些使用密码进行哈希处理的数据.它不需要是秘密,它只需要对给定的密码不可预测.对于盐的每个值,攻击者需要一个新的字典.如果使用一个字节的salt,攻击者需要256个字典副本,每个副本使用不同的盐生成.首先,他使用salt查找正确的字典,然后他使用hash输出来查找可用的密码.但是如果添加4个字节怎么办?现在他需要40亿份字典.通过使用足够大的盐,排除了字典攻击.实际上,来自加密质量随机数发生器的8到16个字节的数据是很好的.
通过预先计算表,攻击者可以在每次尝试时计算哈希值.现在找到密码需要多长时间完全取决于散列候选人所需的时间.通过散列函数的迭代来增加该时间.数字迭代通常是密钥导出函数的参数; 今天,许多移动设备使用10,000到20,000次迭代,而服务器可能使用100,000或更多.(bcrypt算法使用术语"成本因子",它是所需时间的对数度量.)
我想你必须在数据库中为加密密码添加一列,然后在获取当前密码的所有记录上运行批处理作业,对其进行加密(因为其他人提到像md5这样的哈希是非常标准的编辑:但不应该单独使用 - 请参阅其他答案以获得良好的讨论),将其存储在新列中并检查所有内容是否顺利进行.
然后,您需要更新前端以在登录时散列用户输入的密码,并验证是否与存储的散列相比,而不是检查明文与明文.
在最终删除明文密码之前,将两个列保留一段时间以确保没有任何异常发生,这似乎是谨慎的做法.
不要忘记,只要密码被访问,代码就必须更改,例如密码更改/提醒请求.你当然会失去通过电子邮件发送忘记密码的能力,但这不是坏事.您将不得不使用密码重置系统.
编辑:最后一点,您可能要考虑避免我第一次尝试在测试床安全登录网站上犯的错误:
处理用户密码时,请考虑进行散列的位置.在我的例子中,哈希是由在Web服务器上运行的PHP代码计算的,但密码是以明文形式从用户机器传输到页面的!这在我工作的环境中是可以的(ish),因为它无论如何都在https系统内(uni网络).但是,在现实世界中,我想你会想要在离开用户系统之前对密码进行哈希处理,使用javascript等,然后将哈希传输到你的站点.