使用Python 2.7.12,我编写了一个简单的小脚本psk_validate.py,提示用户输入潜在的密码,并检查其是否包含大小写字符,数字,特殊字符,并且长度至少为8个字符。

据我所知,可以使用regex库来更有效地编写此代码,但是,我还没有学习到regex。

该程序似乎可以正常工作很好,对于这么小的程序,我认为不使用正则表达式也很好。

我想要有关此程序的所有反馈。特别是,我想知道编写而成的程序是否可以在实际应用中使用。我还想知道程序中是否存在任何逻辑错误和/或错误。

from sys import exit

def check_upper(input):
    uppers = 0 
    upper_list = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split()
    for char in input:
        if char in upper_list:
            uppers += 1
    if uppers > 0:
        return True
    else:
        return False

def check_lower(input):
    lowers = 0
    lower_list = "a b c d e f g h i j k l m n o p q r s t u v w x y z".split()
    for char in input:
        if char in lower_list:
            lowers += 1
    if lowers > 0:
        return True
    else:
        return False

def check_number(input):
    numbers = 0
    number_list = "1 2 3 4 5 6 7 8 9 0".split()
    for char in input:
        if char in number_list:
            numbers += 1
    if numbers > 0:
        return True
    else:
        return False

def check_special(input):
    specials = 0
    special_list = "! @ $ % ^ & * ( ) _ - + = { } [ ] | \ , . > < / ? ~ ` \" ' : ;".split()
    for char in input:
        if char in special_list:
            specials += 1
    if specials > 0:
        return True
    else:
        return False

def check_len(input):
    if len(input) >= 8:
        return True
    else:
        return False


def validate_password(input):
    check_dict = {
        'upper': check_upper(input),
        'lower': check_lower(input),
        'number': check_number(input),
        'special': check_special(input),
        'len' : check_len(input)
    }
    if check_upper(input) & check_lower(input) & check_number(input) & check_special(input) & check_len(input):
        return True
    else:
        print "Invalid password! Review below and change your password accordingly!"
        print
        if check_dict['upper'] == False:
            print "Password needs at least one upper-case character."
        if check_dict['lower'] == False:
            print "Password needs at least one lower-case character."
        if check_dict['number'] == False:
            print "Password needs at least one number."
        if check_dict['special'] == False:
            print "Password needs at least one special character."
        if check_dict['len'] == False:
            print "Password needs to be at least 8 characters in length." 
        print                  

while True:
    password = raw_input("Enter desired password: ")
    print 
    if validate_password(password):
        print "Password meets all requirements and may be used."
        print 
        print "Exiting program..."
        print
        exit(0)


评论

相关:blogs.dropbox.com/tech/2012/04/…

#1 楼

概念

强制性XKCD漫画,在我开始之前:



不再要求通过使用人类不友好的字符来增强密码强度。不过,我将按照编写代码的方式进行检查。

“显而易见”的简化方式


任何带有if bool_expr: return True; else: return False模式的代码都应简单地写为return bool_expr

字符串可以直接迭代;无需先使用.split()将它们转换为列表。换句话说,如果您只写以下代码,则代码将起作用:

upper_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"


更好的是,您可以只使用string.ascii_uppercase
uppers += 1计数循环可以使用sum()内置函数可以更富表现力。实际上,在这种情况下,由于您只关心uppers > 0,因此可以只使用any()函数。

经过这些更改,您的check_upper()函数变成了单行代码:

def contains_upper(s):
    return any(c in ascii_uppercase for c in s)


我已将check_upper()重命名为contains_upper(),以明确表明该函数返回TrueFalse。另外,请避免使用与内置函数名称一致的变量名,例如input:如果您要使用input(),可能会引起麻烦。

代码复制

您的大多数check_something()功能是相同的。您应该概括而不是重复代码。

from string import ascii_uppercase, ascii_lowercase, digits

def contains(required_chars, s):
    return any(c in required_chars for c in s)

def contains_upper(s):
    return contains(ascii_uppercase, s)

def contains_lower(s):
    return contains(ascii_lowercase, s)

def contains_digit(s):
    return contains(digits, s)

def contains_special(s):
    return contains(r"""!@$%^&*()_-+={}[]|\,.></?~`"':;""", s)

def long_enough(s):
    return len(s) >= 8


请注意,我使用了原始的长字符串来帮助处理标点符号字符串中的反斜杠。

validate_password()

check_dict对您没有任何帮助。拥有五个布尔变量,您将再好不过了。您还将两次调用每个验证函数。

&(二进制按位与)运算符在这里不太合适。 and(布尔AND)运算符会更合适。即使结果看起来相同,执行方式也不同:逻辑and允许进行短路评估。

我个人就是这样写的,收集了所有失败消息的列表:

def validate_password(password):
    VALIDATIONS = (
        (contains_upper, 'Password needs at least one upper-case character.'),
        (contains_lower, 'Password needs at least one lower-case character.'),
        (contains_digit, 'Password needs at least one number.'),
        (contains_special, 'Password needs at least one special character.'),
        (long_enough, 'Password needs to be at least 8 characters in length.'),
    )
    failures = [
        msg for validator, msg in VALIDATIONS if not validator(password)
    ]
    if not failures:
        return True
    else:
        print("Invalid password! Review below and change your password accordingly!\n")
        for msg in failures:
            print(msg)
        print('')
        return False


如果函数返回将True放在一个地方,那么为了保持一致性,最好在另一分支中返回False而不是None

自由浮动代码

