缘起
今天在看一个开源项目的时候,启动shell脚本里面因不存在命令 pwgen 而报错,查了下资料,这个命令是用来生成可读的密码。
pwgen - generate pronounceable passwords
深入去思考,使用命令工具生成的密码,应该有一套密码生成的规则,比如是否包含大小写字母,密码的长度是多少,哪些特殊符号可用,密码中必须包含多少大小写等。
具体的规则有了,生成代码逻辑也很容易完成。我们看下 pwgen 的源码,看下他有什么规则以及源码中有什么值得借鉴学习的内容。
pwgen
帮助文档
分析源码前,我们先看下 pwgen 的官方文档,了解他的功能及文档中提及的架构设计。文档地址为: https://linux.die.net/man/1/pwgen,也可以在命令行中执行 man pwgen
查看。
文档描述部分,说了这些(P.S. 老外,在解释概念方面,确实有一套,准确而具体)
- 能生成容易记忆的密码,也可以生成完全随机但不容易记忆的密码
- 在交互模式和shell脚本调用时,pwgen 的行为是不同的,交互模式能生成多个密码,shell脚本中,只生成一个密码
命令使用及参数
在命令行执行下运行命令
ubuntu@yun:~$ pwgen -h # 打印命令的帮助信息
Usage: pwgen [ OPTIONS ] [ pw_length ] [ num_pw ]
Options supported by pwgen:
-c or --capitalize
Include at least one capital letter in the password
-A or --no-capitalize
Don't include capital letters in the password
-n or --numerals
Include at least one number in the password
-0 or --no-numerals
Don't include numbers in the password
-y or --symbols
Include at least one special symbol in the password
-s or --secure
Generate completely random passwords
-B or --ambiguous
Don't include ambiguous characters in the password
-h or --help
Print a help message
-H or --sha1=path/to/file[#seed]
Use sha1 hash of given file as a (not so) random generator
-C
Print the generated passwords in columns
-1
Don't print the generated passwords in columns
-v or --no-vowels
Do not use any vowels so as to avoid accidental nasty words
ubuntu@yun:~$ pwgen # 生成了 8 * 20 个密码
oat0Ieph oo7Shumo aeBeegh2 oor7muPo pho6ooNg aecuuGh1 ohne2Kef Iec1Shoh
...
ij6guoSh yiCh5Ieg Jee3Aigo aavae8Ra Po5poh3U Ohj3Raiz eegh8aiR Geil4Aab
ubuntu@yun:~$ pwgen -1 # 一个密码
Bohba7Ji
ubuntu@yun:~$ pwgen 12 1 # 生成长度为12,1个密码
aexahque5Ohb
pwgen
的参数有两个,分别控制密码的长度和生成密码的个数
-
pw_length
密码的长度,默认为 8 -
num_pw
生成密码的个数
对于选项,则控制密码中包含那种字符,如何打印等。
控制密码组成的选项
-
-0, --no-numerals
不包含数字 -
-A, --no-capitalize
不包含大写字母 -
-B, --ambiguous
不使用容易混淆,分不清的字母,如数字1
和 字母l
,这些字母有B8G6I1l0OQDS5Z2
-
-c, --capitalize
至少包含一个大写字母,这个在 tty 设备是默认的 -
-n, --numerals
至少包含一个数字 -
-s, --secure
生成完全随机,不易记忆的密码 -
-v, --no-vowels
密码中不包含元音,即这些字母01aeiouyAEIOUY
-
-y, --symbols
至少包含一个特殊符号 -
-H, --sha1=/path/to/file[#seed]
使用指定文件的sha1
值生成密码
控制输出的选项
-
-1
一行打印一个密码 -
-C
按列打印密码, 在 tty 设备执行该命令,默认选项 -
-N, --num-passwords=num
生成多少个密码,P.S. 参数中有控制密码个数,如果同时出现,以参数为准
下载编译源码
知道如何使用后,我们看下他的源码. pwgen
是用 C
语言编写的,当前开发和维护者为 Theodore Ts'o,在他之前,还有别的开发者。根据作者和程序名称,在 Github 上找到了项目 tytso/pwgen 的源码。
如果使用 Debain 或者 Ubuntu 系统,可以通过如下命令下载源码.
sudo apt-get source pwgen
我下载了 Github 上的 tytso/pwgen,项目中有文件configure.ac
缺少 Makefile
,需要自己的平台编译,以 Ubuntu
为例,首先下载编译的工具 gcc
和 autoconf
sudo apt install autoconf
然后在源码目录执行命令
ubuntu@yun:~/github/pwgen$ autoscan
ubuntu@yun:~/github/pwgen$ autoconf # 生成文件 configure
ubuntu@yun:~/github/pwgen$ ls
autom4te.cache configure configure.scan depfix.sed Makefile.in pwgen.c pw_phonemes.c randnum.c sha1.h wordwrap.pl
autoscan.log configure.ac debian install-sh pwgen.1 pwgen.h pw_rand.c sha1.c sha1num.c
ubuntu@yun:~/github/pwgen$ ./configure # 生成文件 Makefile
....
ubuntu@yun:~/github/pwgen$ make # 编译源码
gcc -c -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DHAVE_GETOPT_LONG=1 -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_GETOPT_H=1 -g -O2 -Wall -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wshadow -Wwrite-strings -Wpointer-arith -Wcast-qual -Wcast-align -pedantic pwgen.c -o pwgen.o
...
如果想安装到本机的化,执行 make install
即可.
源码分析
程序的执行示意图如下:
生成密码的策略有两种,分别基于
- 随机密码,即随机的字符,比如大小写,数字,特殊符号等
- phoneme rules 音素规则,比如元音,辅音
我们重点看下随机密码,随机密码可以使用的字母如下:
// from file pw_rand.c
const char *pw_digits = "0123456789"; // 数字
const char *pw_uppers = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 大写字母
const char *pw_lowers = "abcdefghijklmnopqrstuvwxyz"; // 小写字母
const char *pw_symbols = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; // 特殊字母
const char *pw_ambiguous = "B8G6I1l0OQDS5Z2"; // 易混淆的字母
const char *pw_vowels = "01aeiouyAEIOUY"; // 元音
组合流程的示意图如下
按照程序的输入选项,包含那种字母,然后将可供选择的字母拼成字符串 chars
,在组合密码时,先随机产生一个正整数 a(a 的上线为 chars
的长度),字符串 chars
第 a 位的字符作为可供选择的字符,依次选择字符,如果不符合规则,则重新进行选择,只到符合规则为止。
实现中,有调用函数pw_number(int max)
产生随机数字,该函数对应的实现为 randnum.c
文件中的函数 pw_random_number(int)
,文件通过读取系统的文件/dev/urandom
(如果失败,则调用/dev/random
)前4个字节,获取随机值,然后对最大值进行取余,即得到随机数值。
关于文件 /dev/random
,wiki 上有这段描述:
In Unix-like operating systems, /dev/random, /dev/urandom and /dev/arandom are special files that serve as pseudorandom number generators.
即 /dev/random
, /dev/urandom
, /dev/arandom
是特殊的文件,可以当作伪随机序列生成器的作用。