JetBrains

在 PyCharm Community 中创建一个密码短语生成器 | PyCharm 教程

Image

在本教程中,您将在 PyCharm 中创建一个密码短语生成器。您可通过这个教程收获:

  • 在 PyCharm Community Edition 中创建项目。

  • 安装并导入 Python 软件包。

  • 使用 Typer 库在 Python 中创建命令行接口。

  • 在 PyCharm 中运行和调试代码。

  • 创建和编辑运行配置。

本教程的目的是,展示如何使用免费的 PyCharm Community Edition 开发简单的 CLI 应用程序来自动执行日常任务。虽然在本教程结束时您将获得一个可用的密码短语生成器,但请仅将其视为一个学习项目。切勿使用此生成器生成的密码短语保护任何真实数据。

克隆仓库即可获取完整的代码。要了解有关克隆的信息,请参阅 PyCharm 文档。

关于密码短语

什么是密码短语?

我们每天都会使用很多密码。注册使用服务或网站时,您都需要创建一个长而独一无二的密码,包含数字、特殊字符、大写字母等。

这些要求都是为了使密码能够抵抗暴力攻击。暴力攻击基本上是多次尝试猜测密码,直到最终猜对为止。需要多少尝试和多长时间取决于密码的长度和复杂度。

密码短语(passphrase)是由多个随机单词组成的密码。它不需要有意义,也不需要符合语法规则。密码短语通常包含 4 到 5 个单词,越多越好。例如,PhysicianBuiltHotPotatoRegularly就是一个密码短语。

为什么密码短语更好?

A^1rL#2k2oPiA9H是一个安全系数高的好密码。它包含大小写字母、数字、特殊符号,长度为 15 个字符。但是您更愿意记住哪个,A^1rL#2k2oPiA9H 还是PhysicianBuiltHotPotatoRegularly?顺便说一下,后者有 32 个字符。

除了记住密码的难易程度之外,我们还应该注意破解密码的难易程度。来看下表:

Image

两者都很强,但密码短语安全系数更高且更好记。另外,如果在密码短语中添加一些数字和特殊字符,这将使所需的平均猜测次数增加到 2210 次 —— 可以说是几乎不可能破解!

综上:

  • 由随机单词组成的密码短语比由随机字符组成的密码更好记。

  • 密码短语通常比密码更长,因此更能抵抗暴力攻击,更安全。

  • 密码短语可以根据复杂度要求进行修改。例如,您可以将单词大写来包含大写字母,或者在单词之间添加特殊字符和数字作为分隔符。

什么是密码短语生成器

通常,密码短语生成器是一种将随机单词组合成伪句来生成密码的程序。在本教程中,我们将使用 PyCharm 和 Typer 开发一个命令行工具,它将执行以下操作:

  • 生成由 4-5 个随机单词组成的密码短语。

  • 按照选项将密码短语中的单词大写。单词默认不大写。

  • 使用任意符号作为单词的分隔符。默认不含分隔符。

  • 添加第五个单词,创建更长的密码短语。默认长度为四个单词。

工具不会存储您的密码短语。

前提

第一步

使用 Typer 编写“Hello World”

首次启动 PyCharm 时,您会看到欢迎屏幕。点击 New Project(新建项目):

Image

如果 PyCharm 已经在运行,可以从主菜单中选择 File | New Project(文件 | 新建项目)。

在 New Project(新建项目)窗口打开后,找到顶部的 Location(位置)字段,使用它指定项目的目录。这也将用作项目名称。

Image

您可以选择 PyCharm 将在其中安装项目依赖项的虚拟环境类型。您还可以选择创建环境的位置,以及基础 Python 解释器。

选择首选环境类型并指定选项(或保留默认值),然后点击 Create(创建)。

PyCharm 将创建包含虚拟环境的项目目录(在我们的示例中为 venv)。如果您在上一步中没有清除 Create a main.py welcome script(创建 main.py 欢迎脚本)复选框,它也会创建 main.py 并在编辑器中将其打开:

Image

文件包含带有一些基本指令的“Hello World”脚本。将以下代码复制到剪贴板:

def main():
    print("Hello World")

if __name__ == "__main__":
    typer.run(main)

转到 PyCharm,按 ⌘A / Ctrl+A,然后按 ⌘V / Ctrl+V 替换 main.py 的内容。您将得到:

Image

您可以看到typer下方有一条红色波浪线。这表示 Python 解释器无法识别 Type。我们需要安装此软件包并将其导入 main.py 才能启动脚本。

