我已经发现了一个Web应用程序,我刚刚在SQL Server数据库中以纯文本形式存储了超过300,000个用户名/密码 . 我意识到这是一件非常糟糕的事情 .
知道我必须更新登录和密码更新过程以加密/解密,并且对系统其余部分的影响最小,您会建议从数据库中删除纯文本密码的最佳方法是什么?
任何帮助表示赞赏 .
Edit: Sorry if I was unclear, I meant to ask what would be your procedure to encrypt/hash the passwords, not specific encryption/hashing methods.
我应该只是:
-
备份数据库
-
更新登录/更新密码
-
下班后,浏览用户表中记录密码并替换每个密码的所有记录
-
测试以确保用户仍然可以登录/更新密码
我想我的关注更多来自于大量的用户,所以我想确保我正确地做到这一点 .
16 回答
第1步:将加密字段添加到数据库
第2步:更改代码,以便在更改密码时更新两个字段,但登录仍使用旧字段 .
第3步:运行脚本以填充所有新字段 .
第4步:更改代码,以便登录使用新字段和更改密码停止更新旧字段 .
步骤5:从数据库中删除未加密的密码 .
这应该允许您在不中断最终用户的情况下完成转换 .
另外:我要做的就是将新数据库字段命名为与“LastSessionID”或类似无聊的密码完全无关的东西 . 然后,只需填充随机数据的哈希值,而不是删除密码字段 . 然后,如果您的数据库遭到入侵,他们可以花费所有时间来尝试解密“密码”字段 .
这实际上可能无法实现任何目标,但想到有人坐在那里试图找出毫无 Value 的信息,这很有趣
使用类似MD5的内容进行加密,将其编码为十六进制字符串
你需要一个盐;在您的情况下,用户名可以用作盐(它必须是唯一的,用户名应该是唯一可用的值;-)
使用旧密码字段存储MD5,但标记MD5(即g "MD5:687A878...."),以便旧(纯文本)和新(MD5)密码可以共存
如果存在MD5,
更改登录过程以针对MD5进行验证,否则更改普通密码
更改"change password"和"new user"函数以仅创建MD5密码
现在您可以运行转换批处理作业,这可能需要多长时间
运行转换后
,删除旧版支持
我不是安全专家,但我认为目前的建议是使用bcrypt / blowfish或SHA-2变体,而不是MD5 / SHA1 .
也许你需要考虑完整的安全审计
几个星期前,这是我的一个问题 . 我们正在将一个大型MIS项目部署到975个不同的地理位置,我们自己的用户凭证存储将用作不同已经实现和正在使用的应用程序集的验证器 . 我们已经提供了基于REST和SOAP的身份验证服务,但客户坚持能够从其他应用程序到达用户凭据存储,只需要一个DB连接即可查看相关表或视图的只读视图 . 叹息......(这种高度耦合的糟糕设计决定是另一个问题的主题) .
这迫使我们坐下来将我们的盐渍和迭代散列密码存储方案转换为规范,并提供一些不同的语言实现以便于集成 .
我们称它为Fairly Secure Hashed Passwords或简称FSHP . 在Python,Ruby,PHP5中实现它并将其发布到Public Domain . 可在GitHub上消费,分叉,火焰或吐痰http://github.com/bdd/fshp
FSHP是一个盐渍的,迭代哈希的密码哈希实现 .
设计原则类似于RFC 2898中的PBKDF1规范(又名:PKCS#5:基于密码的密码规范2.0版).FSHP允许在SHA-1和SHA-2中选择盐长度,迭代次数和底层加密散列函数(256,384,512) . 在每个输出的开头自定义元前缀使其可移植,同时让消费者选择自己的密码存储安全基线 .
SECURITY :
默认FSHP1使用8字节盐,具有4096次SHA-256散列迭代 . - 8字节盐使得彩虹表攻击通过将所需空间乘以2 ^ 64而变得不切实际 . - 4096次迭代导致暴力攻击相当昂贵 . - 在此版本发布时,没有已知的针对SHA-256的攻击来查找具有少于2 ^ 128个操作的计算工作量的冲突 .
IMPLEMENTATIONS:
Python:使用2.3.5(w / hashlib),2.5.1,2.6.1进行测试
Ruby:测试1.8.6
PHP5:使用5.2.6进行测试
每个人都非常欢迎创建缺少语言的实现或改进当前的实现 .
BASIC OPERATION (with Python):
CUSTOMIZING THE CRYPT: 让我们削弱我们的密码散列方案 . - 将盐长度从默认值8减少到2. - 将迭代轮次从默认值4096减少到10. - 选择FSHP0,使用SHA-1作为基础哈希算法 .
MD5和SHA1显示出一些弱点(两个字可能导致相同的散列),因此建议使用SHA256-SHA512 /迭代散列来散列密码 .
我会用编写应用程序的语言编写一个小程序,然后生成一个对每个用户都是唯一的随机盐和密码的哈希值 . 我倾向于使用与验证相同的语言的原因是不同的加密库可以稍微不同地做一些事情(即填充),因此使用相同的库来生成散列并验证它消除了这种风险 . 此应用程序还可以在表更新后验证登录,如果您需要,因为它仍然知道纯文本密码 .
不要使用MD5 / SHA1
生成一个好的随机盐(许多加密库有一个盐生成器)
推荐使用orip的迭代哈希算法
确保密码不通过电汇以纯文本形式传输
我想你必须在数据库中为加密密码添加一列,然后在获取当前密码的所有记录上运行批处理作业,对其进行加密(因为其他人提到像md5这样的散列很标准 edit: but should not be used on its own - see other answers for good discussions ),存储它在新专栏中检查一切顺利进行 .
然后,您需要更新前端以在登录时对用户输入的密码进行哈希处理,并验证是否与存储的哈希相比,而不是检查plaintext-vs-plaintext .
在最终将明文密码全部删除之前,将两个列保留一段时间以确保没有任何异常发生,这似乎是谨慎的做法 .
不要忘记,只要密码被访问,代码就必须更改,例如密码更改/提醒请求 . 你当然会失去通过电子邮件发送忘记密码的能力,但这不是坏事 . 您将不得不使用密码重置系统 .
编辑:最后一点,您可能要考虑避免我在第一次尝试在测试床安全登录网站上犯的错误:
处理用户密码时,请考虑散列发生的位置 . 在我的情况下,哈希是由在Web服务器上运行的PHP代码计算的,但密码是以明文形式从用户机器传输到页面的!这在我工作的环境中是可以的(ish),因为它无论如何都在https系统内(uni network) . 但是,在现实世界中,我想你会想要在离开用户系统之前对密码进行哈希处理,使用javascript等,然后将哈希传输到你的站点 .
按照Xan's advice保持当前密码列一段时间,如果情况变坏,您可以快速回滚 .
至于加密密码:
使用盐
使用's meant for passwords (ie., - it'慢的哈希算法
有关详细信息,请参阅Thomas Ptacek的Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes .
基本策略是使用密钥派生函数用一些盐“哈希”密码 . salt和哈希结果存储在数据库中 . 当用户输入密码时,盐和它们的输入以相同的方式进行散列并与存储的值进行比较 . 如果匹配,则对用户进行身份验证 .
细节决定成败 . 首先,很大程度上取决于所选择的哈希算法 . 像PBKDF2这样的密钥派生算法,基于基于散列的消息认证代码,使得在计算上不可行地找到将产生给定输出的输入(在这种情况下,密码)(攻击者在数据库中找到了什么) ) .
预先计算的字典攻击使用预先计算的索引或字典,从散列输出到密码 . 散列很慢(或者它应该是,无论如何),因此攻击者一次性散列所有可能的密码,并以这样的方式存储索引结果,即给定哈希,他可以查找相应的密码 . 这是时间空间的经典权衡 . 由于密码列表可能很大,因此有一些方法可以调整权衡(如彩虹表),这样攻击者就可以放弃一点速度来节省大量空间 .
使用"cryptographic salt"阻止了预计算攻击 . 这是一些使用密码进行哈希处理的数据 . It doesn't need to be a secret, 对于给定的密码,它只需要是不可预测的 . 对于盐的每个值,攻击者需要一个新的字典 . 如果您使用一个字节的盐,攻击者需要256个字典副本,每个副本使用不同的盐生成 . 首先,他'd use the salt to lookup the correct dictionary, then he'd使用哈希输出来查找可用的密码 . 但是如果添加4个字节怎么办?现在他需要40亿份字典 . 通过使用足够大的盐,排除了字典攻击 . 实际上,来自加密质量随机数发生器的8到16个字节的数据是很好的 .
通过预先计算表,a攻击者在每次尝试时计算哈希值 . 现在找到密码需要多长时间完全取决于散列候选人所需的时间 . 通过散列函数的迭代来增加该时间 . 数字迭代通常是密钥导出函数的参数;今天,许多移动设备使用10,000到20,000次迭代,而服务器可能使用100,000或更多 . (bcrypt算法使用术语“成本因子”,这是所需时间的对数度量 . )
我认为你应该做以下事情:
创建一个名为HASHED_PASSWORD的新列或类似的东西 .
修改代码,以便检查两列 .
逐渐将密码从非散列表迁移到散列表 . 例如,当用户登录时,将其密码自动迁移到散列列并删除未散列的版本 . 所有新注册的用户都将使用哈希密码 .
下班后,您可以运行一个可以一次迁移n个用户的脚本
当您没有剩余未加密码的密码时,您可以删除旧密码列(您可能无法删除,取决于您使用的数据库) . 此外,您可以删除代码来处理旧密码 .
你做完了!
与所有安全决策一样,存在权衡 . 如果您对密码进行散列(这可能是最简单的移动),则无法提供返回原始密码的密码检索功能,您的员工也无法查找某人的密码以访问其帐户 .
您可以使用对称加密,它有自己的安全缺陷 . (如果您的服务器遭到入侵,对称加密密钥也可能会受到损害) .
您可以使用公钥加密,并在单独的计算机上运行密码检索/客户服务,该计算机将私钥与Web应用程序隔离 . 这是最安全的,但需要双机架构,并且可能介于两者之间 .
用md5哈希它们 . 这就是通常用密码完成的事情 .
我想建议the great python example posted by Orip的一个改进 . 我将重新定义
random_bytes
函数为:当然,您必须导入
os
模块 .os.urandom
函数提供随机的字节序列,可以安全地用于加密应用程序 . 有关详细信息,请参见the reference help of this function .出于身份验证的目的,您应该避免使用可逆加密存储密码,即您应该只存储密码哈希,并根据您存储的哈希检查用户提供的密码的哈希值 . 但是,这种方法有一个缺点:如果攻击者获取了您的密码存储数据库,它很容易受到攻击 .
你应该做的是存储一个预先选择(和秘密)盐值的哈希密码 . 即,连接salt和密码,散列结果,并存储此哈希 . 进行身份验证时,请执行相同操作 - 连接salt值和用户提供的密码hash,然后检查是否相等 . 这使彩虹表攻击变得不可行 .
当然,如果用户通过网络发送密码(例如,如果您正在使用Web或客户端 - 服务器应用程序),那么您不应该以明文形式发送密码,因此不要存储哈希(盐密码)你应该存储和检查哈希(盐哈希(密码)),并让你的客户端预先哈希用户提供的密码,并通过网络发送密码 . 如果用户(尽可能多)重复使用相同的密码用于多种目的,这也可以保护用户的密码 .
正如其他人所提到的,如果你能提供帮助,你不想解密 . 标准的最佳做法是使用单向散列进行加密,然后在用户登录哈希时使用密码进行比较 .
否则,您将不得不使用强加密来加密然后解密 . 如果政治原因很强,我只会建议这样做(例如,您的用户习惯于可以致电服务台来检索他们的密码,并且您有来自顶层的强大压力而不是改变它) . 在那种情况下,我将从加密开始,然后开始构建业务案例以转向哈希 .
编辑(2016):按优先顺序使用Argon2,scrypt,bcrypt或PBKDF2 . 使用尽可能大的减速因子 . 使用经审查的现有实施 . 确保使用适当的盐(尽管您正在使用的库应该为您确保这一点) .
哈希密码时使用 DO NOT USE PLAIN MD5 .
使用PBKDF2,这基本上意味着使用随机盐来防止rainbow table攻击,并且迭代(重新散列)足够的时间来减慢散列速度 - 不是说你的应用程序需要太长时间,而是足以让攻击者暴力破解不同密码的数量会通知
从文件:
至少迭代1000次,最好是更长时间实现以查看有多少次迭代是可行的 .
8个字节(64位)的盐就足够了,并且随机不会担心有人会猜到它 .
在散列时应用salt的一种好方法是使用HMAC和您喜欢的哈希算法,使用密码作为HMAC密钥,使用salt作为哈希文本(参见文档的this section) .
Python中的示例实现,使用SHA-256作为安全哈希:
EDIT :如Eli Collins所述,这不是PBKDF2实施 . 您应该更喜欢符合标准的实现,例如PassLib .
要散列密码,您可以使用HashBytes函数 . 返回varbinary,因此您必须创建一个新列,然后删除旧的varchar .
喜欢
然后使用类似的查询修改代码以验证密码
其中<password>是用户输入的值 .