恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
用Python secrets实现本地随机密码生成器
首页
资讯中心
/
用Python secrets实现本地随机密码生成器
用Python secrets实现本地随机密码生成器
发布时间:2026/10/8 3:31:09
前阵子有个朋友跑来跟我吐槽某平台提示他的密码有泄露风险他一脸无辜地说“我所有账号都用同一个密码啊”。我当时确实很想叹气。其实我自己从上学那会儿起就一直用Python写随机密码生成小程序几十行代码放在本地跑想生成多少位就多少位想排除哪些字符就排除哪些字符。这里说的“小程序”不是微信小程序就是一个用Python写的小工具脚本。这篇内容会把这个项目从需求拆解、思路设计、代码实现到调试踩坑完整过一遍适合刚学Python的新手拿来练手也适合不想再被烂密码坑的普通用户直接抄作业。1. 为什么随机密码这事值得自己动手1.1 在线密码生成器看着方便隐患其实在后头很多人图省事直接在搜索引擎里找“随机密码生成器”网页点一下按钮拿到一个16位字符串。这类工具确实方便但你想过没有当你把生成的密码交给一个陌生网站时等于多了一个第三方知道你新账号的密码。如果这个在线工具本身的数据库被拖走或者它背后有一套不透明的日志记录逻辑你精心生成的高强度密码等于白费。自己做离线脚本就不是这个逻辑。生成动作只发生在你自己的电脑内存里所有字符都从你机器本地的系统熵源中产生整个过程不经过任何外部服务器也没有网页脚本在后台收集输入。这个区别在安全意识比较强的人眼里属于本质差别不是多一事少一事的问题。1.2 通用工具给不了那些“奇怪但真实”的定制需求实际注册账号时你会遇到各种离谱要求有的网站不允许特殊符号有的用户名规则要求密码必须同时包含大小写和数字有的登录系统里字母“l”和数字“1”长得几乎一样还有的开机密码根本不显示字符全靠盲打。通用在线工具通常只给你一两个选项框做不到细粒度的过滤。而自己用Python写想要什么规则都能加进去排除易混淆字符、指定必须包含哪几类字符、限制长度范围、一次生成多个候选密码、甚至按“可朗读”的逻辑拼接成词组密码。这些需求一旦落地你会发现通用工具的灵活性和自己写脚本完全不是一个量级。1.3 Python做这种小工具成本低到可以忽略整个项目只用Python自带的标准库就够了不需要安装任何第三方依赖。string模块提供字符池secrets模块提供安全随机数argparse做命令行参数解析tkinter做图形界面一个文件搞定。脚本复制到Windows、macOS、Linux都能跑只要机器上有Python解释器就行。更关键的是这个小工具是很多程序员第一个“写了以后真的天天用”的脚本。它不复杂但会让你第一次感觉到代码不是交作业用的而是能解决真实生活问题的。2. 动手前的设计与思路拆解2.1 先搞清楚你生成的密码到底要应对什么场景写代码之前我习惯先把需求列成一张表因为不同场景对密码的约束差异很大。比如使用场景推荐长度特殊符号排除项其他说明网站账号16位以上允许无字符池尽量取最大服务器SSH/密钥24位以上允许引号、空格避免复制粘贴出错Windows登录密码14位以上允许易混淆字符盲打场景别用l和1临时访客WiFi密码10位左右一般不强制0、O、1、l要方便口头告诉别人这个表格不用写进代码但在设计命令行参数时需要先在心里过一遍。理解了使用场景你才知道为什么默认长度要设成16为什么“排除易混淆字符”和“排除引号”必须是可选项。2.2 随机性来源“看起来随机”和“真的安全”是两回事这是整个项目里最重要的一条原则。Python自带的random模块底层是梅森旋转算法生成的伪随机数它速度快、可复现适合做模拟、抽样、游戏这类场景但不适合用来生成密码。因为伪随机数生成器的状态如果被恢复后面生成的结果是可以被推算的。密码场景要动用的模块是secrets。secrets从操作系统内核获取熵源在Linux上是/dev/urandom在Windows上使用CryptGenRandom一类的加密安全接口生成的随机数不可预测、不可复现。secrets.choice()是专门为密码、令牌这类敏感信息设计的接口。整个脚本从第一行生成逻辑起就和random彻底划清界限。2.3 生成逻辑的核心字符池、长度、强制覆盖随机密码生成看起来就是“从字符池里随机取字符拼成一串”但有几个细节必须处理。第一步是确定字符池。Python的string模块给了现成的集合string.ascii_lowercase26个小写字母string.ascii_uppercase26个大写字母string.digits10个数字string.punctuation32个常见符号把这些拼在一起默认情况下字符池大小是94。理论上16位密码的熵可以达到约104位这是个相当可观的强度。第二步是确定长度。我的经验是普通网站账号至少16位服务器或主密码建议24位以上。密码长度每增加一位暴力破解的尝试量指数增长所以长度比“是否包含符号”更直接影响安全性。第三步是强制覆盖。如果只是从字符池里随机抽可能出现一个长达16位的密码里恰好没有数字或者没有大写字母。有些平台会直接拒绝这种密码。所以工具必须具备一个能力保证指定类型的字符至少出现一次。实现思路是循环生成生成后逐类检查不满足就重新生成。因为密码长度通常在10位以上检查失败的概率不高循环次数对性能几乎没有影响。2.4 命令行还是图形界面取决于谁会用它我第一版只写了命令行工具自己用非常顺手python genpass.py -l 16直接出一串密码。但后来发现身边有些人不会开终端所以我补了一个tkinter图形版。tkinter是Python自带的GUI库不需要额外安装跨平台表现稳定做一个“按钮输入框”的小窗口正好合适。命令行版适合你自己用图形版适合拿给家人用。两版共用同一套密码生成核心逻辑所以代码结构上我会把生成函数独立出来避免两端逻辑不一致。3. 核心代码实现与实操细节3.1 命令行版完整实现这里给出一个可直接保存运行的版本文件取名genpass.pyimport argparse import secrets import string def build_pool(use_lowerTrue, use_upperTrue, use_digitsTrue, use_symbolsTrue, excludeNone): 按需拼接字符池并过滤掉需要排除的字符。 pool if use_lower: pool string.ascii_lowercase if use_upper: pool string.ascii_uppercase if use_digits: pool string.digits if use_symbols: pool string.punctuation exclude exclude or pool .join(ch for ch in pool if ch not in exclude) return pool def has_required_types(password, use_lowerTrue, use_upperTrue, use_digitsTrue, use_symbolsTrue): 检查密码是否覆盖了要求的所有字符类型。 if use_lower and not any(ch.islower() for ch in password): return False if use_upper and not any(ch.isupper() for ch in password): return False if use_digits and not any(ch.isdigit() for ch in password): return False if use_symbols and not any(not ch.isalnum() for ch in password): return False return True def generate_password(length16, use_lowerTrue, use_upperTrue, use_digitsTrue, use_symbolsTrue, excludeNone): 循环生成密码直到满足类型覆盖要求。 pool build_pool(use_lower, use_upper, use_digits, use_symbols, exclude) if not pool: raise ValueError(字符池为空请检查参数) while True: password .join(secrets.choice(pool) for _ in range(length)) if has_required_types(password, use_lower, use_upper, use_digits, use_symbols): return password def main(): parser argparse.ArgumentParser(description本地随机密码生成器) parser.add_argument(-l, --length, typeint, default16, help密码长度默认16) parser.add_argument(--no-lower, actionstore_true, help不包含小写字母) parser.add_argument(--no-upper, actionstore_true, help不包含大写字母) parser.add_argument(--no-digits, actionstore_true, help不包含数字) parser.add_argument(--no-symbols, actionstore_true, help不包含特殊符号) parser.add_argument(-e, --exclude, default, help需要排除的字符例如 -e 0O1lI) parser.add_argument(-n, --count, typeint, default1, help一次生成几个密码默认1) args parser.parse_args() for _ in range(args.count): print(generate_password( lengthargs.length, use_lowernot args.no_lower, use_uppernot args.no_upper, use_digitsnot args.no_digits, use_symbolsnot args.no_symbols, excludeargs.exclude, )) if __name__ __main__: main()这段代码的关键点都在函数注释里但有两个地方我需要单独拎出来讲。3.2 强制类型覆盖的两种写法我最后选了循环验证关于“保证包含数字和大写字母”网上常见的做法是先分别从每个类型里随机抽一个字符再用剩余位数从全字符池里随机抽取最后把整个密码打乱顺序。这个思路没问题但有一个细节容易翻车很多人顺手用random.shuffle()去打乱这等于把安全性关键的最后一步交给了伪随机数生成器。secrets模块本身没有提供shuffle()方法所以更稳妥的做法就是上面代码里的循环验证每次生成的密码仍然完全由secrets.choice()从统一字符池中独立抽取分布均匀只是多了一次是否符合类型覆盖的检查。不符合就重新生成。以16位密码为例同时要求大小写数字符号全包含时一次通过的概率并不低实际跑下来几乎都是第一轮就出结果性能完全不是问题。有读者可能会想那不是浪费时间吗其实真正浪费时间的反而是“先分组再洗牌”方案里对洗牌随机性的纠结以及为了确保每类字符数量分布而做的复杂统计处理。代码里少一点不确定性比什么都值。3.3 tkinter图形版给不想碰命令行的人用图形版的定位是“足够简单”布局就三行字符池选项、长度滑条、生成按钮和结果展示。下面是一个可运行的GUI版本import tkinter as tk from tkinter import ttk from genpass import generate_password def make_password(): length int(length_var.get()) password generate_password( lengthlength, use_lowerlower_var.get(), use_upperupper_var.get(), use_digitsdigits_var.get(), use_symbolssymbols_var.get(), excludeexclude_entry.get(), ) result_var.set(password) root tk.Tk() root.title(随机密码生成器) root.geometry(420x240) lower_var tk.BooleanVar(valueTrue) upper_var tk.BooleanVar(valueTrue) digits_var tk.BooleanVar(valueTrue) symbols_var tk.BooleanVar(valueTrue) length_var tk.StringVar(value16) result_var tk.StringVar(value) ttk.Checkbutton(root, text小写字母, variablelower_var).grid(row0, column0, padx5, pady5) ttk.Checkbutton(root, text大写字母, variableupper_var).grid(row0, column1, padx5, pady5) ttk.Checkbutton(root, text数字, variabledigits_var).grid(row0, column2, padx5, pady5) ttk.Checkbutton(root, text特殊符号, variablesymbols_var).grid(row0, column3, padx5, pady5) ttk.Label(root, text长度).grid(row1, column0, padx5, pady5) ttk.Entry(root, textvariablelength_var, width8).grid(row1, column1, padx5, pady5) ttk.Label(root, text排除字符).grid(row1, column2, padx5, pady5) ttk.Entry(root, textvariableexclude_entry : tk.StringVar(), width10).grid(row1, column3, padx5, pady5) ttk.Button(root, text生成密码, commandmake_password).grid(row2, column0, columnspan2, padx5, pady10) ttk.Entry(root, textvariableresult_var, width30).grid(row2, column2, columnspan2, padx5, pady10) root.mainloop()这个版式的设计原则是让使用者一眼能看懂每个选项长度直接填数字排除字符留一个输入框。生成结果放在输入框里方便直接选中复制。注意我没有给GUI加“复制到剪贴板”按钮因为纯tkinter没有跨平台的剪贴板封装不想为此引入第三方依赖。如果你愿意装一个非常轻量的pyperclip库一句pyperclip.copy(result_var.get())就能解决不影响本文主体。3.4 参数细节排除字符和强度自检命令行里-e参数可以排掉你不想出现的字符。比如很多人不喜欢密码里出现引号因为个别网站提交时会被转义弄出各种奇怪问题可以这样python genpass.py -e \另外建议在工具里顺手打印一下密码的“信息熵”概念至少心里有数。密码熵的估算公式是长度 × log2(字符池大小)。字符池大小和熵值对照大致如下字符池内容字符池大小12位熵16位熵24位熵纯小写2656.4位75.2位112.8位小写大写5268.5位91.3位137.0位大小写数字6271.5位95.3位143.0位大小写数字符号9478.2位104.3位156.5位这个表不需要写进程序里但它能帮助你定默认参数。我一般把默认长度设为16默认启用所有类型这样开箱即用就是大约104位的强度。3.5 代码组织上的一个建议生成逻辑和入口分离上面的两个文件里命令行和GUI都调用了同一个generate_password()函数这就是一个简单的模块化习惯。不要把所有逻辑塞进main()里因为GUI也需要复用同样的密码生成规则。保持“核心逻辑独立”之后以后想加Web接口、批量生成、密码文件导出都只需要新增一个入口文件不用动核心函数。4. 实操过程与运行调试4.1 环境准备其实只需要一个Python解释器运行环境没有任何特殊要求。Windows用户去Python官网下载安装包时记得勾选“Add Python to PATH”macOS用户直接用系统自带的Python 3即可Linux发行版通常也预装了Python 3。在终端里验证一下版本python --version看到Python 3.6.0以上版本就可以。整个脚本只用argparse、secrets、string和可选的tkinter全部是标准库没有任何pip install的步骤。4.2 命令行实测把各种参数组合都跑一遍我建议拿到脚本后先按下面几组命令测试能覆盖绝大多数使用场景# 默认16位大小写、数字、符号全包含 python genpass.py # 指定20位密码 python genpass.py -l 20 # 不要特殊符号 python genpass.py --no-symbols # 排除易混淆字符 0O1lI python genpass.py -e 0O1lI # 一次生成10个备选密码 python genpass.py -n 10 -l 16以一次生成10个12位密码为例输出看起来会是这样$ python genpass.py -n 10 -l 12 W3#vQ9zLp2s aK!mR7cY5xTd nG8$uB1fE4zW ...注意如果你指定了-n 10但忘记了某些类型脚本默认仍然会保证大小写数字符号全覆盖因为has_required_types()在默认情况下始终启用。如果你用了--no-symbols那么特殊符号检查也会随之关闭逻辑是正确的。4.3 GUI版在Windows上的双击启动把gui_pass.py和genpass.py放在同一个目录下在Windows资源管理器里直接双击gui_pass.py就能打开窗口。如果双击没有反应在文件上右键选择“打开方式”里的Python即可。macOS和Linux用户就在终端里运行python gui_pass.py这个GUI窗口虽然简陋但给家人用足够了勾选需要的字符类型填长度点“生成密码”选中结果直接CtrlC复制。不需要教他们理解参数和命令行。4.4 批量生成的实际场景给同事开账号我在公司里帮同事批量初始化账号时用过这样一个扩展思路写一个很薄的外层脚本循环调用generate_password()然后按照“用户名密码”的格式输出成文本文件。from genpass import generate_password users [zhangsan, lisi, wangwu] with open(init_passwords.txt, w, encodingutf-8) as f: for user in users: pwd generate_password(length16) f.write(f{user}: {pwd}\n)这里要提个醒这种明文文件只适合当作临时交接文件用后马上删除尽量用加密压缩包或者发送到密码管理器里。随机密码生成器的职责是生成高强度密码但它不负责替你保管密码。5. 常见问题与排查技巧实录5.1 为什么有人改了两行代码就把安全等级降了经常有人看着网上的教程把secrets.choice改成random.choice理由是“反正结果看起来都一样”。看起来确实一样但安全模型完全变了。random模块的初始种子通常是基于当前时间或固定值生成的攻击者只要能拿到几个生成结果就可能推测出后续输出。密码这类对抗性场景里“看起来随机”是远远不够的。我自己的判断标准很简单但凡这段字符串之后要用在身份认证、访问令牌、密码等场景一律只碰secrets。5.2 Windows下运行Python时中文乱码怎么处理如果你把脚本里的中文提示打印到Windows命令行有时候会看到类似锘?的乱码。这通常是因为脚本文件保存成了无BOM的UTF-8而Windows命令行默认用GBK解码。解决办法有两个第一在代码文件顶部加一行# -*- coding: utf-8 -*-同时确保编辑器保存为UTF-8格式第二在运行环境里先执行chcp 65001切换命令行代码页。个人建议两者都做保险系数更高。5.3 生成的密码复制不到剪贴板命令行版生成的密码虽然打印在屏幕上但没有直接写进系统剪贴板。Windows下可以用一条管道命令实现python genpass.py | clipmacOS则用python genpass.py | pbcopyLinux桌面环境一般用xclippython genpass.py | xclip -selection clipboard需要提醒的是Windows的clip会把命令输出的末尾回车符也复制进去所以粘贴后密码末尾可能多出一个换行。这也是我后来倾向于在GUI版里用pyperclip的原因它能避免这种边界问题。5.4 某些网站注册总报“密码强度不够”问题出在规则理解上有一类网站对“强密码”的定义不是“必须有特殊符号”而是“必须同时包含大小写字母和数字且长度不少于8位”。这种时候执行python genpass.py --no-symbols -l 12就能搞定。还有更刁钻的网站不允许密码中出现连续三位键盘顺序字符比如abc、qwe、123。如果遇到这种需求可以再做一个简单的过滤函数检测生成的密码里是否包含任何连续键盘路径子串不满足就重新生成。这类规则确实冷门但真实世界就是有。5.5 密码里的特殊字符被网站“吃掉”了有些系统的输入框会限制特殊字符范围尤其单引号、双引号、尖括号、反斜杠这类往往在传输或存储时被转义。遇到这种情况直接用排除参数把不友好的符号过滤掉python genpass.py -e \/\\这会牺牲一点字符池大小但对一个20位密码来说减少几个符号的影响几乎可以忽略换来的是注册流程顺畅。5.6 手动改动生成后的密码是个坏习惯有些用户总觉得生成出来的密码“看着别扭”于是自己把后两个字符改成生日或字母缩写。这一改可能就把一个均匀分布的强密码降级成可猜测的模式。我的建议是生成结果一个字符都不要改如果觉得某串密码不好记忆那就再生成一次而不是人工“微调”。随机就是随机一旦人为介入熵就开始流失。6. 这个脚本不是一次性的它会一直跟着我这个小工具我从刚学Python时就开始用前后迭代过好几轮。第一版只能生成固定12位后来加了参数、加了排除字符、加了GUI最近又给它补上了批量生成能力。每次遇到新的注册规则第一反应不再是上网找在线生成器而是看一眼命令行参数能不能覆盖这个需求。有一个意外收获是因为这个脚本足够小它成了我向身边朋友普及“密码不要复用”的敲门砖别人看到你本地一条命令输出一串高强度密码会比听你讲一百句安全建议都更愿意改变习惯。我会顺手提醒他们把密码放进密码管理器里随机密码本来就该配合密码管理器使用而不是直接存进手机备忘录。如果你也想把这个项目继续扩展下去可以考虑的方向还很多给图形版加一个“生成后自动复制”的按钮把生成的密码按域名保存成历史记录或者利用argparse支持从配置文件读取参数。但无论怎么扩展核心逻辑里那行secrets.choice都不要轻易动。它才是这个小程序最值钱的地方。