将鼠标指针悬停在高亮显示的符号上,然后在弹出窗口中选择 Install and import package ‘typer’(安装并导入软件包 ‘typer’):

Image

PyCharm 会将 Typer 软件包安装到项目环境中,并将其导入 main.py。

现在,我们可以运行脚本了。点击装订区域中的运行图标,选择 Run ‘main’(运行 ‘main’):

Image

带有“Hello World”的 Run(运行)工具窗口将在底部打开:

Image

生成第一个密码短语

修改代码,让它打印密码短语而不是“Hello World”。这里的想法是随机挑选单词并组成短语。因此,我们需要一个或多个可供选择的单词列表。您可以手动准备这样的列表,也可以使用大型语言模型生成。

创建单词列表时,请确保它们的安全性。如果有恶意行为者访问了单词列表,他们将能够在几秒钟内破解您的密码。

我们的仓库包含单词列表以及本教程的完整代码。您可以仅出于学习目的下载和使用,风险自负。强烈建议不要根据这些单词列表生成真正的密码短语。

在这一步中,您需要包含 4 个单词的列表:

  • obj_nouns.txt,包含将在我们生成的伪句中充当宾语的名词。

  • sub_nouns.txt,包含将充当主语的名词。

  • verbs.txt,包含动词。

  • adjectives.txt,包含形容词。

每个列表中的单词越多,脚本能够生成的组合就越多。每个单词都应另起一行。

将生成或下载的单词列表复制到项目目录。如果您想手动创建单词列表,可以在 PyCharm 中进行:

  • 在 Project(项目)工具窗口中点击项目目录,然后按 ⌘N / Ctrl+N。

  • 选择 File(文件),指定文件名,例如obj_nouns.txt。

  • PyCharm 将创建文件并在编辑器中将其打开。

项目结构应该如下所示:

Image

首先,我们需要从文本文件读取单词。将print("Hello World")替换为以下代码:

sub_nouns = read_words('sub_nouns.txt')

同样,read_words 下方有一条红色波浪线。我们需要创建这个函数。将鼠标悬停在 read_words 上,然后在弹出窗口中点击 Create function ‘read_words’(创建函数 'read_words'):

Image

PyCharm 将创建一个函数存根。指定file_name为函数形参,然后按 Tab 开始编写函数代码:

Image

您可以将高亮显示的代码复制到函数体中:

def read_words(file_name):
    with open(file_name, 'r') as f:
        words = f.readlines()
    return words

该函数将打开名称在第一个形参中提供的文件。然后,它会应用 readlines() 方法,这将返回包含文件行作为其元素的 Python 列表。列表被保存到 words 变量中并由函数返回。

我们回到 main() 函数,使用新创建的 read_words 函数读取另外 3 个单词列表:

def main():
    sub_nouns = read_words('sub_nouns.txt')
    verbs = read_words('verbs.txt')
    adjectives = read_words('adjectives.txt')
    obj_nouns = read_words('obj_nouns.txt')

接下来,创建一个单词列表的列表,将其命名为 word_bank。稍后,我们将在为密码短语选择随机单词时遍历它:

    word_bank = [sub_nouns, verbs, adjectives, obj_nouns]

选择的随机单词将被保存到另一个列表中。把它命名为 phrase_words 并进行初始化:

    phrase_words = []

在接下来的 for 循环中,我们遍历 word_bank 的条目。word_bank 中的每个条目都是一个包含单词的列表。我们从内置 random 模块中调用 SystemRandom() 类的 choice() 方法,从列表中选择一个随机单词。然后,将所选单词追加到 phrase_words:

    for word_list in word_bank:
        random_word = random.SystemRandom().choice(word_list)
        phrase_words.append(random_word)

虽然 random 是内置模块,但我们还是需要导入它。像之前一样,您可以通过编辑器中的红色波浪线来判断。将鼠标悬停在它上面,选择 Import this name(导入此名称)。

最后,使用 join 将包含随机选择单词的列表转换成短语并打印结果:

    passphrase = ''.join(phrase_words)
    print(passphrase)

main() 在这个阶段应该是这样的:

def main():
    sub_nouns = read_words('sub_nouns.txt')
    verbs = read_words('verbs.txt')
    adjectives = read_words('adjectives.txt')
    obj_nouns = read_words('obj_nouns.txt')
    word_bank = [sub_nouns, verbs, adjectives, obj_nouns]
    phrase_words = []
    for word_list in word_bank:
        random_word = random.SystemRandom().choice(word_list)
        phrase_words.append(random_word)
    passphrase = ''.join(phrase_words)
    print(passphrase)

