用于验证密码强度的正则表达式

198

我的密码强度标准如下:

  • 长度为8个字符
  • 2个大写字母
  • 1个特殊字符(!@#$&*)
  • 2个数字(0-9)
  • 3个小写字母

请问有人能给我编写相应的正则表达式吗?密码必须满足所有条件。


2
你真的愿意把你的密码安全措施托付给整个互联网吗? - Borealid
20
@Borealid说,公布你的密码策略通常 不会 显著影响你的安全性。如果会有影响的话,那么你的策略就是糟糕的(“只有 passwordhello123 是有效的密码!”)。 - Joachim Sauer
3
@Joachim Sauer: 我的意思不是这样的。我的意思是那位发帖者可能会相信他所收到的任何正则表达式,这并不是一个好主意。 - Borealid
4
实际上,这个正则表达式将会被用在服务代码中,我会测试它来检验不同的情况,而不是盲目相信它 :) - Ajay Kelkar
11
复杂的密码规则通常不会导致更安全的密码,重要的是只要有最小长度即可。人们无法记住大量强密码,而这样的规则可能会干扰良好的密码方案。人们可以变得非常有创意,以规避此类规则,例如使用像“Password-2014”这样的弱密码。通常情况下,你会得到比原来更弱的密码,而不是更强的密码。 - martinstoeckli
显示剩余3条评论
13个回答

592

您可以使用正向预查断言来进行这些检查:

^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8}$

Rubular link

