postgresql设置密码复杂度校验

1 postgresql设置密码复杂度校验

1.1 默认密码校验策略

在PG中可以使用passwordcheck.so模块实现密码复杂度校验的功能,不过这个工具的默认要求很低,只可用于简单的密码复杂度校验,默认检查规则如下:

  1. 密码长度大于8位
  2. 密码不能与用户名相同
  3. 密码必须包括字母和数字

1.1.1 passwordcheck密码校验

安装完PostgreSQL之后,默认是没有开启密码复杂度,为了数据库安全以及应对等保测评等要求,有时我们需要设置密码复杂度。

PostgreSQL支持通过动态库的方式扩展PG的功能,pg在使用这些功能时需要预加载相关的共享库。而密码复杂度可以通过预加载passwordcheck.so模块实现。

有几种设置可用于将共享库预加载到服务器中,如下:

  • local_preload_libraries (string)
  • session_preload_libraries (string)
  • shared_preload_libraries (string)

下面介绍shared_preload_libraries (string)方式加载passwordcheck.so模块,此模块可以检查密码,如果密码太弱,他会拒绝连接;创建用户或修改用户密码时,强制限制密码的复杂度,限制密码不能重复使用例如密码长度,包含数字,字母,大小写,特殊字符等,同时排除暴力破解字典中的字符串。

1.1.2 shared_preload_libraries 方式启用passwordcheck.so模块

在PG库的数据目录下(centos默认路径为:/var/lib/pgsql/11/data,windows默认路径为:D:\PostgreSQL\11\data)找到postgresql.conf文件,修改

修改内容行为:shared_preload_libraries = 'passwordcheck' # (change requires restart)。

修改完成后重启服务服务生效(systemctl restart postgresql-11)。

imageimage

1.1.3 验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 提示密码太短
postgres=# create role ttt with password '123123';
ERROR: password is too short

# 提示密码必须包含字母和非字母
postgres=# create role ttt with password '12345678';
ERROR: password must contain both letters and nonletters

postgres=# create role ttt with password 'qweqweqwe';
ERROR: password must contain both letters and nonletters

# 提示密码不能包含用户名
postgres=# create role tttt with password 'tttt123456';
ERROR: password must not contain user name

1.2 增强密码校验策略

通过修改源代码实现。本文记录如何通过修改源码passwordcheck.c达到增强复杂度检验的目的,修改后验证规则如下:

  1. 密码长度大于8位
  2. 密码不能与用户名相同
  3. 密码必须包括字母
  4. 密码必须包括数字
  5. 密码必须包括特殊字符

1.2.1 步骤

1.2.1.1 实验环境

实验环境:CentOS7.6 + PG11.8 source code

源码下载地址: https://www.postgresql.org/ftp/source/v11.8/postgresql-11.8.tar.gz

源码安装文档:https://www.postgresql.org/docs/11/install-short.htm

1.2.1.2 使用方式

  • 替换目录 ../postgresql-11.4/contrib/passwordcheck 下的 passwordcheck.c
  • 编译安装 make && make install
  • postgresql配置文件内修改 (postgresql.conf)
  • shared_preload_libraries = ‘passwordcheck’
  • passwordcheck.level = ‘true’

1.2.1.3 效果

当密码长度足够,不符合规则的时候,无法新建用户

1.2.1.4 源码修改

将下载后的源码解压缩, 找到postgresql-11.8/contrib/passwordcheck/passwordcheck.c源文件,修改后保存退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/*-------------------------------------------------------------------------
* Luckyness
* 20191202
* 在源代码上修改自用,配置pg密码必须包含特殊字符
* pg版本11.4
* 使用方式:
* 替换目录 ../postgresql-11.4/contrib/passwordcheck 下的 passwordcheck.c
* 编译安装 make && make install
* postgresql配置文件内修改 (postgresql.conf)
* shared_preload_libraries = 'passwordcheck'
* passwordcheck.level = 'true'
*-------------------------------------------------------------------------
*/
/*-------------------------------------------------------------------------
*
* passwordcheck.c
*
*
* Copyright (c) 2009-2018, PostgreSQL Global Development Group
*
* Author: Laurenz Albe <laurenz.albe@wien.gv.at>
*
* IDENTIFICATION
* contrib/passwordcheck/passwordcheck.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"

#include <ctype.h>

#ifdef USE_CRACKLIB
#include <crack.h>
#endif

#include "commands/user.h"
#include "libpq/crypt.h"
#include "fmgr.h"
/* 引入扩展 */
#include "utils/guc.h"


PG_MODULE_MAGIC;

/*
* 配置文件内passwordcheck.level='true' 为需要特殊字符
* passwordcheck.level='false' 为只需要英文和数字
*/
static bool passwordcheck_level = false;


/* passwords shorter than this will be rejected */
#define MIN_PWD_LENGTH 8

extern void _PG_init(void);

/*
* check_password
*
* performs checks on an encrypted or unencrypted password
* ereport's if not acceptable
*
* username: name of role being created or changed
* password: new password (possibly already encrypted)
* password_type: PASSWORD_TYPE_* code, to indicate if the password is
* in plaintext or encrypted form.
* validuntil_time: password expiration time, as a timestamptz Datum
* validuntil_null: true if password expiration time is NULL
*
* This sample implementation doesn't pay any attention to the password
* expiration time, but you might wish to insist that it be non-null and
* not too far in the future.
*/
static void

