基于Python编写一个强密码随机生成器
作者:溪语流沙
“随机”和“安全”是一回事吗?——不是。
这个区别,恰好是本篇最大的知识点,也是很多写了三年Python的人没想过的问题。
本篇成品:一个80行上下的密码生成器。长度可选、字符集可配、一键进剪贴板,用“密码学级”的随机数保证每个密码都不可预测。
太长不看版(给想快速上手的你)
| 项目信息 | 一句话说明 |
|---|---|
| 项目名称 | 强密码生成器(Password Generator) |
| 代码行数 | ~80行(含注释) |
| 依赖 | 第一个第三方包:pyperclip(剪贴板功能) |
| 核心功能 | 长度可选、字符集可配、一键复制、密码学安全 |
| 跑起来的命令 | python main.py(默认16位) |
| 核心知识点 | secrets(真随机)≠ random(伪随机) |
| 做完你能得到 | 一个可以天天用的密码工具 |
一、先做一个实验:random的“伪”字怎么理解
打开终端,亲手跑这两行:
import random random.seed(42) print(random.random())
输出:0.6394267984578837
关掉重跑一遍——还是这个数。 只要种子相同,random的“随机”序列就一模一样,因为它用的是数学公式(梅森旋转算法)模拟出来的随机,原理上只要观察到足够多的输出就能被推断。
这就是“伪随机”。对游戏骰子、抽样模拟毫无问题;但用来生成密码就是灾难——攻击者推断出你的随机规律,等于你的密码有了出厂说明书。
所以Python专门提供了secrets模块
| 对比维度 | random(伪随机) | secrets(真随机) |
|---|---|---|
| 随机来源 | 数学公式(种子决定) | 操作系统真实熵(鼠标移动、磁盘抖动等物理噪声) |
| 能否复现 | ✅ 种子相同即可复现 | ❌ 无法复现 |
| 适用场景 | 游戏、模拟、抽样 | 密码、令牌、安全相关一切 |
| Python官方建议 | “不应用于安全目的” | “用于生成密码、令牌等” |
一句话记住:决定“游戏里怪走哪条路”用random,决定“你的密码是什么”用secrets。
二、成品预览
$ python main.py 生成的密码:k7#mQx2$Lp9@vR4w $ python main.py -l 24 --no-symbols 生成的密码:hT3nBq8Xw2mZ5rK9pL4vE7cA $ python main.py -l 12 --no-symbols --no-upper 生成的密码:f4k2m9x1q7wz $ python main.py -c 生成的密码:k7#mQx2$Lp9@vR4w 已复制到剪贴板 ✅
四个需求全满足
| 需求 | 参数 | 说明 |
|---|---|---|
| 长度可选 | -l 24 | 默认16位 |
| 字符集可配 | --no-symbols / --no-upper | 默认全启用 |
| 一键进剪贴板 | -c | 复制后直接Ctrl+V粘贴 |
| 密码学安全 | secrets | 真随机,不可预测 |
三、前置知识:两个标准库模块
string模块 —— 现成的字符集常量表
不用手打a到z,直接用:
| 常量 | 内容 |
|---|---|
string.ascii_lowercase | abcdefghijklmnopqrstuvwxyz |
string.ascii_uppercase | ABCDEFGHIJKLMNOPQRSTUVWXYZ |
string.digits | 0123456789 |
string.punctuation | 全部英文符号(本篇自选了一个更温和的子集) |
secrets三件套
本篇用到两个,第三个预告一下:
| 函数 | 作用 |
|---|---|
secrets.choice(序列) | 从序列里安全随机挑一个 |
secrets.randbelow(n) | 返回0到n-1的安全随机整数 |
secrets.token_hex() | 生成十六进制令牌(第23篇FastAPI会用到) |
四、第0步:setup——第一个第三方依赖登场
1. 新建文件夹passwd_gen,VSCode打开
2. python -m venv venv && 激活venv
3. git init + .gitignore
pip install pyperclip pip freeze > requirements.txt # 立刻存档,从第一个依赖就养成习惯
看到requirements.txt里多出pyperclip==x.x.x这行,你就明白第3篇的“存档/读档”不是纸上谈兵。
五、第1步:字符池——按开关拼装弹药库
"""passwd —— 强密码生成器"""
import argparse
import secrets
import string
CHARSETS = {
"lower": string.ascii_lowercase, # a-z
"upper": string.ascii_uppercase, # A-Z
"digits": string.digits, # 0-9
"symbols": "!@#$%^&*-_=+?", # 自选的安全符号子集
}
def build_pool(use):
"""按开关拼出字符池,并抽出"每类保底"的必备字符"""
pool = ""
required = []
for name, chars in CHARSETS.items():
if use[name]:
pool += chars
required.append(secrets.choice(chars))
return pool, required
required的设计意图:如果用户要“含数字”的密码,就不能靠运气——每启用的类别保底放一个,剩下的再从全池随机补。这是密码生成器的行业惯例。
六、第2步:安全洗牌——random.shuffle也不行(重点!)
有个隐蔽的问题:保底字符永远排在密码开头(第一位小写、第二位大写……),规律等于白保底。所以要洗牌。
但注意——random.shuffle()也不行,它内部还是伪随机。三行自己写一个secrets版:
def secure_shuffle(items):
"""secrets版洗牌(Fisher-Yates):random.shuffle的随机性不够"硬" """
for i in range(len(items) - 1, 0, -1):
j = secrets.randbelow(i + 1)
items[i], items[j] = items[j], items[i]
return items
这是经典的Fisher-Yates洗牌:从尾往前,每个位置和一个随机更早的位置交换。算法课上的知识,在这里真的用上了。
七、第3步:generate——保底、补齐、洗牌
def generate(length, use):
pool, required = build_pool(use)
if not pool:
raise ValueError("至少要启用一类字符")
if length < len(required):
raise ValueError(f"长度至少要 {len(required)},才能保证每类字符都出现")
chars = required[:] # 每类保底一个
chars += [secrets.choice(pool) for _ in range(length - len(required))]
return "".join(secure_shuffle(chars)) # 洗牌后拼接
两个raise ValueError的意义
| 校验 | 场景 | 作用 |
|---|---|---|
if not pool | 四个--no-xxx全开 | 字符池为空,没法生成 |
if length < len(required) | -l 2还要四类字符保底 | 数学上不可能 |
报错信息写清楚原因——这是把用户当人看。不是程序崩溃,是帮用户发现不合理的需求。
八、第4步:argparse + 剪贴板
def main():
parser = argparse.ArgumentParser(description="强密码生成器")
parser.add_argument("-l", "--length", type=int, default=16,
help="密码长度,默认16")
parser.add_argument("--no-lower", action="store_true", help="不用小写字母")
parser.add_argument("--no-upper", action="store_true", help="不用大写字母")
parser.add_argument("--no-digits", action="store_true", help="不用数字")
parser.add_argument("--no-symbols", action="store_true", help="不用符号")
parser.add_argument("-c", "--copy", action="store_true",
help="生成后自动复制到剪贴板")
args = parser.parse_args()
use = {"lower": not args.no_lower, "upper": not args.no_upper,
"digits": not args.no_digits, "symbols": not args.no_symbols}
try:
password = generate(args.length, use)
except ValueError as e:
print(f"参数有问题:{e}")
return
print(f"生成的密码:{password}")
if args.copy:
try:
import pyperclip
pyperclip.copy(password)
print("已复制到剪贴板 ✅")
except ImportError:
print("未安装pyperclip:pip install pyperclip后可自动复制")
if __name__ == "__main__":
main()
两个设计细节
| 设计 | 写法 | 为什么 |
|---|---|---|
| 开关反向 | --no-xxx | 默认全启用,常用路径最短 |
| 剪贴板容错 | try/except ImportError | 没装pyperclip照样出密码——可选功能不许拖死核心功能 |
九、第5步:验收清单
python main.py # 默认16位,四类字符都有 python main.py -l 24 --no-symbols # 24位无符号,适合死板的网站 python main.py -l 12 --no-symbols --no-upper python main.py -c # 去Ctrl+V粘贴试试 python main.py -l 2 # 应该看到友好的报错,而不是崩溃
提交Git
git add . git commit -m "完成密码生成器v1:secrets随机 + 剪贴板"
十、常见报错:这6个,安全类工具的特色坑(重点!)
ModuleNotFoundError: No module named 'pyperclip'
原因:装了还是报错?八成装进了别的环境。
解法:第3篇的排查三连回归——
- 看提示符有没有
(venv); python -m pip list确认pyperclip在列表里;- 不行就
python -m pip install pyperclip重装。
参数有问题:至少要启用一类字符
原因:你把四个--no-xxx全开了,字符池为空。校验在拦你。
解法:至少留一类字符。这不是bug,是第7步那句raise ValueError在站岗。
参数有问题:长度至少要 4
原因:-l 2还想四类字符保底?数学上不可能。
解法:要么加长度,要么减类别。
生成的密码开头永远是小写、大写、数字、符号的顺序
原因:你忘了洗牌,或者用了random.shuffle。
解法:确认调用的是secure_shuffle而不是random.shuffle。用断点在return前看一眼chars,洗牌前后一对比就明白。
Linux下报PyperclipException
原因:pyperclip在Linux需要xclip或xsel支持。
解法:
sudo apt install xclip # Ubuntu/Debian # 或 sudo apt install xsel
Windows/macOS开箱即用。
某些网站死活不接受生成的密码
原因:不少老系统只允许有限的符号(比如只认!@#,不认$%^)。
解法:
- 用
--no-symbols开关生成纯字母数字; - 或者修改
CHARSETS里的“symbols”字符串,换成网站允许的符号。
这就是CHARSETS里“自选符号子集”存在的原因——生成器迁就规则,而不是反过来。数据驱动的好处又见面了。
十一、课后练习
| # | 练习 | 难度 | 提示 |
|---|---|---|---|
| 1 | 强度可视化:打印“熵约Nbit” | ⭐⭐ | length * math.log2(len(pool));经验值80bit以上算强 |
| 2 | 记忆型密码:支持“四单词模式”,如tiger-apple-moon-river | ⭐⭐ | 单词表 + '-'.join(secrets.choice(words) for _ in range(4)) |
| 3 | 批量模式:加--count 5一次生成5个 | ⭐⭐ | for循环调generate |
| 4(选做) | 排除易混淆字符:0/O、1/l长得太像 | ⭐⭐⭐ | 从字符池里.replace()掉即可 |
一个安全彩蛋
顺手把本篇的知识串起来:生成密码只是第一步,密码本身的存放同样是安全问题。
所以:
- 别把密码写进任何代码、笔记或截图
- 用密码管理器(Bitwarden、1Password等)
- 开启两步验证(2FA)
这个后续会正式展开:用户系统里的密码为什么必须“哈希加盐”存储。
你现在只需要记住军规——明文密码不进仓库(.gitignore的最后三行就是为它准备的)。
以上就是基于Python编写一个强密码随机生成器的详细内容,更多关于Python密码生成器的资料请关注脚本之家其它相关文章!
