在数据库中encryption/散列纯文本密码

我已经inheritance了一个Web应用程序,我刚刚发现在SQL Server数据库中以纯文本格式存储了超过300,000个用户名/密码。 我意识到这是一个非常糟糕的事情。

知道我必须更新login和密码更新过程来进行encryption/解密,并且对系统其余部分的影响最小,那么作为从数据库中除去纯文本密码的最佳方式,您会推荐什么?

任何帮助表示赞赏。

编辑:对不起,如果我不清楚,我的意思是问什么是你的程序encryption/散列的密码,而不是具体的encryption/散列方法。

我应该只是:

  1. 备份数据库
  2. 更新login/更新密码
  3. 在几小时之后,通过用户表中的所有logging散列密码并replace每个logging
  4. testing以确保用户仍然可以login/更新密码

我想我的关注更多来自绝对数量的用户,所以我想确保我正确地做到了这一点。

我会想象你将不得不添加一个列到数据库的encryption密码,然后运行一个批处理作业的所有logging获取当前的密码,encryption它(其他人已经提到像md5哈希是非常标准的编辑:但不应该单独使用 – 请参阅其他答案进行良好的讨论 ),将其存储在新列中,并检查一切是否顺利。

然后,您需要更新前端以在login时散列用户input的密码,并validation存储的散列,而不是检查明文vs明文。

对于我来说,在两个专栏之间保留一段时间,以确保没有什么恶意行为已经发生,最终将所有明文密码全部删除之前,似乎谨慎行事。

不要忘记,只要密码已经存入,代码将不得不改变,例如密码更改/提醒请求。 当然,你将失去发送被遗忘密码的能力,但这不是件坏事。 您将不得不使用密码重置系统。

编辑:最后一点,你可能要考虑避免我在testing平台安全login网站上的第一次尝试的错误:

处理用户密码时,考虑散列发生的地方。 在我的情况下,哈希值是通过在networking服务器上运行的PHP代码计算出来的,但密码是以纯文本的forms从用户计算机传输到页面的! 在我工作的环境中,这是好的(ish),因为它在https系统内部(uninetworking)。 但是,在现实世界中,我想你会希望在密码离开用户系统之前使用javascript等来散列密码,然后将散列传送到您的网站。

编辑(2016):按照优先顺序使用Argon2 , scrypt , bcrypt或PBKDF2 。 使用尽可能大的减速因子是可行的。 使用经过审查的现有实施。 确保你使用适当的盐(虽然你正在使用的库应该为你确保这一点)。


在散列密码时, 请勿使用普通MD5

使用PBKDF2 ,这基本上意味着使用随机盐来防止彩虹表攻击,并重复(重新哈希)足够的时间来减缓哈希 – 不要太多,以至于你的应用程序需要很长的时间,但足以让攻击者蛮力大量不同的密码会通知

从文件:

  • 迭代至less1000次,最好是更多次执行,看看有多less次迭代可行。
  • 8字节(64位)的盐就足够了,随机的不需要安全(盐是未encryption的,我们并不担心有人会猜测它)。
  • 散列时应用salt的一个好方法是使用HMAC和你最喜欢的哈希algorithm,使用密码作为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来“散列”密码。 salt和散列结果存储在数据库中。 当用户input密码时,盐和它们的input以相同的方式被散列,并与存储的值相比较。 如果它们匹配,则用户被authentication。

细节决定成败。 首先,很多取决于所select的散列algorithm。 像PBKDF2这样的密钥派生algorithm,基于基于散列的消息authentication码,使得在计算上不可能find一个input(在这个例子中是一个密码),它将产生一个给定的输出(攻击者在数据库中发现了什么)。

预先计算的字典攻击使用从哈希输出到密码的预先计算的索引或字典。 散列速度很慢(或者它应该是这样),所以攻击者将所有可能的密码散列一次,并存储索引结果,以便给出一个散列,他可以查找相应的密码。 这是一个经典的时空折中。 由于密码列表可能很大,所以有办法调整权衡(如彩虹表),这样攻击者可以放弃一点速度来节省很多空间。

预计算攻击是通过使用“密码盐”来阻止的。 这是一些与密码散列的数据。 它不需要是一个秘密,只需要给定的密码是不可预知的。 对于盐的每个值,攻击者都需要一个新的字典。 如果您使用一个字节的盐,攻击者需要256个字典的副本,每个副本用不同的盐生成。 首先,他会使用salt来查找正确的字典,然后使用散列输出来查找可用的密码。 但是如果你添加4个字节呢? 现在他需要40亿本字典。 通过使用足够大的盐,字典攻击被排除在外。 实际上,来自密码质量随机数发生器的8到16个字节的数据是很好的盐。

