ptt / pttbbs

PTT BBS source code
GNU General Public License v2.0
1.09k stars 138 forks source link

[Feature Request] Support more than 8 characters password #112

Open LImoritakeU opened 2 years ago

LImoritakeU commented 2 years ago

問題描述

目前 ptt 只支援最高 8 個字元的密碼,希望能將上限提升至 32 或 64 字元以提升帳號安全性

https://github.com/ptt/pttbbs/blob/4d56e77f264960e43e060b77e442e166e5706417/include/pttstruct.h#L49

我沒有寫過 C ,但就 code-level 並沒有特別對 8 個字元限制特別說明,我只能猜想或許限制是在儲存層 ?

wens commented 2 years ago

DES crypt...

IepIweidieng commented 2 years ago

第一限制:fcrypt() 的實作(採用 DES hashing)

在常見的 DES hashing 實作中,密碼明碼僅有前 8 字元的 7-bit 部份有效。

在 PttBBS 中的實作,請見 https://github.com/ptt/pttbbs/blob/master/common/sys/crypt.c

由於 glibc 的 crypt 函式庫爲了最佳化的執行速度而使用的全域加速 table 佔用了太多記憶體空間(下述),PttBBS,恰如同一些 FireBird BBS 分支,改採用其它空間需求較少的函式庫來提供 DES hashing 之功能。

後來的 glibc 的 crypt() 尚增加了如 MD5、SHA-256、等等較現代化的 hashing 方式,但礙於記憶體空間,此函式無法直接於 PttBBS 使用。 可見 https://man7.org/linux/man-pages/man3/crypt.3.html

※ DreamBBS 直接使用了 glibc 的 crypt() ,故有記憶體使用量上的疑慮。

可能的解決方法擧隅

第二限制:userec_t::passwd[] 的大小(= PASSLEN = 14)

如果要用 32 至 64 個 bytes 長的可輸入字串 (假設爲 Big5 編碼) 作爲密碼的話,理論上至少需要 ⌈32 ⋅ log₂(95 + 128) ÷ 8⌉ = 32 至 ⌈64 ⋅ log₂(95 + 128) ÷ 8⌉ = 63 個 bytes 大的空間。

又,因需儲存 hash 資訊,實際需要的空間更大。

※ DreamBBS 儲存 SHA-256-hashed 密碼時,將 hash 資訊與 hashed 密碼分開儲存,其中 hash 資訊沿用欄位 passwd[],而 hashed 密碼的空間 (passhash[]) 則是將已不再使用的大資料欄位變更用途而來的。

可能的解決方法擧隅

第三限制:使用者(?)

只增加支援 > 8 bytes 8-bit 密碼明碼還不足夠,還需使用者主動修改密碼。

由於依 hashed 密碼直接回推原密碼明碼,再以新方式 hash,需要一定的計算資源(下述),又因爲 DES hashing 只看密碼明碼 7-bit 的部份,如果使用者的密碼明碼包含特殊字元,會回推錯誤,造成使用者以同樣的密碼明碼無法登入之窘況。

因此站方尚需加以宣導,鼓勵使用者利用新密碼 hash 的方式,方可發揮 > 8 bytes 8-bit 密碼的優點。

kcwu commented 2 years ago
  1. ptt 的 crypt 函式是我換的, 該函式的確只支援 DES crypt, 不過我想另外找一個 library 來用應該不是太大的問題. 當然 @IepIweidieng 建議的解法也可以嘗試.
  2. passwd[] 大小的確不夠, 因此需要擴增欄位, 關站轉換檔案格式. 不過因為只需要存密碼的 salt 跟 hash, 空間不一定需要用到 @IepIweidieng 說的那麼大. p.s. 如果要做, 記得 "取備份" 這功能要特別處理, 以免拿舊欄位蓋掉新的資料, 可能會造成資料錯亂的問題.
  3. 基本上不會採用逆推原密碼的方式處理這個問題. 比較簡單的做法是, 同時支援新舊方式, 當使用者上站時, 密碼驗證通過之後當場轉成新的 hash funciton 寫回檔案. 由於帳號太久沒用會被砍掉, 所以理論上幾個月之後全站就能轉換完成.
robertabcd commented 2 years ago
  1. 基本上不會採用逆推原密碼的方式處理這個問題. 比較簡單的做法是, 同時支援新舊方式, 當使用者上站時, 密碼驗證通過之後當場轉成新的 hash funciton 寫回檔案. 由於帳號太久沒用會被砍掉, 所以理論上幾個月之後全站就能轉換完成.

直接在背後轉換會有問題,因為 crypt 現在只吃 8 碼,但登入可以打超過 8 碼(logind 吃 14 碼),在背後轉無法確認超過的部分是不是使用者想要的,如果在轉換的那次不小心 8 位以後輸入錯誤,之後會無法登入。

kcwu commented 2 years ago

直接在背後轉換會有問題,因為 crypt 現在只吃 8 碼,但登入可以打超過 8 碼(logind 吃 14 碼),在背後轉無法確認超過的部分是不是使用者想要的,如果在轉換的那次不小心 8 位以後輸入錯誤,之後會無法登入。

good point. 所以這時要讓使用者再輸入一次確認? 不過這樣大概會讓很多做自動 login 的 app 壞掉 XD

robertabcd commented 2 years ago