现在,运行脚本来检查它是否正常运作。点击装订区域中的 Run(运行)图标,选择 Run ‘main’(运行 ‘main’),您应该得到:

Image

现在已经有了 4 个单词,但它绝对不是短语。当代码正常工作但产生意外结果时,就需要进行调试。

从当前输出可以看到,脚本成功从单词列表选择了随机单词。它没有做到的是将单词组合成短语。main()的倒数第二行似乎是问题根源:

def main():
    ...
    passphrase = ''.join(phrase_words)
    print(passphrase)

为了查看特定代码行生成的结果,我们应该在该行上放置一个断点。然后,调试器将在执行带有断点的行之前停止。要设置断点,请点击我们要检查的行旁边的装订区域:

Image

要开始调试,像之前一样点击装订区域中的 Run(运行)图标,但这次,在弹出窗口中选择 Debug ‘main’(调试 ‘main’)。调试器将在断点处启动并停止执行,在底部打开 Debug(调试)工具窗口:

Image

在 Debug(调试)工具窗口的右侧窗格中,您可以看到目前为止已赋值的变量。展开 phrase_words 来查看其中的内容:

Image

列表中有 4 个类型为 str 的条目。每个字符串都以新行 ('n') 结尾。因此,后续将这些字符串连接在一起打印时,每个单词都打印在单独的行中。

如果查看其他列表,例如 adjectives,可以发现其中的所有条目也都以 'n' 结尾。我们从 read_words 函数获取这些列表。这意味着我们需要修正它,让它返回没有尾随 'n' 的单词列表。

让我们使用 strip() 和列表推导式,在返回之前去除每个列表条目中的 'n':

def read_words(file_name):
    with open(file_name, 'r') as f:
        words = f.readlines()
        words = [word.strip() for word in words]
    return words

重新运行main(),获得结果:

Image

创建更好的密码短语

让它们更好记

显然,上一步生成的密码短语有些难读。将每个单词大写来提高可读性呢?

在这个应用程序中使用 Typer,因为我们不想只运行脚本和获取密码短语。我们需要创建一个命令行接口,用来将各种选项传递给脚本,控制生成的密码短语的属性。一种选项是将单词大写。

运行 main.py 时,将执行以下行:

...
if __name__ == "__main__":
    typer.run(main)

所以,我们将使用 typer 执行 main 函数。这是因为 Typer 可以接受命令行实参,然后将它们作为形参传递给函数。

我们在 main() 函数中引入 capitalize 形参,并默认将其设为 False:

def main(capitalize = False):
    sub_nouns = read_words('sub_nouns.txt')
    ...
    passphrase = ''.join(phrase_words)
    print(passphrase)

根据 Python 编码最佳做法,我们应该指定形参类型。将文本光标置于 capitalize 上,按 ⌥Enter/ Alt+Enter。然后,选择 Specify type for the reference using annotation(使用注解指定引用类型)。输入bool,因为 capitalize 应该有一个布尔值。

现在,如果 capitalize 为 True,我们需要在连接单词之前将它们大写。使用 if capitalize: 开始一条 if 语句。让我们使用类似于修正 read_words 函数时使用的方法,不过,这次我们将使用实时模板而不是手动编写列表推导式。输入 compl 并按 Enter。然后,指定列表推导式的所有元素,按 Tab 移动到下一个元素。

应该得到:

def main(capitalize: bool = False):
    sub_nouns = read_words('sub_nouns.txt')
    verbs = read_words('verbs.txt')
    adjectives = read_words('adjectives.txt')
    obj_nouns = read_words('obj_nouns.txt')
    word_bank = [sub_nouns, verbs, adjectives, obj_nouns]
    phrase_words = []
    for word_list in word_bank:
        random_word = random.SystemRandom().choice(word_list)
        phrase_words.append(random_word)
    if capitalize:
    phrase_words = [phrase_word.capitalize() for phrase_word in phrase_words]
    passphrase = ''.join(phrase_words)
    print(passphrase)

为确保大写正确工作,我们需要在运行 main.py 时将capitalize作为实参传递。为此,我们需要编辑运行配置。在 IDE 窗口顶部找到 Run(运行)微件:

Image

使用这个微件可以选择所需的运行配置,以及在运行或调试模式下启动它。在我们点击装订区域图标并启动脚本时,PyCharm 已经创建了 main 配置。点击配置名称,打开菜单,然后选择 Edit configurations(编辑配置):

Image

在打开的对话框中,在 Parameters(形参)字段中指定 --capitalize:

Image

点击 OK(确定)保存更新后的配置。然后,点击微件中的运行图标。

结果如下:

Image

