不是秘密的秘密


如果使用的網站服務需要以密碼登入,而且有忘記密碼的功能,試著使用看看,如果它發送給你的郵件或簡訊通知上,確實地寫著當初你設定的密碼,就可以寫信去罵人了!

  • 加密雜湊演算

若使用明碼儲存密碼,就意謂著若是資料庫被駭入,或者是被 SQL Injection 撈出了使用者資料,那在使用者察覺之前,攻擊者就可以登入系統通行無阻了,別以為這應該是 FAQ 等級的常識,不久前國內就有網路銀行,還將明文的密碼寫到 JavaScript 裏,發送至使用者的瀏覽器。

有個「我的密碼沒加密」網站,收錄了不少在密碼處理上有問題的服務;2018 年 5 月也發生過家長監控軟體 TeenSafe 在 AWS 上的伺服器,存放用戶資料並未設定密碼防護,而且以明碼儲存密碼;有時甚至是系統漏洞問題,導致密碼沒加密就直接儲存,像是 Twitter 在 2018 年 5 月發生的加密漏洞問題。

在密碼防護方面,不儲存明碼的最基本方式是,將密碼進行加密雜湊(Cryptographic hash)演算,過去常見的是使用 MD5 產生訊息摘要(Message digest)並儲存,而不是儲存原始密碼;在使用者登入時,輸入的密碼會經由相同的加密雜湊演算,再與儲存的摘要進行比對,若相同表示密碼正確。

不過,MD5 生成速度快,這意謂著在今日高速的處理器運算下,就算是暴力攻擊或字典攻擊,也可能能迅速被破解;MD5 並沒有避免碰撞,這意味不同密碼有可能生成相同的雜湊值,令查表法攻擊的可能性提高,網路上甚至有許多的彩虹表(Rainbow Tables),為有心人士做出的密碼與雜湊比對表,網路上搜尋「md5 crack」,也可以找到許多破解MD5的服務。

在過去,黑客曾經在暗網出售 Yahoo 的使用者資料,雖然密碼經 MD5 處理,不過在當時就認為與明碼儲存無異;就算是 SHA-1 也不見得安全,也是可使用彩虹表來破解,雖然 SHA 的碰撞機率比 MD5 來得小,然而 Google 曾於 2017 年達成了 SHA-1 碰撞,雖然運算成本高昂,然而處理器運算能力等的增加,這樣的運算成本在未來將會越來越低。

  • 加鹽雜湊演算

彩虹表破解密碼的概念就是查表,如果能讓相同的密碼經過雜湊演算時,產生不同的結果,就可以避免彩虹表方式來破解密碼,因此有人想出了,在儲存密碼時產生一個隨機鹽值(Salt),將鹽值混入密碼後進行雜湊,因為每次的鹽值都是隨機的,產生的摘要就會不同。

為了能在使用者登入時確認密碼是否正確,鹽值也必須儲存下來,在使用者登入時取得各自的鹽值,混入使用者輸入的密碼並進行雜湊,將結果與儲存下來的摘要比對,以確認密碼是否正確。

加鹽雜湊時使用的鹽值不應該重複使用,更不要以使用者名稱、ID或者是手機號碼等來作為鹽值,因為若有使用者具備相同的密碼,那麼加鹽雜湊後的結果也會是相同的,攻擊者可以試著事先製作出具有相同摘要的密碼,然後反向查詢,找出具有相同摘要的使用者,這是因為許多使用者都會使用相同的密碼,使得這類攻擊手法得以生效。

應該在每次儲存新的密碼時產生新的鹽值,加鹽雜湊時使用的鹽值也不能太短,若是太短,攻擊者就可以事先做出全部鹽值的列表;不要使用普通的隨機數演算來產生鹽值,建議使用和雜湊演算輸出的摘要等長的鹽值,並使用安全偽隨機數生成器(Cryptographically Secure Pseudo-Random Number Generator),像是 Java 的 SecureRandom

  • 慢得剛好的雜湊演算

有人會對密碼進行多次的雜湊演算,甚至是多次混合不同的雜湊演算組合,試圖讓攻擊者不知道系統使用哪種雜湊組合,是採取這個動作的目的之一,然而在〈Salted Password Hashing - Doing it Right〉就提到,如果攻擊者取得了原始碼(像是開放原始碼),就有可能找出密碼與摘要間的對應關係,反向推出演算的過程;雜湊演算組合的另一個理由是,可以讓破解過程變慢,希望慢到令攻擊者會想放棄。

加密雜湊演算本身可以很快地產生摘要,而由於現代的運算設備速度越來越快,使得攻擊者就算是採用字典攻擊或暴力破解密碼也成為可能,為此,增加運算的成本,讓破解過程變慢也確實是防禦的一種策略,不過相較於自行使用多重雜湊演算組合來試圖變慢,有些演算就是為了讓加密運算的速度變慢而設計出來的,像是 BCrypt、PBKDF2、SCRYPT 等。

這類演算通常會有個參數決定如何減緩摘要的產生過程,然而犧牲的就是效能,找出相對適合的參數,也就是對使用者不會造成差異,然而能在暴力破解時可極度增大成本,就是這類演算的目的。

以目前廣為被使用的BCrypt為例,可以透過cost參數來決定,而且也使用一個隨機加鹽的流程以防禦彩虹表攻擊,就算是相同的密碼,因為每次產生的鹽值不同,編碼後的結果也就不會相同;試圖讓攻擊者不知道系統使用哪種雜湊組合其實是沒必要的,例如BCrypt編碼後的特徵是很明確的,像是 $2a$10$yh5WJetawp2KloUtEoVzRuT4/WEeR5BhPdfRZGoAvnCtKAbFBP8Sa。

這一看就知道是 BCrypt 產生的編碼,$ 是分隔符號,2a 表示 BCrypt 版本,10 是成本參數(建議採更高的值),實際上當中還包括了編碼時產生的鹽值;像這類 BCrypt 演算都是公開的秘密,因為這類演算在設計上,目的就是在知道演算過程下也難以破解,像 Spring Security 甚至還建議在儲存密碼時,加上 {bcrypt}、{pbkdf2} 之類的前置,以便於資料庫表格間的轉換遷移。

  • 非專家也要有的基本認識

密碼處理上的重要性,從 Spring Security 5 以後,強制一定得選用某個密碼編碼器來處理、儲存密碼可見一般,不過就算是這樣,還是有人試圖用自定的 PasswordEncoder,完全不處理編碼來混過去,足見對密碼處理上基本認知的不足。

實際上,在觀察一些新聞時也可以察覺密碼相關的問題,例如國內曾有新聞報導,有婦人在半年內,於購物網站購買 225 次不取貨,而該購物網站清查之後,發現當事人雖申請的帳號不同,但使用的密碼卻都一樣,你覺得這代表著什麼呢?

雖然並非密碼學專家,然而,開發者對密碼也要有基本的認識,特別是這陣子以來對密碼處理的歷史演化過程,上列的〈Salted Password Hashing - Doing it Right〉值得一看,另一方面〈用户密碼加密存儲十問十答〉也值得參考。