PostgreSQL 密碼驗證功能增強
阿新 • • 發佈:2021-06-17
密碼驗證介紹
passwordcheck 模組是在 CREATE ROLE 或者 CREATE USER 期間檢查使用者密碼是否符合指定的規則模組如果密碼比較弱,那麼在此期間將會拒絕執行密碼並返回一個錯誤。該模組位於 srcpkg/contrib 目錄下,安裝後位於 $libdir 目錄下,使用 shared_preload_libraries載入並重新啟動伺服器後生效。在該模組中,主要有兩個規則判斷,一個是使用者名稱自身的判斷,一個是密碼長度少於8位的判斷,一個是對是否包含使用者名稱本身的判斷。
密碼驗證增強功能
密碼驗證增強功能主要是在原有密碼檢查模組的基礎上,增加了對密碼中是否包含至少一個大小寫字母,一個數字和一個特殊字元的判斷。
實現
/*------------------------------------------------------------------------- * * passwordcheck_enchance.c * * Author: Sungsasong * * IDENTIFICATION * contrib/passwordcheck_enhance/passwordcheck_enhance.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include <ctype.h> #ifdef USE_CRACKLIB #include <crack.h> #endif #include "commands/user.h" #include "catalog/namespace.h" #include "libpq/crypt.h" #include "fmgr.h" #include "utils/guc.h" #if PG_VERSION_NUM < 100000 #include "libpq/md5.h" #endif PG_MODULE_MAGIC; /* passwords shorter than this will be rejected */ #define MIN_PWD_LENGTH 8 #define MIN_UPPER_LETTER 1 #define MIN_LOWER_LETTER 1 #define MIN_DIGIT_CHAR 1 #define MIN_SPECIAL_CHAR 1 extern void _PG_init(void); /********************************************************************** *Function:passwordcheck_enhance * *Verifying the password at least need contains one upper letter,lower* *letter,digit and specital character * *********************************************************************/ #if PG_VERSION_NUM >= 100000 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 contain 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, // pwd_has_nonletter; int PWD_UPPER_LETTER_COUNT = 0; int PWD_LOWER_LETTER_COUNT = 0; int PWD_SPECIAL_CHAR_COUNT = 0; int PWD_DIGIT_COUNT = 0; int PWD_CONTAINS_LETTER_COUNT = 0; //如果滿足至少8位密碼的條件,那麼判斷密碼中是否包含至少一個大小寫字母和特殊字元 for(i = 0; i < pwdlen; i++) { /* enforce minimum length */ if(pwdlen < MIN_PWD_LENGTH) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼長度至少需要 %d 位,並且至少需要包含一個大小寫字母和特殊字元 ",MIN_PWD_LENGTH))); } //判斷是否包含字母 if(isalpha((unsigned char) password[i])) { PWD_CONTAINS_LETTER_COUNT++; if(islower((unsigned char) password[i])) { PWD_LOWER_LETTER_COUNT++; }else if(isupper((unsigned char) password[i])) { PWD_UPPER_LETTER_COUNT++; } }else if(isdigit((unsigned char) password[i])) { PWD_DIGIT_COUNT++; }else { PWD_SPECIAL_CHAR_COUNT++; } } //判斷是否至少包含了一個數字,大小寫字母和特殊字元 if(PWD_LOWER_LETTER_COUNT < MIN_LOWER_LETTER) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼至少需要包含 %d 個小寫字母", MIN_LOWER_LETTER))); }else if(PWD_UPPER_LETTER_COUNT < MIN_UPPER_LETTER) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼至少需要包含 %d 個大寫字母", MIN_UPPER_LETTER))); }else if(PWD_DIGIT_COUNT < MIN_DIGIT_CHAR) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼至少需要包含 %d 個數字", MIN_DIGIT_CHAR))); }else if(PWD_SPECIAL_CHAR_COUNT < MIN_SPECIAL_CHAR) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼至少需要包含 %d 個特殊字元", MIN_DIGIT_CHAR))); } /* check if the password contains the username */ if (strstr(password, username)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼不能與使用者名稱同名"))); } } /* all checks passed, password is ok */ } #else static void check_password(const char *username, const char *password, int password_type, Datum validuntil_time, bool validuntil_null) { int namelen = strlen(username); int pwdlen = strlen(password); char encrypted[MD5_PASSWD_LEN + 1]; int i; bool pwd_has_letter, pwd_has_nonletter; int PWD_UPPER_LETTER_COUNT = 0; int PWD_LOWER_LETTER_COUNT = 0; int PWD_SPECIAL_CHAR_COUNT = 0; int PWD_DIGIT_COUNT = 0; int PWD_CONTAINS_LETTER_COUNT = 0; switch (password_type) { case PASSWORD_TYPE_MD5: /* * 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. */ if (!pg_md5_encrypt(username, username, namelen, encrypted)) { elog(ERROR, "password encryption failed"); } if (strcmp(password, encrypted) == 0) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("password must not contain user name"))); } break; case PASSWORD_TYPE_PLAINTEXT: /* * For unencrypted passwords we can perform better checks */ /* enforce minimum length */ //如果滿足至少8位密碼的條件,那麼判斷密碼中是否包含至少一個大小寫字母和特殊字元 for(i = 0; i < pwdlen; i++) { /* enforce minimum length */ if(pwdlen < MIN_PWD_LENGTH) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼長度至少需要 %d 位,並且至少需要包含一個大小寫字母和特殊字元 ",MIN_PWD_LENGTH))); } //判斷是否包含字母 if(isalpha((unsigned char) password[i])) { PWD_CONTAINS_LETTER_COUNT++; if(islower((unsigned char) password[i])) { PWD_LOWER_LETTER_COUNT++; }else if(isupper((unsigned char) password[i])) { PWD_UPPER_LETTER_COUNT++; } }else if(isdigit((unsigned char) password[i])) { PWD_DIGIT_COUNT++; }else { PWD_SPECIAL_CHAR_COUNT++; } } //判斷是否至少包含了一個數字,大小寫字母和特殊字元 if(PWD_LOWER_LETTER_COUNT < MIN_LOWER_LETTER) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼至少需要包含 %d 個小寫字母", MIN_LOWER_LETTER))); }else if(PWD_UPPER_LETTER_COUNT < MIN_UPPER_LETTER) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼至少需要包含 %d 個大寫字母", MIN_UPPER_LETTER))); }else if(PWD_DIGIT_COUNT < MIN_DIGIT_CHAR) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼至少需要包含 %d 個數字", MIN_DIGIT_CHAR))); }else if(PWD_SPECIAL_CHAR_COUNT < MIN_SPECIAL_CHAR) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼至少需要包含 %d 個特殊字元", MIN_DIGIT_CHAR))); } /* check if the password contains the username */ if (strstr(password, username)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("密碼不能與使用者名稱同名"))); } break; default: elog(ERROR, "unrecognized password type: %d", password_type); break; } /* all checks passed, password is ok */ } #endif /* * Module initialization function */ void _PG_init(void) { /* activate password checks when the module is loaded */ check_password_hook = check_password; }
獲得原始碼
[passwordcheck_enhance](https://github.com/DeveloperHonor/passwordkcheck-enhance-for-postgresql.git "passwordcheck_enhance module")
安裝
*下載原始碼檔案並傳入到 PostgreSQL 原始碼包 contrib 目錄下* *解壓下載的原始碼包檔案* `[postgres@sungsasong contrib]$ unzip passwordkcheck-enhance-for-postgresql-main` *切換到解壓目錄* *執行 make && make install* *在 $PGDATA/postgresql.auto.conf或者 $PGDATA/postgresql.conf檔案中加入如下* `shared_preload_libraries = 'passwordcheck_enhance` *重新啟動 PostgreSQL 伺服器*
驗證
postgres=# CREATE USER user_test WITH PASSWORD 'user'; ERROR: 密碼長度至少需要 8 位,並且至少需要包含一個大小寫字母和特殊字元 postgres=# CREATE USER user_test WITH PASSWORD 'useruser'; ERROR: 密碼至少需要包含 1 個大寫字母 postgres=# CREATE USER user_test WITH PASSWORD 'useruseA'; ERROR: 密碼至少需要包含 1 個數字 postgres=# CREATE USER user_test WITH PASSWORD 'useruseA1'; ERROR: 密碼至少需要包含 1 個特殊字元 postgres=# CREATE USER user_test WITH PASSWORD 'useruseA1!'; CREATE ROLE