为了提升可读性,我们可以将密码短语中的单词分开。使用特殊字符作为分隔符还将起到另一重作用,生成符合特定密码复杂度要求的密码短语。

编辑main()函数的倒数第二行,如下所示:

def main(capitalize: bool = False):
    ...
    passphrase = separator.join(phrase_words)

PyCharm 使用红色波浪线高亮显示separator,因为函数还没有这个形参。将鼠标悬停在它上面,在弹出窗口中选择 Create parameter ‘separator’(创建形参 ‘separator’)。然后,将 '' 指定为其默认值,因为默认情况下我们不想添加分隔符。

也将str指定为形参类型。应该得到:

def main(capitalize: bool = False, separator: str = ''):
    ...
    passphrase = separator.join(phrase_words)
    print(passphrase)

现在,您肯定想查看结果了。不要忘记先更新运行配置。点击 Run(运行)微件,选择 Edit configurations(编辑配置),然后在 Parameters(形参)字段中添加新形参:

Image

运行配置以查看结果:

Image

现在,您可以用特殊字符和数字分隔密码短语中的单词。还可以使用多个符号来满足网站的密码要求,例如“#4”或“%7”。

让它们更难破解

密码短语越长,暴力攻击需要尝试的次数就越多。让我们向密码短语再添加一个单词。

首先,准备包含副词的第五个单词列表,并将其放入项目目录。将布尔类型的 long 形参添加到 main() 函数的签名中。根据这个形参(非必选,默认设置为 False),我们将再向 word_bank 添加一个单词列表:

def main(capitalize: bool = False, separator: str = '', long: bool = False):
    ...
    word_bank = [sub_nouns, verbs, adjectives, obj_nouns]
    if long:
        adverbs = read_words('adverbs.txt')
        word_bank.append(adverbs)

    ...

这一次,使用内置终端运行脚本。按 ⌥F12/ Alt+F12,在打开的 Terminal(终端)工具窗口中输入以下命令:

python main.py --capitalize --separator "1_" --long

结果应该类似于:

Image

准备工具以供使用

定义短选项名称

如果您使用过 CLI 工具,您应该知道它们通常只允许用户以一个字母指定实参。我们也将这个功能添加到工具中。为了提升代码可读性,让我们重新格式化函数签名,让每个形参都在单独的行中:

def main(
    capitalize: bool = False,
    separator: str = '',
    long: bool = False
)
:

...

然后,将各个形参的默认值替换为typer.Option(, , ):

这是main()的最终签名:

def main(
    capitalize: bool = typer.Option(False, '--caps', '-c'),
    separator: str = typer.Option('', '--separator', '-s'),
    long: bool = typer.Option(False, '--long', '-l')
)
:

...

接下来,我们可以一起指定所有选项。分隔符 (‘-s’) 应该放在最后,因为它后面需要一个字符串:

Image

记录选项

默认情况下,Typer 还会添加 --help 选项。来看它现在是如何工作的:

Image

我们可以了解存在哪些形参以及它们的长短名称。再添加一些注释来解释它们的实际作用怎么样?为 main() 的每个形参添加 help,如下所示:

def main(
    capitalize: bool = typer.Option(False, '--caps', '-c', help='Capitalize each word.'),
    separator: str = typer.Option('', '--separator', '-s', help='Separate words with the given symbol.'),
    long: bool = typer.Option(False, '--long', '-l', help='Make the passphrase longer by including an adverb.')
)
:

...

--help现在生成了更多实用信息:

Image

您可能会想在不需要 PyCharm 的情况下使用密码短语生成器,例如在系统终端中。为此,应该使用以下命令将 Typer 安装到系统解释器:

python3 -m pip install --user typer

总结

在本教程中,您学习了如何:

  • 在 PyCharm Community Edition 中创建项目。

  • 使用 Python 和 Typer 开发人性化 CLI 工具。

  • 使用快速修复和实时模板更快编写代码并避免错误。

  • 调试代码。

  • 使用运行配置和终端在 PyCharm 中运行代码。

本博文英文原作者:

Denis Mashutin

Image

PyCharm 相关阅读

Image
Image
Image
Image

关于 PyCharm

PyCharm 是一款可以帮助专业 Python 开发者提升效率、信心和代码质量的集成开发环境 (IDE)。PyCharm Pro 原生支持整个 Python 工作流,包括 Web 框架、前端技术、数据库和科学工具。

PyCharm Community Edition 是一项免费的开源项目,也可用于一般的 Python 编程任务。

进一步了解 PyCharm

Image

⏬ 戳「阅读原文」了解更多