【问题标题】:Preventing password reuse with Devise使用设计防止密码重复使用
【发布时间】:2012-02-08 00:42:51
【问题描述】:

我知道强制密码在用户创建密码后的一段时间后过期不是设计逻辑的一部分,我打算编写自己的代码来实现这一点。

看起来还需要手动编码强制用户不重复使用最后 X 个(在我的情况下为 10 个)密码之一。

我的想法是,我将创建一个类似于 user_passwords 表的东西,并在我的代码中使用逻辑来确保新密码与该用户表中的任何密码都不匹配。同时,我会将新密码插入表中,除非该用户已经有 10 条记录,这意味着我将用新值覆盖最旧的记录。表结构是这样的:

用户密码

  • user_id
  • 加密密码
  • created_at

如果有人有更好、更优雅的解决方案来处理这个问题,我将不胜感激。

【问题讨论】:

  • 我认为“不要使用你最后 10 个密码中的任何一个”有点武断,除非你有具体的商业原因。您可以轻松地强制执行“不要让新密码与旧密码相同”。现实情况是密码很难记住,而您要推动的行为是让用户在便笺上写下密码。
  • 这是客户的实际需求。这是一家金融公司。
  • 那么我认为没有比你提出的更优雅的解决方案了......
  • 找到这个:devise_security_extension
  • 太棒了。用那个回答你自己的问题!

标签: ruby-on-rails devise


【解决方案1】:

我知道强制密码在用户创建密码后的一段时间后过期不是设计逻辑的一部分,我打算编写自己的代码来实现这一点。

在实践中,与安全相关的研究发现这是一个坏主意。那是因为每次更改您都会获得递减的回报。也就是说,随着用户尝试遵守策略,密码开始时很强大,然后随着时间的推移变得更弱。请参阅 Peter Gutmann 的 Engineering Security 和第 7 章,密码

从书中,其他愚蠢的事情包括复杂性要求。 (在你反对之前,请阅读本书的相关部分)。


...创建类似 user_passwords 表的内容,并在我的代码中使用逻辑来确保新密码与该用户表中的任何密码都不匹配。

一旦你阅读了这一章,我就会问:为什么你首先允许用户选择一个弱/受伤/损坏的密码?与Mark Brunett's list of 10 million leaked passwords 结合使用时,那些 60 KB 的 Bloom 过滤器看起来非常有用:)


防止密码重复使用...

会造成伤害的重用是跨站点的密码重用。 Brown、Bracken、Zoccoli 和 Douglas 表示,Generating and Remembering Passwords 中的数字约为 70%(应用认知心理学,第 18 卷,第 6 期,第 641-651 页)。 Das、Bonneau、Caesar、Borisov 和 Wang 在The Tangled Web of Password Reuse 中报告的数字约为 45%。请注意,Tangled Web 研究必须破解密码,因此该数字可能更高,因为他们无法恢复所有密码

根据The Tangled Web of Password Reuse 中的 Das、Bonneau、Caesar、Borisov 和 Wang 的说法,为了使重用成为更严重的问题,用户必须记住大约 25 个不同站点的密码。

几年前我什至被这个烫伤了。我在两个低价值帐户上使用了相同的密码。然后GNU's Savannah 被黑了,攻击者可以使用恢复的密码来入侵一个用过的电子邮件帐户。

现在,当我需要凭据时,我只生成一个长的随机字符串。对于大多数网站,我什至不费心把它们写下来。当我需要再次访问某个站点时,我只需执行恢复过程即可。

【讨论】:

  • 遗憾的是密码恢复过程是穷人的两因素身份验证。
【解决方案2】:

devise_security_extension 似乎可以满足我的需要。

但是,目前它不支持 Devise 2.0 或更高版本。我遇到了许多问题,不得不将我的 Devise 降级到 1.5.3。根据他们留言板上的 cmets,他们目前正在努力将 gem 移植到与 Devise 2.0 兼容的版本。

我对它的 password_expirable 和 password_archivable 模块进行了修改。一切似乎都按预期工作。

它还支持secure_validatable、session_limitable和expirable,前两个我可能会在不久的将来使用。

【讨论】:

【解决方案3】:

在 PasswordsController 中可以查看。

class PasswordsController < Devise::PasswordsController

  def update
    current_user = User.with_reset_password_token(params[:user][:reset_password_token])
    if current_user && previous_and_new_password_is_same?(current_user)
      current_user.errors[:password] << "has been used previously."
      self.resource = current_user
      respond_with resource
    else
      super
    end
  end

  private

  def previous_and_new_password_is_same?(current_user)
    bcrypt       = ::BCrypt::Password.new(current_user.encrypted_password)
    hashed_value = ::BCrypt::Engine.hash_secret([params[:user][:password], Devise.pepper].join, bcrypt.salt)
    hashed_value == current_user.encrypted_password
  end

end

Here 是 Devise gem 的文档,用于如何禁用以前使用的密码?

【讨论】:

    【解决方案4】:

    devise_security_extension 在 rails 5 中对我不起作用,我创建了我的自定义:

    • 创建一个迁移并添加一个额外的列,例如: add_column :users, :old_passwords, :text
    • 在您的模型中添加两​​个回调:after_save :cache_old_pass 和 before_save :verify_old_pass

    • 创建回调方法:

      private
      def verify_old_pass
        if self.encrypted_password_changed?
          old_passwords.to_s.split(',').each do |pass_encrypted|
            if Devise::Encryptor.compare(self.class, pass_encrypted, password)
              errors.add(:base, 'Your password cannot be previous up to 3 back')
              return throw(:abort)
            end
          end
        end
      end
      
      def cache_old_pass # cache last 3 passwords
       if self.encrypted_password_changed?
         update_column(:old_passwords, ([self.encrypted_password] + old_passwords.to_s.split(',')[0, 3]).join(','))
       end
      end
      

    【讨论】:

    • 告诉我们您没有保存加密或未加密的实际密码,那是完全不负责任的。当攻击者获得数据库时,他也将获得加密密钥。
    • 这不是保存未加密的密码,而是保存设计生成的历史密码并由设计验证(没有被黑)
    • 所以我猜 Devise::Encryptor 是错误命名的,因为它似乎执行的是散列,而不是加密——这是个好消息。如果docs 能准确地解释正在发生的事情,那就太好了,但我猜你付出了什么。糟糕的命名应该受到惩罚,但我们只是在编码猴子,而不是专业人士。
    猜你喜欢
    • 2018-09-14
    • 1970-01-01
    • 2014-09-01
    • 2021-09-29
    • 2020-05-06
    • 2017-09-25
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    相关资源
    最近更新 更多