check_password(const char *username,
const char *shadow_pass,
PasswordType password_type,
Datum validuntil_time,
bool validuntil_null)
{
if (password_type != PASSWORD_TYPE_PLAINTEXT)
{
/*
* Unfortunately we cannot perform exhaustive checks on encrypted
* passwords - we are restricted to guessing. (Alternatively, we could
* insist on the password being presented non-encrypted, but that has
* its own security disadvantages.)
*
* We only check for username = password.
*/
char *logdetail;

if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not equal user name")));
}
else
{
/*
* For unencrypted passwords we can perform better checks
*/
const char *password = shadow_pass;
int pwdlen = strlen(password);
int i;
/* bool pwd_has_letter,*/
bool
pwd_has_number,pwd_has_special,pwd_has_letter;

/* enforce minimum length */
if (pwdlen < MIN_PWD_LENGTH)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password is too short")));

/* check if the password contains the username */
if (strstr(password, username))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must not contain user name")));

if(passwordcheck_level)
{
/* check if the password contains both letters and number and specialchar */
pwd_has_number = false;
pwd_has_special = false;
pwd_has_letter = false;
for (i = 0; i < pwdlen; i++)
{
if (isalpha((unsigned char) password[i]))
pwd_has_letter = true;
else if (isdigit((unsigned char) password[i]))
pwd_has_number = true;
else
pwd_has_special = true;
}
if (!pwd_has_number || !pwd_has_letter || !pwd_has_special)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must contain both letters and number and specialchar")));
}
else
{
/* check if the password contains both letters and non-letters */
pwd_has_letter = false;
pwd_has_number = false;
for (i = 0; i < pwdlen; i++)
{
if (isalpha((unsigned char) password[i]))
pwd_has_letter = true;
else
pwd_has_number = true;
}
if (!pwd_has_letter || !pwd_has_number)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password must contain both letters and nonletters")));
}

#ifdef USE_CRACKLIB
/* call cracklib to check password */
if (FascistCheck(password, CRACKLIB_DICTPATH))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("password is easily cracked")));
#endif
}

/* all checks passed, password is ok */
}

/*
* Module initialization function
*/
void
_PG_init(void)
{
/* 密码级别参数 */
DefineCustomBoolVariable(
"passwordcheck.level",
gettext_noop("passwordcheck_level true: Password must contain leter, number, special characters;false : Password must contain leter, special characters"),
NULL,
&passwordcheck_level,
false,
PGC_POSTMASTER,
GUC_SUPERUSER_ONLY,
NULL, NULL, NULL);

/* activate password checks when the module is loaded */
check_password_hook = check_password;
}

1.2.1.5 编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
##编译安装pg server

##进入源码解压目录, 执行

cd postgresql-11.8

./configure--prefix=/u01/pgsql11.8 --without-zlib --without-readline -->>>>指定安装目录为/u01/pgsql11.8, 处于演示目的,所以没有安装zlib和readline包。

make

make install

##编译安装contrib,是一些第三方组织贡献出来的工具代码。

##进入源码解压目录执行

cd postgresql-11.8/contrib

make

make install

##编译完成后,在postgresql-11.8/contrib/passwordcheck/目录下增加passwordcheck.so文件
##编译后的文件,可以迁移到同版本数据库内使用

1.2.2 验证

首先开启passwordcheck验证, 修改参数文件postgresql.conf

1
2
3
# 修改如下
shared_preload_libraries= 'passwordcheck'
passwordcheck.level='true‘

重启实例生效

1
pg_ctl restart

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 提示密码太短
postgres=# create role btest with login password '123';
ERROR: password is too short


# 提示必须同时包括字母、数字、特殊字符
postgres=# create role btest with login password '12345678';
ERROR: password must contain both letters and number and specialchar

postgres=# create role btest with login password 'qqqqqqqq';
ERROR: password must contain both letters and nonletters

postgres=# create role btest with login password '!!!!!!!!!!!!';
ERROR: password must contain both letters and nonletters

postgres=# create role btest with login password '12345678a';
ERROR: password must contain both letters and number and specialchar

postgres=# create role btest with login password '1234567!';
ERROR: password must contain both letters and nonletters

postgres=# create role btest with login password '12345678a!';
CREATE ROLE

# 修改密码提示必须同时包含字母、数字和特殊字符
postgres=# alter role btest password '12345678!';
ERROR: password must contain both letters and number and specialchar

# 提示密码不能包含用户名
postgres=# alter role btest password '123456btest!';
ERROR: password must not contain user name

1.2.3 问题

警告
这里还存在个问题,就是通过\password命令修改的话,可以输入不满足长度的密码,原因是使用\passwordpasswordcheck检查的是加密后的口令,官方文档提到过,检查md5加密后的口令是很困难的,所以当passwordcheck检查加密的口令时,只检查密码是否与用户名相同这一项,实际上是将用户名通过md5加密后与数据库中的md5密码做比较,如果相同,则报错口令不能与用户名相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
postgres=#\password bert

Enter new password: ---->>>>>可以输入小于8位的口令, 而不被阻. 但是输入的是与用户名相同的话可以被检测出来。

Enter itagain:

postgres=#



postgres=#\set ECHO_HIDDEN ON

postgres=#\password bert

Enter newpassword:

Enter itagain:

*********QUERY **********

ALTERUSER bert PASSWORD 'md5efabd7549b98ddce9b14ba5e2e83eae1'

1.4 参考

https://www.modb.pro/db/171257

https://www.cnblogs.com/Luckyness/p/11996834.html

https://github.com/Luckyness/passwordcheck/blob/master/passwordcheck.c

-------------本文结束感谢您的阅读-------------

本文标题:postgresql设置密码复杂度校验

文章作者:OperationMAN

发布时间:2022年01月12日 - 14:01

最后更新:2022年06月05日 - 21:06

原始链接:https://kxinter.gitee.io/2022/01/12/Postgresql设置密码复杂度校验/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!