資料庫在管理上會需要進行密碼的管控,以及密碼複雜度等設定,在 ORACLE 部分有 PASSWORD PROFILE 的功能可以使用,而在 EDB 企業版 PGSQL 中有對應相容的 PASSWORD PROFILE 能夠使用,這邊會針對 EDB 以及 PGSQL 在相關密碼管控上的功能進行比較與討論。
1. EDB 支援 ORACLE 相容性的 PASSWORD PROFILE 功能,能夠自定義密碼複雜度檢查函數(包含與前次密碼進行檢查),密碼重複使用次數,密碼過期時間,相關的檢查僅會在更改密碼時生效,不會影響當前使用者,但是在 PROFILE 部分如果有設定過期時間會生效開始計時。
2.PGSQL 可以透過啟用 passwordcheck 模組來進行密碼複雜度的檢查,只需要在設定檔案 potsgresql.auto.conf 裡面新增 shared_preload_libraries = '$libdir/passwordcheck' 後重啟即可生效,預設檢查密碼長度至少八個字、必須包含英數字、不能與使用者名稱相同,此檢查會涵蓋所有使用者,可以透過手動改寫C程式碼重新編譯的方式來做更複雜的調整。
另外在編譯過程可選擇啟用 CrackLib 的 Library 支援來阻擋容易被破解的密碼,並且可以自定義檢查用的字典。
目前 PGSQL 無法進行檢查密碼是否重複使用,也無法與前一個密碼進行比較是否有足夠差異。
3. PGSQL 可以透過設定 password valid time 來限制密碼使用期限,此設定與 PROFILE 的功能不相同,比較像是限制帳號的使用期限,因此不會隨著密碼更新而刷新時間,並且僅能透過管理者去移除設定與調整時間,由於管理員除非有紀錄 pg_shadow 裡面加密過後的密碼(不建議),不然無法比較確認使用者是否有更改過密碼進而去調整過期時間,並不適合用來限制密碼過期使用。
4. 不論是 EDB 的 password profile 或是 PGSQL 使用 passwordcheck 都僅能對明碼進行密碼複雜度的檢查,而如果使用加密過的密碼(md5XXXXX),做修改的時候則僅會檢查是否與帳號名稱一致,而不會做其餘檢查,因此如果使用到 psql 功能 \password 的時候,輸入的密碼會自動被轉碼成加密過的格式,因此僅會檢查密碼使否與使用者名稱相同,或是是否有重複使用,並無法檢查密碼檢查函數裡面設定的相關規則。
5. 如果在修改時欲使用加密過的密碼可以使用以下指令,將結果加上 md5 後進行修改密碼,或是用 psql 功能 \password。
[enterprisedb@ ~]$ echo -n "password+username" | md5sum edb=# alter user username password 'md5714c22dab12a8211a0915aa4b343b0c1';
下面會針對 PGSQL 的 passwordcheck 模組做簡單的改寫範例,此處不針對 EDB 版本做測試,在 EDB 直接使用 PROFILE 功能即足夠應付密碼安全性需求。
本處需要下載原始碼進行編譯
wget postgresql-13.4.tar.gz tar -zxvf postgresql-13.4.tar.gz cd postgresql-13.4/contrib/passwordcheck/ cp passwordcheck.c passwordcheck.c.bkp # 先備份檔案方便後面重試 vi passwordcheck.c
主要編輯 check_password 這個函數內容,來修改密碼複雜度檢查規範,此處新增一範例檢查密碼是否為帳號倒過來,以及調整對於英數字數量的長度限制。
#define MIN_PWD_LENGTH 8 /*可以修改此處調整密碼最低長度*/ check_password(const char *username, const char *shadow_pass, PasswordType password_type, Datum validuntil_time, bool validuntil_null) { if (prev_check_password_hook) /*加密過後的密碼走此處檢查*/ prev_check_password_hook(username, shadow_pass, password_type, validuntil_time, 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; int pwd_has_letter, pwd_has_nonletter; int pwd_is_reverse; /* 檢查密碼最小長度 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"))); /* check if the password is the reverse of username 檢查密碼是否為帳號倒過來*/ if (strlen(username) == pwdlen) { pwd_is_reverse = 0; for ( i = 0; i < pwdlen; i++ ) { if ( password[pwdlen-i-1] == username[i]) pwd_is_reverse++; } if (pwd_is_reverse == pwdlen) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("password must not be the reverse of user name"))); } /* check if the password contains both letters and non-letters */ pwd_has_letter = 0; pwd_has_nonletter = 0; for (i = 0; i < pwdlen; i++) { /* * isalpha() does not work for multibyte encodings but let's * consider non-ASCII characters non-letters * 此處調整改為檢查英文字母和非英文字母都必須至少包含三個以上 */ if (isalpha((unsigned char) password[i])) pwd_has_letter++; else pwd_has_nonletter++; } if (pwd_has_letter < 3 || pwd_has_nonletter < 3) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("password must contain both 3 letters and 3 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 */ }
cd /root/postgresql-13.4 ./configure gmake world
cd /root/postgresql-13.3/contrib/passwordcheck/ make clean; make;
完成後將檔案複製到當前資料庫 LIB 目錄後啟動資料庫進行測試使用,如果有狀況則回頭修改 passwordcheck.c 檔案後再重新編譯安裝。
cp /usr/pgsql-13/lib/passwordcheck.so ~/passwordcheck.so.bkp cp /root/postgresql-13.3/contrib/passwordcheck/passwordcheck.so /usr/pgsql-13/lib/ service postgresql-13 start
測試使用
-bash-4.2$ psql -E psql (13.3) Type "help" for help. postgres=# create user abcd1234; CREATE ROLE postgres=# alter user abcd1234 password 'abcd'; ERROR: password is too short postgres=# alter user abcd1234 password 'abcd1234'; ERROR: password must not contain user name postgres=# alter user abcd1234 password 'abcdefghi12'; ERROR: password must contain both 4 letters and 4 nonletters postgres=# alter user abcd1234 password '4321dcba'; ERROR: password must not be the reverse of user name postgres=# \password -- 此處加密因此不受密碼檢查影響 Enter new password:abc Enter it again:abc ********* QUERY ********** ALTER USER postgres PASSWORD 'SCRAM-SHA-256$4096:TEzknK706ouVnIb+gXDmeg==$U7QDORNSX3Dkmo5y5s+PeggplFYjKk/iAb8z/diA8WY=:6OUaunD7wtxHfV+gZjnb/Ycvc32ME2cgNWlXcMRDSbs=' ************************** postgres=#
參考連結:
https://www.modb.pro/db/32449?ywm
https://www.postgresql.org/docs/13/passwordcheck.html
https://paquier.xyz/postgresql-2/postgres-module-highlight-customize-passwordcheck-to-secure-your-database/
https://billtian.github.io/digoal.blog/2014/10/09/01.html
https://ravenonhill.blogspot.com/2017/03/postgresql-edb-password-profile.html