通过预先计算表,攻击者计算每次尝试的散列值。 现在需要多长时间才能find密码,这完全取决于候选人需要多长时间。 这个时间通过迭代散列函数而增加。 数字迭代通常是密钥导出函数的参数; 今天,许多移动设备使用10,000到20,000次迭代,而服务器可能使用100,000次或更多。 (bcryptalgorithm使用术语“成本因子”,这是所需时间的对数度量。)

按照Xan的build议保持当前的密码列一段时间,所以如果事情不好,你可以回滚quick-n-easy。

至于encryption你的密码:

  • 使用盐
  • 使用用于密码的散列algorithm(即, – 它很

有关详细信息,请参阅Thomas Ptacek的“彩虹表的足够使用”:有关安全密码scheme的一些信息。

我认为你应该做以下几点:

  1. 创build一个名为HASHED_PASSWORD的新列或类似的东西。
  2. 修改您的代码,以便它检查两列。
  3. 逐渐将密码从非哈希表迁移到哈希表。 例如,当用户login时,将他或她的密码自动迁移到散列列,并删除未散列的版本。 所有新注册的用户将有散列的密码。
  4. 几小时后,您可以运行一个脚本,一次迁移n个用户
  5. 如果没有更多的未经保存的密码,您可以删除旧密码列(您可能无法这样做,这取决于您使用的数据库)。 此外,您可以删除代码来处理旧的密码。
  6. 你完成了!

几个星期前,这是我几个人的问题。 我们正在向975个不同的地理位置部署一个大型MIS项目,我们自己的用户凭证存储将用作不同的已经实施和正在使用的应用程序集的authentication器。 我们已经提供了基于REST和SOAP的身份validation服务,但是客户坚持要从其他应用程序访问用户凭证存储,只需将数据库连接到相关表或视图的只读视图。 叹息… (这个高度耦合的糟糕的devise决定是另一个问题的主题)。

这迫使我们坐下来,将我们的腌制和迭代哈希密码存储scheme转换为规范,并提供一些不同的语言实现,以便于集成。

我们称之为相当安全的哈希密码或简称FSHP 。 在Python,Ruby,PHP5中实现,并将其发布到公共领域。 可以在http://github.com/bdd/fshp上使用,分叉,燃烧或吐出GitHub

FSHP是一个腌制的迭代散列密码哈希实现。

devise原则与RFC 2898中的PBKDF1规范类似(又名:PKCS#5:基于密码的密码规范版本2.0) 。FSHP允许在SHA-1和SHA-2中select盐长度,迭代次数和底层密码散列函数(256,384,512)。 在每个输出开始时自定义元前缀使其变得便于携带,同时让消费者select自己的密码存储安全基线。

安全

默认的FSHP1使用8字节的盐,4096次迭代的SHA-256散列。 – 8字节的盐渲染彩虹表攻击是不切实际的,所需的空间乘以2 ^ 64。 – 4096次迭代导致蛮力攻击相当昂贵。 – 没有已知的攻击针对SHA-256发现冲突,在此版本发布时,计算量less于2 ^ 128个操作。

实现:

  • Python:用2.3.5(w / hashlib),2.5.1,2.6.1testing
  • Ruby:testing1.8.6
  • PHP5:testing5.2.6

每个人都欢迎创build缺less的语言实现或抛光当前的语言。

基本操作(使用Python)

 >>> fsh = fshp.crypt('OrpheanBeholderScryDoubt') >>> print fsh {FSHP1|8|4096}GVSUFDAjdh0vBosn1GUhzGLHP7BmkbCZVH/3TQqGIjADXpc+6NCg3g== >>> fshp.validate('OrpheanBeholderScryDoubt', fsh) True 

自定义CRYPT:

让我们削弱我们的密码散列scheme。 – 将盐的长度从默认值8减less到2. – 将迭代周期从默认值4096减less到10. – select带有SHA-1的FSHP0作为基础的哈希algorithm。

 >>> fsh = fshp.crypt('ExecuteOrder66', saltlen=2, rounds=10, variant=0) >>> print fsh {FSHP0|2|10}Nge7yRT/vueEGVFPIxcDjiaHQGFQaQ== 

正如其他人提到的,如果你能帮到你,你不想解密。 标准的最佳做法是使用单向散列进行encryption,然后在用户login时对其密码进行散列以进行比较。

否则,你将不得不使用强大的encryption来encryption,然后解密。 如果政治原因很强大(例如,您的用户习惯于能够呼叫帮助台取回密码,并且您有强大的压力,不会改变这一点),我只会推荐这一点。 在这种情况下,我会先从encryption开始,然后开始构build一个商业案例以转向哈希。

出于身份validation的目的,您应该避免使用可逆encryption来存储密码,也就是说,您应该只存储密码哈希值,并根据您存储的哈希值检查用户提供的密码的哈希值。 但是,这种方法有一个缺点:如果攻击者抓住你的密码存储数据库,就容易受到彩虹表攻击。

你应该做的是存储一个预选(和秘密)盐值+密码的哈希。 即,连接盐和密码,散列结果,并存储这个散列。 进行身份validation时,执行相同的操作 – 连接salt值和用户提供的密码hash,然后检查是否相等。 这使得彩虹桌的攻击是不可行的。

当然,如果用户通过networking发送密码(例如,如果您正在使用networking或客户端 – 服务器应用程序),那么您不应该以明文forms发送密码,而不是将散列(salt +密码),你应该存储和检查哈希(salt +哈希(密码)),并让你的客户端预先哈希用户提供的密码,并通过networking发送一个。 这也保护了你的用户的密码,如果用户(同样多)重复使用相同的密码有多种用途。

  • 使用类似于MD5的东西进行encryption,将其编码为hexstring
  • 你需要一个盐; 在你的情况下,用户名可以作为盐(它必须是唯一的,用户名应该是最可用的唯一值;-)
  • 使用旧的密码字段存储MD5,但标记MD5(ieg“MD5:687A878 ….”),以便旧(纯文本)和新(MD5)密码可以共存
  • 如果存在MD5,则更改login过程以对MD5进行validation,否则,对纯密码进行validation
  • 只需更改“更改密码”和“新用户”function即可创buildMD5密码
  • 现在您可以运行转换批处理作业,这可能需要很长时间
  • 在转换运行之后,删除legacy-support

步骤1:将encryption的字段添加到数据库

步骤2:更改代码,以便在更改密码时更新两个字段,但login仍使用旧字段。

第3步:运行脚本来填充所有新的字段。

第4步:更改代码,以便login使用新的字段和更改密码停止更新旧字段。

第5步:从数据库中删除未encryption的密码。

这应该允许您在不中断最终用户的情况下完成转换。

另外:我会做的是命名新的数据库字段的东西是完全不相关的密码,如“LastSessionID”或类似的无聊。 然后,而不是删除密码字段,只是随机数据散列填充。 然后,如果您的数据库遭到破坏,他们可以花费所有时间来尝试解密“密码”字段。

这可能实际上并没有完成任何事情,但想到有人坐在那里试图弄清楚毫无价值的信息是很有趣的

与所有的安全决策一样,也存在权衡。 如果您input密码(这可能是您最简单的操作),那么您将无法提供密码恢复function来返回原始密码,您的员工也无法查找用户的密码以访问其帐户。

您可以使用对称encryption,这有其自身的安全缺陷。 (如果您的服务器遭到入侵,那么对称encryption密钥也可能会受到影响)。

您可以使用公钥encryption,并在独立于Web应用程序存储私钥的单独机器上运行密码检索/客户服务。 这是最安全的,但需要一个双机架构,并可能介于两者之间的防火墙。

我不是一个安全专家,但我目前的build议是使用bcrypt / blowfish或SHA-2变种,而不是MD5 / SHA1。

也许你需要考虑全面的安全审计

MD5和SHA1显示了一点弱点(两个单词可能导致相同的散列),所以build议使用SHA256-SHA512 /迭代散列来散列密码。

我会用应用程序写入的语言编写一个小程序,并生成一个随机salt,它对于每个用户都是唯一的,并且是密码的散列。 我倾向于使用相同的语言作为validation的原因是不同的encryption库可以做一些稍微不同的事情(即填充),所以使用相同的库生成哈希,并validation它消除了这种风险。 这个应用程序还可以在表更新后validationlogin,因为它知道纯文本密码仍然存在。

  1. 不要使用MD5 / SHA1
  2. 生成一个好的随机盐(许多encryption库有一个盐生成器)
  3. 推荐使用orip的迭代哈希algorithm
  4. 确保密码不是以纯文本的forms传输的

我想build议对Orip发布的伟大python示例进行一个改进。 我会重新定义random_bytes函数是:

 def random_bytes(num_bytes): return os.urandom(num_bytes) 

当然,你将不得不导入os模块。 os.urandom函数提供了可以在encryption应用程序中安全使用的随机字节序列。 有关更多详细信息,请参阅此function的参考帮助 。

哈希密码,您可以使用HashBytes函数。 返回一个varbinary,所以你必须创build一个新的列,然后删除旧的varchar。

喜欢

 ALTER TABLE users ADD COLUMN hashedPassword varbinary(max); ALTER TABLE users ADD COLUMN salt char(10); --Generate random salts and update the column, after that UPDATE users SET hashedPassword = HashBytes('SHA1',salt + '|' + password); 

然后你修改代码来validation密码,使用类似的查询

 SELECT count(*) from users WHERE hashedPassword = HashBytes('SHA1',salt + '|' + <password>) 

其中<密码>是用户input的值。

用md5把它们打散。 这通常是通过密码完成的。