^                         Start anchor
(?=.*[A-Z].*[A-Z])        Ensure string has two uppercase letters.
(?=.*[!@#$&*])            Ensure string has one special case letter.
(?=.*[0-9].*[0-9])        Ensure string has two digits.
(?=.*[a-z].*[a-z].*[a-z]) Ensure string has three lowercase letters.
.{8}                      Ensure string is of length 8.
$                         End anchor.

135
对于任何想要长度至少为 n 的人,将 .{8} 替换为 .{n,} - NullUserException
26
感谢您的信任。我的翻译如下:根据您的回答,我可以调整正则表达式,尽管我的密码规则不同。请给一个完整的解释点赞。 - Morvael
18
谢谢您解释正则表达式中正在发生的事情。对于那些从未真正了解语法的人来说,这是一个很好的学习例子。我将尽力使翻译更加通俗易懂,但不会改变原本的意思。 - user673046
4
我也感谢对正则表达式的解释。很多时候,我使用自己找到的复杂正则表达式,却不真正理解其含义。 - Nicholas Smith
10
很棒的模式,不过我想知道为什么没有使用量词?至少要包含1个大写字母、1个数字、1个特殊字符和8个字符:^(?=.([A-Z]){1,})(?=.[!@#$&]{1,})(?=.[0-9]{1,})(?=.*[a-z]{1,}).{8,100}$ - RockOnGom
显示剩余14条评论

38

你还应考虑更改一些规则,例如:

  1. 增加更多特殊字符,如%,^,(),-,_,+和句点。我会把你错过的所有特殊字符都加上,这些特殊字符需要在正则表达式中转义。
  2. 密码的长度应该至少为8个字符,而不仅仅是一个固定的数字8。

通过以上改进,为了更加灵活和易读,我会修改正则表达式为:

^(?=(.*[a-z]){3,})(?=(.*[A-Z]){2,})(?=(.*[0-9]){2,})(?=(.*[!@#$%^&*()\-__+.]){1,}).{8,}$

基本说明

(?=(.*RULE){MIN_OCCURANCES,})     
每个规则块都由(?=(){})表示。可以轻松地单独指定和测试规则和出现次数,然后再进行组合。
^                               start anchor
(?=(.*[a-z]){3,})               lowercase letters. {3,} indicates that you want 3 of this group
(?=(.*[A-Z]){2,})               uppercase letters. {2,} indicates that you want 2 of this group
(?=(.*[0-9]){2,})               numbers. {2,} indicates that you want 2 of this group
(?=(.*[!@#$%^&*()\-__+.]){1,})  all the special characters in the [] fields. The ones used by regex are escaped by using the \ or the character itself. {1,} is redundant, but good practice, in case you change that to more than 1 in the future. Also keeps all the groups consistent
{8,}                            indicates that you want 8 or more
$                               end anchor

最后,为了测试目的,这里有一个与上述正则表达式相对应的robulink


1
谢谢@AFract。我在我的代码中使用它。我喜欢易读性和可重复性,以便将来需要更改时,比如密码策略的更改 :) - lsu_guy
1
非常棒的解释。在我看来,这应该是被接受的答案。 - Mohamed Wagih
@Isu_guy,如果您希望在超过最大出现次数时使其失败,那么该怎么办?只需要使用{min_occurances,max_occurances}吗? - ennth
正确的格式应该是{min_occurances,max_occurances}。请注意,我还没有测试过,请先测试一下。我也没有见过指定最大值的密码策略。您能分享一下需要最大值的用例吗?谢谢。 - lsu_guy
1
@RichardTylerMiles,没错。你的字符串中没有2个大写字母。如果再加一个大写字母,正则表达式就会接受它。 - lsu_guy
显示剩余7条评论

13

上面给出的答案很完美,但我建议使用多个较小的正则表达式,而不是一个大的。


将长的正则表达式拆分有一些优点:

  • 编写和阅读更容易
  • 更容易调试
  • 更容易添加/删除正则表达式的部分

通常,这种方法使代码更易于维护。

话虽如此,我在这里分享一段我用Swift编写的示例代码:

struct RegExp {

    /**
     Check password complexity

     - parameter password:         password to test
     - parameter length:           password min length
     - parameter patternsToEscape: patterns that password must not contains
     - parameter caseSensitivty:   specify if password must conforms case sensitivity or not
     - parameter numericDigits:    specify if password must conforms contains numeric digits or not

     - returns: boolean that describes if password is valid or not
     */
    static func checkPasswordComplexity(password password: String, length: Int, patternsToEscape: [String], caseSensitivty: Bool, numericDigits: Bool) -> Bool {
        if (password.length < length) {
            return false
        }
        if caseSensitivty {
            let hasUpperCase = RegExp.matchesForRegexInText("[A-Z]", text: password).count > 0
            if !hasUpperCase {
                return false
            }
            let hasLowerCase = RegExp.matchesForRegexInText("[a-z]", text: password).count > 0
            if !hasLowerCase {
                return false
            }
        }
        if numericDigits {
            let hasNumbers = RegExp.matchesForRegexInText("\\d", text: password).count > 0
            if !hasNumbers {
                return false
            }
        }
        if patternsToEscape.count > 0 {
            let passwordLowerCase = password.lowercaseString
            for pattern in patternsToEscape {
                let hasMatchesWithPattern = RegExp.matchesForRegexInText(pattern, text: passwordLowerCase).count > 0
                if hasMatchesWithPattern {
                    return false
                }
            }
        }
        return true
    }

    static func matchesForRegexInText(regex: String, text: String) -> [String] {
        do {
            let regex = try NSRegularExpression(pattern: regex, options: [])
            let nsString = text as NSString
            let results = regex.matchesInString(text,
                options: [], range: NSMakeRange(0, nsString.length))
            return results.map { nsString.substringWithRange($0.range)}
        } catch let error as NSError {
            print("invalid regex: \(error.localizedDescription)")
            return []
        }
    }
}

此外,当使用像上面那样的复杂正则表达式时,很容易陷入灾难性回溯的情况(https://www.regular-expressions.info/catastrophic.html)。这可能不会被注意到,直到有一天您的服务器因为用户使用了“奇怪”的密码而挂起并占用100%的CPU。例如:^([a-z0-9]+){8,}$(您能看到错误吗?) - aKzenT

11

你可以使用零长度正向先行断言来单独指定每个约束条件:

(?=.{8,})(?=.*\p{Lu}.*\p{Lu})(?=.*[!@#$&*])(?=.*[0-9])(?=.*\p{Ll}.*\p{Ll})

如果您的正则表达式引擎不支持\p符号并且仅ASCII足够,那么您可以将\p{Lu}替换为[A-Z],将\p{Ll}替换为[a-z]


11

以上所有的正则表达式(regex)对我都不起作用。

一个强密码应该遵守以下基本规则:

  • 至少包含一个大写字母
  • 至少包含一个小写字母
  • 至少包含一个数字
  • 至少包含一个特殊字符
  • 并且有最小长度要求

所以,最佳的正则表达式为:

^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*]).{8,}$

上面的正则表达式最小长度为8。您可以将它从{8,}更改为{任何数字,}。
规则修改?
假设您想要至少x个字符的小写字母,y个字符的大写字母,z个数字字符,总最小长度w。那么请尝试下面的正则表达式。
^(?=.*[a-z]{x,})(?=.*[A-Z]{y,})(?=.*[0-9]{z,})(?=.*[!@#\$%\^&\*]).{w,}$

注意: 在正则表达式中更改 xyzw

编辑:更新了正则表达式答案

编辑2:添加了修改


你的正则表达式匹配了12345678,你确定它是一个强密码吗?请在发布前尝试你的正则表达式。 - Toto
这样更好,但并没有回答问题,他们想要1)8个字符长度。2)2个大写字母。3)1个特殊字符(!@#$&*)。4)2个数字(0-9)。5)3个小写字母。 - Toto
@Toto,你现在可以分享一下你的想法吗? - Juned Khatri
你的正则表达式没有考虑到两个必须的大写字母可能会被其他字符分隔开,同样的情况也适用于小写字母和数字。有效答案是已被接受的答案。 - Toto
这是答案。 - collinsmarra
1
这个正则表达式对我来说有效。谢谢。 - joseph chikeme

5
我建议添加
(?!.*pass|.*word|.*1234|.*qwer|.*asdf) exclude common passwords

2
import re

RegexLength=re.compile(r'^\S{8,}$')
RegexDigit=re.compile(r'\d')
RegexLower=re.compile(r'[a-z]')
RegexUpper=re.compile(r'[A-Z]')


def IsStrongPW(password):
    if RegexLength.search(password) == None or RegexDigit.search(password) == None or RegexUpper.search(password) == None or RegexLower.search(password) == None:
        return False
    else:
        return True

while True:
    userpw=input("please input your passord to check: \n")
    if userpw == "exit":
        break
    else:
        print(IsStrongPW(userpw))

我认为这是正确的方式。 - Mahdi mehrabi

2

codaddict的解决方案可以正常工作,但这个方案更加高效:(Python语法)

password = re.compile(r"""(?#!py password Rev:20160831_2100)
    # Validate password: 2 upper, 1 special, 2 digit, 1 lower, 8 chars.
    ^                        # Anchor to start of string.
    (?=(?:[^A-Z]*[A-Z]){2})  # At least two uppercase.
    (?=[^!@#$&*]*[!@#$&*])   # At least one "special".
    (?=(?:[^0-9]*[0-9]){2})  # At least two digit.
    .{8,}                    # Password length is 8 or more.
    $                        # Anchor to end of string.
    """, re.VERBOSE)

否定字符类可以一步到位地消耗掉所需字符之前的所有内容,无需回溯。(点星解决方案也能正常工作,但需要一些回溯。)当然,在长度较短的目标字符串(如密码)中,这种效率提升将是微不足道的。

请问您能否检查一下这段代码是否正确?我有些疑惑,因为第一行三个双引号和问号之间有一个左圆括号。我可以看到 Python 的注释符号(#)在后面。但是我没有看到与结束锚点($)对应的右圆括号。需要说明的是,我不是正则表达式专家。 - lospejos
@lospejos - # 不是一条普通的单行注释开头。 这个井号是 注释组 的一部分,它以 (?# 开始并以 ) 结束。 在这个正则表达式中没有不匹配的括号。 - ridgerunner

1

codaddict给出了一个很好的答案:

^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=.*[a-z].*[a-z].*[a-z]).{8}$

但是如果你想要有8个小写字母而不是3个,可以这样写:

(?=.*[a-z].*[a-z].*[a-z].*[a-z].*[a-z].*[a-z].*[a-z].*[a-z])

这样写起来真的很麻烦,所以你可以这样写

(?=(?:.*[a-z]){8})

?: 用于排除捕获组

因此,我认为更好的答案应该是:

^(?=.*[A-Z].*[A-Z])(?=.*[!@#$&*])(?=.*[0-9].*[0-9])(?=(?:.*[a-z]){3}).{8}$

rubular链接

请注意,(?=.*[a-z]{8})的工作方式不同,因为它是一个用于匹配8个连续小写字母的正则表达式。


1
与最多票答案相同的表达,但更简短易写易懂。
^(?=[A-Z]{2})(?=.*[!@#$&*])(?=.*[0-9]{2})(?=.*[a-z]{3}).{8}$

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接