所以這時要讓使用者再輸入一次確認?

我覺得可以有個宣導期,過了之後登入強迫改密碼。

IepIweidieng commented 2 years ago
  1. passwd[] 大小的確不夠, 因此需要擴增欄位, 關站轉換檔案格式. 不過因為只需要存密碼的 salt 跟 hash, 空間不一定需要用到 @IepIweidieng 說的那麼大.

吾忘 hash 會撞,因此 32–63 實指「無 collisions」所需之 passwd[] 最小空間。 crypt() 各模式之 hash 長度:https://man7.org/linux/man-pages/man3/crypt.3.html#NOTES

因不愼假設資料結構需向舊相容,我未列入擴大其欄位之方法。 DreamBBS 將 crypt() 回傳的總 hash 分割儲存:

可參考 https://github.com/ccns/dreambbs/blob/9b3e98f9d7/lib/passwd.c#L89

若可擴大 passwd[] 的話,大可不必如此切割。

  1. 基本上不會採用逆推原密碼的方式處理這個問題. 比較簡單的做法是, 同時支援新舊方式, 當使用者上站時, 密碼驗證通過之後當場轉成新的 hash funciton 寫回檔案. 由於帳號太久沒用會被砍掉, 所以理論上幾個月之後全站就能轉換完成.

function

上述計算顯示暴力逐一破解是 不可行 的(不過忘了寫上結論,還請見諒)。但若破解 6 碼密碼已有一定難度,設定 32–64 碼長的密碼眞的有必要嗎?(不過,由於人造密碼遠弱於隨機密碼,還是需要 > 8 碼密碼以彌補其強度)

DreamBBS 之密碼最長 36 (= PLAINPASSSIZE - 1),一是防 collisions(必要性或可議),二是防 vget() 輸入框溢出畫面(雖後已隱藏顯示),三是防誤按鍵盤過度會連打退格鍵過度(?)。

由於站臺之 社會角色之差異,DreamBBS 未啟用賬號退場機制,也無法有效籍助 忘記密碼 之功能,因此無法採取批踢踢的策略,僅能無限期同時持續支援 DES 與 SHA-256 之 hash 方式,並預設對新密碼使用 SHA-256 hashing。

即使密碼長度不變,改用非 DES hashing 仍有一定作用。考慮 DES hashing 每碼僅 lower 7 bits 有效,8 碼密碼至少可有 2⁸ = 256 種 hash 相同的輸入。超出 8 碼的話,hash 相同的輸入爲 223ⁿ⁻⁸ 倍。 但這也對 滾動式密碼自動更新 造成窒礙。

另外,由於 checkpasswd() 會把原密碼抹除,所以驗證後取不到原密碼(雖然可以修改程式以 類繞過)。見 https://github.com/ptt/pttbbs/blob/2fae7442d4/common/bbs/passwd.c#L224

chhsiao1981 commented 2 years ago

覺得目前更重要的是如何在 login 成功以後. 在 UI 上如果發現 user 的 password "太簡單". 引導 user 設定"不是太簡單的 password"

對於"不是太簡單的 password"的定義可以再討論. 我的提議是需字數 >= 7 + 含有 non-alnum 的 char. (或是更高級的就是弄個 dict 把簡單 password set 放進去.)

(不論 crypt system 多強~ 只要 user 設定 password 是 1111 這種爛密碼~. 仍然是很容易被破~)

IepIweidieng commented 2 years ago

如果要儘可能維持與連線軟體的相容,又要有滾動式密碼自動更新的可靠驗證的話,以下方式或許可行。

假設 PttBBS 將採用 SHA-256 hashing。

  1. 需要允許在各 BBS 使用者家目錄下建立一個檔案,其內容爲 SHA-256 hashing。假設其名稱爲 sha256.passwd
    • 此資料僅於密碼更改的宣導期間使用,因此不宜併入 userec_t,否則宣導期結束後需再次更改資料結構,造成維護上的不便。
  2. 使用者登入時,若密碼驗證成功,爲 DES hashing,且 sha256.passwd 不存在的話,則用原密碼計算對應的 SHA-256 hash 並存入 sha256.passwd。(尚不更改 userec_t::passwd[]
  3. 使用者下次登入時,若密碼以 DES hashing 驗證成功,且 sha256.passwd 存在的話,就再以 SHA-256 hashing 驗證。
    • 若 SHA-256 hash 符合的話,就將 userec_t::passwd[]sha256.passwd 的內容覆蓋,並刪除 sha256.passwd
    • 若 SHA-256 hash 不符合的話,直接刪除 sha256.passwd,當作 1. 沒發生。
  4. 密碼更改的宣導期結束後,如果使用者的密碼仍使用 DES hashing,且存在有效的 sha256.passwd 的話,就以 sha256.passwd 的內容直接覆蓋 userec_t::passwd[],並刪除 sha256.passwd。否則萬一 sha256.passwd 存在的話,直接將之刪除。

此外,若以上任一階段被執行時,登入後會顯示相應訊息告知使用者說其密碼發生了什麼變化的話,可幫助使用者掌握其密碼的更新情況。

hungte commented 1 year ago

含有 non-alnum 的 char.

Latest NIST password guideline explicitly said don't add complexity restrictions. We need to support longer passwords, not enforcing special characters in the pw.