【问题标题】:What is the best way to check the strength of a password?检查密码强度的最佳方法是什么?
【发布时间】:2010-09-09 15:46:39
【问题描述】:

在注册或更改密码表单中确保用户提供的密码是强密码的最佳方法是什么?

我有一个想法(在 python 中)

def validate_password(passwd):
    conditions_met = 0
    conditions_total = 3
    if len(passwd) >= 6: 
        if passwd.lower() != passwd: conditions_met += 1
        if len([x for x in passwd if x.isdigit()]) > 0: conditions_met += 1
        if len([x for x in passwd if not x.isalnum()]) > 0: conditions_met += 1
    result = False
    print conditions_met
    if conditions_met >= 2: result = True
    return result

【问题讨论】:

  • 有一个开放和免费的John the Ripper密码破解器,它是检查现有密码数据库的好方法。
  • 一个很好的问题,但答案很差。没有一个答案实际上是好的密码。
  • @Johan 实际上这是一个糟糕的问题。它要求以固执己见的方式来做某事,其中实现和质量必然会因上下文而异,甚至没有提供任何衡量答案质量的指标。
  • 答案当然是“correct horse battery staple”。我很惊讶这还不是第一条评论。

标签: algorithm security passwords


【解决方案1】:

根据语言的不同,我通常使用正则表达式来检查是否有:

  • 至少一个大写和一个 小写字母
  • 至少一个数字
  • 至少一个特殊字符
  • 长度至少为六个字符

您可以要求以上所有内容,或使用强度计类型的脚本。对于我的强度计,如果密码长度合适,则评估如下:

  • 满足一个条件:弱密码
  • 满足两个条件:中等密码
  • 满足所有条件:强密码

您可以根据需要调整以上内容。

【讨论】:

  • diceware 算法 (world.std.com/~reinhold/diceware.html) 除了最后一个(“至少六个字符的长度”)之外什么都不做,但是 6 字密码有 77 位熵——相当可观.
  • 如果可能的话,我会避免使用任意强加此处定义的大部分或全部指标的任何系统。对于任何设计密码强度检查器的人,请记住,您的一些用户已经从密码单词演变为密码短语。我的一次性密码短语是 38 个字符长,全部小写,并且有 170 位熵。即使是我们星球上最快的超级计算机,太阳也会在它被破解之前燃烧殆尽。相比之下,台式机破解一个刚好满足上述要求的密码大约需要 15 分钟。
  • 我还想澄清一下:向用户建议上述指标并使用强度计是完全可以接受的——只是不要强迫我勾选所需字符类型的列表。 It doesn't make my password more secure and it only makes it harder to remember。另外,不要将我限制为 8 或 12 或 16 个字符——无论如何你都应该存储密码哈希,所以我的实际密码的长度是无关紧要的;使用 SHA1 对 6 个字符的密码和 600 个字符的密码短语进行哈希处理,每次都在 100% 的情况下产生 40 个字符的哈希值。
  • 人类猜测是大多数帐户被盗用的方式......这就是强度评分试图防止的:人类可猜测的密码。例如,不要使用日期、姓名、电话号码、宠物的名字等。
  • 所以根据这个定义,“密码”或“秘密”或“123456”将具有中等强度。完美。
【解决方案2】:

面向对象的方法是一组规则。为每个规则分配一个权重并遍历它们。在伪代码中:

abstract class Rule {

    float weight;

    float calculateScore( string password );

}

计算总分:

float getPasswordStrength( string password ) {     

    float totalWeight = 0.0f;
    float totalScore  = 0.0f;

    foreach ( rule in rules ) {

       totalWeight += weight;
       totalScore  += rule.calculateScore( password ) * rule.weight;

    }

    return (totalScore / totalWeight) / rules.count;

}

基于存在的字符类数量的示例规则算法:

float calculateScore( string password ) {

    float score = 0.0f;

    // NUMBER_CLASS is a constant char array { '0', '1', '2', ... }
    if ( password.contains( NUMBER_CLASS ) )
        score += 1.0f;

    if ( password.contains( UPPERCASE_CLASS ) )
        score += 1.0f;

    if ( password.contains( LOWERCASE_CLASS ) )
        score += 1.0f;

    // Sub rule as private method
    if ( containsPunctuation( password ) )
        score += 1.0f;

    return score / 4.0f;

}

【讨论】:

  • 我想知道以更实用的方式实现它是否更有意义。必须对一大堆可能会使用一次的规则进行子类化,这似乎是一种浪费的打字。例如,创建一个 Rule 类并将匿名函数分配给给定实例的规则属性。
  • 我没有使用 Java,所以这可能适用于该语言,也可能不适用。
  • 我最近根据这个建议实施了密码强度检查器。能够添加和调​​整规则和权重的灵活性允许快速迭代。在花了一些时间推导出体面的规则后,通过比较已知的好密码和坏密码,我找到了一种无需字典即可识别字典单词和专有名词的方法。
【解决方案3】:

1:消除常用密码
根据常用密码列表检查输入的密码(例如,查看泄露的 LinkedIn 密码列表中的前 100.000 个密码:http://www.adeptus-mechanicus.com/codex/linkhap/combo_not.zip),确保包括leetspeek substitutions: A@、E3、B8、S5等
在进入下面的第 2 部分之前,从输入的短语中删除与此列表匹配的部分密码。

2:不要对用户强加任何规则

密码的黄金法则是越长越好。
忘记强制使用大写字母、数字和符号,因为(绝大多数)用户会: - 将第一个字母设为大写; - 把号码1放在最后; - 如果需要符号,请在其后添加!

改为检查密码强度

如需一个不错的起点,请参阅:http://www.passwordmeter.com/

我建议至少遵循以下规则:

Additions (better passwords)
-----------------------------
- Number of Characters              Flat       +(n*4)   
- Uppercase Letters                 Cond/Incr  +((len-n)*2)     
- Lowercase Letters                 Cond/Incr  +((len-n)*2)     
- Numbers                           Cond       +(n*4)   
- Symbols                           Flat       +(n*6)
- Middle Numbers or Symbols         Flat       +(n*2)   
- Shannon Entropy                   Complex    *EntropyScore

Deductions (worse passwords)
----------------------------- 
- Letters Only                      Flat       -n   
- Numbers Only                      Flat       -(n*16)  
- Repeat Chars (Case Insensitive)   Complex    -    
- Consecutive Uppercase Letters     Flat       -(n*2)   
- Consecutive Lowercase Letters     Flat       -(n*2)   
- Consecutive Numbers               Flat       -(n*2)   
- Sequential Letters (3+)           Flat       -(n*3)   
- Sequential Numbers (3+)           Flat       -(n*3)   
- Sequential Symbols (3+)           Flat       -(n*3)
- Repeated words                    Complex    -       
- Only 1st char is uppercase        Flat       -n
- Last (non symbol) char is number  Flat       -n
- Only last char is symbol          Flat       -n

仅仅关注passwordmeter 是不够的,因为它的幼稚算法确实认为 Password1! 很好,但它非常弱。 请务必在评分时忽略首字母大写以及尾随数字和符号(根据最后 3 条规则)。

计算香农熵
见:Fastest way to compute entropy in Python

3:不允许使用任何太弱的密码
与其强迫用户屈服于弄巧成拙的规则,不如允许任何能给出足够高分数的东西。多高取决于您的用例。

最重要的是
当您接受密码并将其存储在数据库中时,make sure to salt and hash it!

【讨论】:

    【解决方案4】:

    要检查的两个最简单的指标是:

    1. 长度。我会说至少 8 个字符。
    2. 密码包含的不同字符类别的数量。这些通常是小写字母、大写字母、数字和标点符号等符号。强密码将包含至少三个这些类别的字符;如果您强制使用数字或其他非字母字符,则会显着降低字典攻击的有效性。

    【讨论】:

      【解决方案5】:

      Cracklib 很棒,在较新的软件包中,有一个 Python 模块可供它使用。然而,在还没有它的系统上,比如 CentOS 5,我已经为系统 cryptlib 编写了一个 ctypes 包装器。这也适用于无法安装 python-libcrypt 的系统。它确实需要具有可用 ctypes 的 python,因此对于 CentOS 5,您必须安装并使用 python26 包。

      它还有一个优点,它可以获取用户名并检查包含它或基本相似的密码,如 libcrypt "FascistGecos" 函数,但不需要用户存在于 /etc/passwd 中。

      我的ctypescracklib library is available on github

      一些示例用法:

      >>> FascistCheck('jafo1234', 'jafo')
      'it is based on your username'
      >>> FascistCheck('myofaj123', 'jafo')
      'it is based on your username'
      >>> FascistCheck('jxayfoxo', 'jafo')
      'it is too similar to your username'
      >>> FascistCheck('cretse')
      'it is based on a dictionary word'
      

      【讨论】:

        【解决方案6】:

        在阅读了其他有用的答案后,我打算这样做:

        -1 与用户名相同
        +0 包含用户名
        +1 超过 7 个字符
        +1 超过 11 个字符
        +1 包含数字
        +1 大小写混合
        +1 包含标点符号
        +1 不可打印字符

        pwcore.py:

        import re
        import string
        max_score = 6
        def score(username,passwd):
            if passwd == username:
                return -1
            if username in passwd:
                return 0
            score = 0
            if len(passwd) > 7:
                score+=1
            if len(passwd) > 11:
                score+=1
            if re.search('\d+',passwd):
                score+=1
            if re.search('[a-z]',passwd) and re.search('[A-Z]',passwd):
                score+=1
            if len([x for x in passwd if x in string.punctuation]) > 0:
                score+=1
            if len([x for x in passwd if x not in string.printable]) > 0:
                score+=1
            return score
        

        示例用法:

        import pwscore
            score = pwscore(username,passwd)
            if score < 3:
                return "weak password (score=" 
                     + str(score) + "/"
                     + str(pwscore.max_score)
                     + "), try again."
        

        可能不是最有效的,但似乎是合理的。 不确定 FascistCheck => '与用户名太相似' 是 值得。

        'abc123ABC!@£' = 如果不是用户名的超集,则得分 6/6

        也许应该得分更低。

        【讨论】:

          【解决方案7】:

          这就是我使用的:

             var getStrength = function (passwd) {
              intScore = 0;
              intScore = (intScore + passwd.length);
              if (passwd.match(/[a-z]/)) {
                  intScore = (intScore + 1);
              }
              if (passwd.match(/[A-Z]/)) {
                  intScore = (intScore + 5);
              }
              if (passwd.match(/\d+/)) {
                  intScore = (intScore + 5);
              }
              if (passwd.match(/(\d.*\d)/)) {
                  intScore = (intScore + 5);
              }
              if (passwd.match(/[!,@#$%^&*?_~]/)) {
                  intScore = (intScore + 5);
              }
              if (passwd.match(/([!,@#$%^&*?_~].*[!,@#$%^&*?_~])/)) {
                  intScore = (intScore + 5);
              }
              if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/)) {
                  intScore = (intScore + 2);
              }
              if (passwd.match(/\d/) && passwd.match(/\D/)) {
                  intScore = (intScore + 2);
              }
              if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/) && passwd.match(/\d/) && passwd.match(/[!,@#$%^&*?_~]/)) {
                  intScore = (intScore + 2);
              }
              return intScore;
          } 
          

          【讨论】:

            【解决方案8】:

            我不知道是否有人会发现这很有用,但我真的很喜欢 phear 建议的规则集的想法,所以我编写了一个规则 Python 2.6 类(尽管它可能与 2.5 兼容):

            import re
            
            class SecurityException(Exception):
                pass
            
            class Rule:
                """Creates a rule to evaluate against a string.
                Rules can be regex patterns or a boolean returning function.
                Whether a rule is inclusive or exclusive is decided by the sign
                of the weight. Positive weights are inclusive, negative weights are
                exclusive. 
            
            
                Call score() to return either 0 or the weight if the rule 
                is fufilled. 
            
                Raises a SecurityException if a required rule is violated.
                """
            
                def __init__(self,rule,weight=1,required=False,name=u"The Unnamed Rule"):
                    try:
                        getattr(rule,"__call__")
                    except AttributeError:
                        self.rule = re.compile(rule) # If a regex, compile
                    else:
                        self.rule = rule  # Otherwise it's a function and it should be scored using it
            
                    if weight == 0:
                        return ValueError(u"Weights can not be 0")
            
                    self.weight = weight
                    self.required = required
                    self.name = name
            
                def exclusive(self):
                    return self.weight < 0
                def inclusive(self):
                    return self.weight >= 0
                exclusive = property(exclusive)
                inclusive = property(inclusive)
            
                def _score_regex(self,password):
                    match = self.rule.search(password)
                    if match is None:
                        if self.exclusive: # didn't match an exclusive rule
                            return self.weight
                        elif self.inclusive and self.required: # didn't match on a required inclusive rule
                            raise SecurityException(u"Violation of Rule: %s by input \"%s\"" % (self.name.title(), password))
                        elif self.inclusive and not self.required:
                            return 0
                    else:
                        if self.inclusive:
                            return self.weight
                        elif self.exclusive and self.required:
                            raise SecurityException(u"Violation of Rule: %s by input \"%s\"" % (self.name,password))
                        elif self.exclusive and not self.required:
                            return 0
            
                    return 0
            
                def score(self,password):
                    try:
                        getattr(self.rule,"__call__")
                    except AttributeError:
                        return self._score_regex(password)
                    else:
                        return self.rule(password) * self.weight
            
                def __unicode__(self):
                    return u"%s (%i)" % (self.name.title(), self.weight)
            
                def __str__(self):
                    return self.__unicode__()
            

            我希望有人觉得这很有用!

            示例用法:

            rules = [ Rule("^foobar",weight=20,required=True,name=u"The Fubared Rule"), ]
            try:
                score = 0
                for rule in rules:
                    score += rule.score()
            except SecurityException e:
                print e 
            else:
                print score
            

            免责声明:未经单元测试

            【讨论】:

            • 如果有人知道放置代码示例的更好位置并且此类和/或 SO 不合适,请告诉我。
            【解决方案9】:

            密码强度检查器,如果您有时间和资源(只有在检查多个密码时才合理),请使用 Rainbow Tables。

            【讨论】:

              【解决方案10】:

              通过一系列检查以确保其符合最低标准:

              • 至少 8 个字符
              • 至少包含一个非字母数字符号
              • 不匹配或不包含用户名/电子邮件/等。

              这是一个报告密码强度的 jQuery 插件(我自己没有尝试过): http://phiras.wordpress.com/2007/04/08/password-strength-meter-a-jquery-plugin/

              同样的东西移植到 PHP: http://www.alixaxel.com/wordpress/2007/06/09/php-password-strength-algorithm/

              【讨论】:

                【解决方案11】:

                在注册或更改密码表单中确保用户提供的密码是强密码的最佳方法是什么?

                不要评估复杂性和/或强度,用户会找到一种方法来欺骗您的系统或感到沮丧以至于他们会离开。那只会让你遇到like this的情况。只需要一定的长度并且不使用泄露的密码。加分项:确保您实现的任何内容都允许使用密码管理器和/或 2FA。

                【讨论】:

                  猜你喜欢
                  • 2011-05-20
                  • 2011-07-16
                  • 1970-01-01
                  • 2022-01-25
                  • 2012-08-15
                  • 1970-01-01
                  • 2014-01-15
                  • 2018-03-19
                  • 1970-01-01
                  相关资源
                  最近更新 更多