通常将if __name__ == '__main__':围绕模块中不在函数内部的语句。这样,您可以通过执行import psk_validate来将功能合并到另一个程序中,而无需实际运行该程序。

如果正确地构造代码,很少或不需要调用sys.exit(0)。在这里,您只需要一个break

if __name__ == '__main__':
    while True:
        password = raw_input("Enter desired password: ")
        print()
        if validate_password(password):
            print("Password meets all requirements and may be used.\n")
            print("Exiting program...\n")
            break


评论


\ $ \ begingroup \ $
我认为也必须链接相关的Security.SE问题。 ;)+1
\ $ \ endgroup \ $
– jpmc26
17年6月8日,0:54

\ $ \ begingroup \ $
作为一名Python爱好者(不是专业人士),我很难阅读验证程序的味精,VALIDATIONS ...行中的味精。正如我头脑中解析为两个元素的列表一样,其中一个是用于验证程序的msg,另一个是在VALIDATIONS中的msg ...(这没有任何意义)。仅在验证器msg上加上括号,使(validator,msg)的行msg在VALIDATIONS中更容易阅读吗?
\ $ \ endgroup \ $
–奥利维尔·格雷戈尔(OlivierGrégoire)
17年6月8日在9:59

\ $ \ begingroup \ $
这个XKCD密码生成挑战也很重要:codegolf.stackexchange.com/questions/122756/…
\ $ \ endgroup \ $
–诺亚·克里斯蒂诺(Noah Cristino)
17年6月8日在11:46

\ $ \ begingroup \ $
@OlivierGrégoire我个人并没有在...中遇到(a,b),但是如果我确实遇到过,那么我会“很奇怪”,这会打破我的思路。
\ $ \ endgroup \ $
– Peilonrayz
17年6月8日在14:02

\ $ \ begingroup \ $
@OlivierGrégoire这是打开包装的一个例子。与x,y = [1,2]相同。循环版本在dict迭代中也很常见:对于d.items()中的k,v。该赋值通常不包含在括号中。您可能也会被列表理解这一事实所吸引。如果您在阅读它时遇到麻烦,我实际上建议在msg之后使用换行符,以将元素评估与循环迭代分开。现在,您已经知道了模式,您将开始更轻松地识别它。
\ $ \ endgroup \ $
– jpmc26
17年6月8日19:52



#2 楼



您可以使check_uppercheck_lower等全部使用一个函数,因此您想要创建诸如check_contains(input, letters)之类的函数。
可以通过以下方法进一步加以改进:


如果return True为true,请使用char in letters早返回。
您可以理解它。
使用(2)时,可以使用any达到与(1)相同的效果。 br />
所以我要使用:

def check_contains(input, letters):
    return any(char in letters for char in input)


我个人希望validate_password(input)只返回true或false,但是要与您的内容保持一致这样做,我会保留它以便打印。
删除sys.exit,它不打算在程序中使用。而是使用break来打破while循环。
我将使用getpass而不是raw_input来获取用户密码。这是因为它应该关闭echo,因此不会显示用户密码,因此其他人可以自己承担密码。
您可以使用strings而不是手动写出字符串,

,因此我将您的代码更改为:

from getpass import getpass
import string

def check_contains(input, letters):
    return any(char in letters for char in input)

def validate_password(input):
    valid = True
    if not check_contains(input, string.ascii_uppercase):
        valid = False
        print "Password needs at least one upper-case character."
    if not check_contains(input, string.ascii_lowercase):
        valid = False
        print "Password needs at least one lower-case character."
    if not check_contains(input, string.digits):
        valid = False
        print "Password needs at least one number."
    if not check_contains(input, string.punctuation + '#'):
        valid = False
        print "Password needs at least one special character."
    if len(input) < 8:
        valid = False
        print "Password needs to be at least 8 characters in length."
    return valid

while True:
    password = getpass("Enter desired password: ")
    if validate_password(password):
        print "Valid password"
        break


如果要使程序遵循SRP,则不应打印validate_password。因此,您可能需要使用以下内容。如果打印消息对您来说非常重要,那么那应该是验证密码是否正确的另一项功能。

from getpass import getpass
import string

def check_contains(input, letters):
    return any(char in letters for char in input)

def validate_password(input):
    return all([
        check_contains(input, string.ascii_uppercase),
        check_contains(input, string.ascii_lowercase),
        check_contains(input, string.digits),
        check_contains(input, string.punctuation + '#'),
        len(input) >= 8
    ])

while True:
    password = getpass("Enter desired password: ")
    if validate_password(password):
        print "Valid password"
        break
    else:
        print "invalid password"


评论


\ $ \ begingroup \ $
如文档中所述,您可以使用string.ascii_lowercase,string.ascii_uppercase替换硬编码字母,并使用string.digits替换数字。
\ $ \ endgroup \ $
– grundic
17年7月7日在18:49

\ $ \ begingroup \ $
@grundic我进行了编辑以添加该内容,我不想记住它是“小写”而不是“小写”,因此我通常跳过使用它; P
\ $ \ endgroup \ $
– Peilonrayz
17年7月7日在18:53

\ $ \ begingroup \ $ 当然;)并且不要忘记导入字符串:p
\ $ \ endgroup \ $
– grundig
17年6月7日在19:02

\ $ \ begingroup \ $
@grundic谢谢,我合理地忘记添加它们,);
\ $ \ endgroup \ $
– Peilonrayz
17年7月7日在19:04

\ $ \ begingroup \ $
如果需要验证许多密码,则可能要为字符集定义全局常量集。
\ $ \ endgroup \ $
–地狱
17年6月8日在6:20