关于算法:检查密码强度的最佳方法是什么?

关于算法:检查密码强度的最佳方法是什么?

What is the best way to check the strength of a password?

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

我有一个主意(在python中)

1
2
3
4
5
6
7
8
9
10
11
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

根据语言,我通常使用正则表达式检查它是否具有:

  • 至少一个大写和一个
    小写字母
  • 至少一个号码
  • 至少一个特殊字符
  • 至少六个字符的长度

您可以要求以上所有条件,或者使用强度计类型的脚本。对于我的强度计,如果密码的长度正确,则密码的评估如下:

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

您可以调整以上以满足您的需求。


面向对象的方法将是一组规则。为每个规则分配权重并对其进行迭代。在伪代码中:

1
2
3
4
5
6
7
abstract class Rule {

    float weight;

    float calculateScore( string password );

}

计算总分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;

}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

}


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

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

密码的黄金法则是越长越好。
无需强制使用大写字母,数字和符号,因为(绝大多数)用户将:
-将首字母大写;
-将数字1放在末尾;
-如果需要符号,则在其后放置!

而是检查密码强度

有关合适的起点,请访问:http://www.passwordmeter.com/

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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条规则)。

计算香农熵
请参阅:用Python计算熵的最快方法

3:不允许任何太弱的密码
与其强迫用户屈服于自欺欺人的规则,还不如让一切得分都足够高。多少取决于您的用例。

最重要的是
当您接受密码并将其存储在数据库中时,请确保对其加盐并进行哈希处理!


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

  • 长度。我至少要说8个字符。
  • 密码包含的不同字符类的数量。这些通常是小写字母,大写字母,数字和标点符号以及其他符号。强密码将包含至少三个此类的字符;如果您强制使用数字或其他非字母字符,则会大大降低字典攻击的效率。

  • 阅读其他有用的答案后,这就是我要去的事情:

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

    pwscore.py:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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

    用法示例:

    1
    2
    3
    4
    5
    6
    7
    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

    也许那应该得分更低。


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

    它还具有以下优点:可以使用用户名并检查包含该用户名的密码或基本相似的密码,例如libcrypt的" FascistGecos"功能,但无需用户存在于/ etc / passwd中。

    我的ctypescracklib库可在github上找到

    一些示例使用:

    1
    2
    3
    4
    5
    6
    7
    8
    >>> 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'


    好吧,这就是我用的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
       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;
    }

    有一个免费的开放式Ripper密码破解器,它是检查现有密码数据库的好方法。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    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__()

    我希望有人觉得这有用!

    用法示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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

    免责声明:未经单元测试


    我写了一个小的Javascript应用程序。看看:另一个密码表。您可以下载源代码并在GPL下使用/修改它。玩得开心!


    除了上周混合使用字母,数字和符号的标准方法外,我上周在MyOpenId上注册时还注意到,密码检查器会告诉您密码是否基于词典单词,即使您添加数字或用相似数字替换字母也是如此。 (使用零而不是'o',使用'1'代替'i',依此类推)。

    我印象深刻。


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

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

    这是一个报告密码强度的jQuery插件(我自己没有尝试过):
    Password Strength Meter (a jquery plugin)

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


    密码强度检查器,以及如果您有时间和资源(仅当您检查多个密码时才是合理的),请使用Rainbow Tables。


    如果有时间,请对它运行密码破解程序。


    What is the best way of ensuring that a user supplied password is a strong password in a registration or change password form?

    不要评估复杂性和强度,用户会发现一种使您的系统受骗或使他们沮丧以至于无法使用的方法。这样只会让您遇到这种情况。仅要求一定的长度,不会使用泄漏的密码。优点:请确保您实施的所有内容均允许使用密码管理器和/或2FA。


    推荐阅读