聽說不能用明文存密碼,那到底該怎麼存?

Larry Lu
Starbugs Weekly 星巴哥技術專欄
10 min readApr 19, 2020

後端資料庫在儲存使用者的密碼時,不應該直接存明碼應該已經是常識了。雖然大家都覺得自己家資料庫很安全,絕對不可能被駭客入侵,但不怕一萬只怕萬一,而且真的出事也來不及了

所以今天要來說說怎麼超前部署,從最簡單的編碼開始探討各種儲存密碼的方式,看看要怎麼做到即使資料庫被駭了,使用者的密碼也不會洩漏出去

先備知識

這篇的內容會延續一個月前的〈一次搞懂密碼學中的三兄弟 — Encode、Encrypt 跟 Hash〉,如果還不清楚編碼、加密跟雜湊分別是什麼,建議先看完上一篇再回來會比較能理解

接著就要從編碼開始探討各種儲存密碼的方案可能會有什麼風險、怎麼做會更安全

方案 1 — 將密碼經過 Base64 編碼

不該用明文儲存密碼,那存編碼後的密碼呢?

譬如說某使用者 Larry 的密碼是 LarryIsSmart,經過 Base64 編碼後變成 TGFycnlJc1NtYXJ0Cg==,所有使用者的密碼存在資料庫內就像這樣

雖然經過編碼後的字串 TGFycnlJc1NtYXJ0Cg== 在人類眼裡就像亂碼一樣,但他其實是不安全的。尤其經過 Base64 後的結果很常都是 === 結尾,所以如果有一天資料庫真的洩漏出去了,駭客也會在第一時間就發現可以用 Base64 解開,然後在很短的時間內得到原本的密碼

如果換個編碼方式呢,譬如說 Base32、Base16 或是 UTF-7 這些比較少人知道的編碼?

也不行,雖然這些編碼演算法比較少人在用,但頂多騙騙不懂電腦的路人甲乙。真正的駭客很可能會使用各種方法嘗試要 decode 密碼,所以不管用哪一種編碼都是不安全的,結論就是不要使用編碼來儲存密碼

方案 2 —用 AES256 加密

那如果把使用者的密碼用加密起來勒,這樣是不是就比較不容易被破解?

下圖三個 user 的密碼是以 pa55w0rd 為 key 進行 AES256 加密後的結果,即便資料庫洩露出去了,如果不知道 key 的話是不可能破解的,所以會比單純編碼還要安全

但因爲使用者要登入時,後端必須確認使用者輸入的密碼加密後跟資料庫內的 password 是否符合,所以還是必須把 key 放在 server 上。既然是放在 server 上,那駭客就還是有機會拿到,你想想他都能偷到你家資料庫了,拿到加密用的 key 其實也不是太難的事情

而且這樣做還有另一個隱憂:因為公司內的員工可能會知道 key,所以就可以從資料庫得到使用者的密碼。如果你知道 Facebook 的工程師只要想要就能得到所有人的密碼,應該也不太放心吧,尤其很多人都在多個網站使用同樣的密碼

結論就是因為無法保證 key 的安全,所以不建議用加密的方式保存密碼

方案 3 — 將密碼用 SHA1 雜湊

我聽說雜湊是不可逆的,那用雜湊總可以了吧

沒錯!雜湊是不可逆的,下圖三個密碼就是經過 SHA1 雜湊過的結果,像 Luka 的密碼 hash 過後是 1785bf0ed0f6346210af2d64b310a99b4024ce44,而這串東西無法經由運算反推回原密碼

當使用者 Luka 要登入時,就把他輸入的密碼拿去經過 SHA1 雜湊,如果算出來的雜湊值跟資料庫內那一大串一樣,那就代表 Luka 有極高機率輸入了正確的密碼,所以就放行讓他登入

但可別忘記了,雖然雜湊值無法直接反推回原密碼,但你可以用電腦把長度為 8 的字串都用 SHA1 雜湊過一遍,譬如說從 00000000 一路算到 zzzzzzzz,然後建一個表

如下圖,因為 SHA1 的碰撞機率極低,如果計算的過程中找到某字串 hash 後剛好是 1785bf0ed0f6346210af2d64b310a99b4024ce44,那該字串 love1234 就非常有可能是 Luka 的密碼

雖然這聽起來很不可思議,真的要做也是一個大工程,但因為現在電腦的運算速度非常快,所以已經有人做過了。只要到 MD5Hashing.net 輸入你想要破解的雜湊值,甚至不需要告訴他是哪一種演算法,他就會幫你查表找出來

像我輸入 Luka 那一串 1785bf0ed0f6346210af2d64b310a99b4024ce44,就能知道他是 love1234 經過 SHA1 雜湊的結果,超厲害的

而且這個網站支援的雜湊演算法有超過六十種,包括大家最常用的 MD5SHA1SHA256Tiger128 等等,所以你換其他雜湊法也沒有用,只要駭客拿到密碼雜湊值,就可以得到原本的密碼

編碼、加密、雜湊這三個方法都被你講完了,結果都不安全,所以到底該怎麼辦啊

剛剛的 love1234 之所以可以被破解其實是因為他太簡單了,只要把長度為 8 的字串雜湊值都算過一遍就好。而且小寫字母加上數字也才 36 個字元,算一算 36⁸ 大約才 2.8 兆種組合,很快就可以建一個表出來

如果你的密碼又臭又長又亂、包含了大小寫甚至還有一些怪怪的字元,像是 -y]@7k[BSB@3m]r$.>"R,那以現在電腦的計算速度就還無法破解,因為長度 20 以內由數字、大小寫還有特殊字元組成的字串太多了,算到天荒地老都不見得能算出來

但身為網站的開發者,不太可能要求所有使用者都設這種密碼,畢竟這種密碼太難記,每天光忘記密碼就飽了。所以大部分的使用者密碼還是會像 love1234,甚至還有更簡單的 123456 或是 qwerty 這類的XD,有興趣可以看看維基百科上 前 25 個最常被使用的密碼

換言之,單純是把密碼進行雜湊也不夠安全了,因為大部分人使用的密碼都可以透過查表的方式快速破解

方案 4 — 加鹽後用 SHA1 雜湊

哦?聽起來只要密碼夠長、夠亂就沒問題了,那我能不能自己產生隨機字串,把使用者的密碼加長呢?

答案是可以,這就是所謂的 加鹽(Salt)。如果今天有一個新的使用者要註冊,這時我們的後端系統就隨機生成一個長度十的字串稱作 Salt,計算 Hash 時就把使用者輸入的密碼跟 Salt 合在一起

以下圖為例,小城 SmallTown 註冊時後端隨機生成的鹽是 h7.@-]%<#L,而他輸入的密碼是 helloworld,所以就計算 SHA1("helloworld" + "h7.@-]%<#L")會得到 f80533d9f6a59796080258d2d4a2e2ec548322d4

因為 salt 會存在資料庫裡面,所以當有天小城要登入時,就把他輸入的密碼加上資料庫內的 salt 雜湊,看會不會得到相同的雜湊值,如果相同的話代表小城輸入的密碼是對的

安全性方面,因為那一大串 f80533d...helloworldh7.@-]%<#L 經過 hash 後的結果,而 helloworldh7.@-]%<#L 本身也夠長夠亂,網路上的表根本不可能包含這個字串,所以把 f80533d... 那一大串丟到網站上也破解不出來,換句話說無法得知小城的密碼是 helloworld

但如果駭客真的拿到你家資料庫,代表他同時拿到每個人的 salt 和 hash value,雖然駭客無法像先前那樣直接查網路上的表,但他還是可以用自己的電腦算雜湊值,從 aaaaaaaaaazzzzzzzzzz 把每個字串都加上 h7.@-]%<#L 再做 hash 還是可以算出小城的密碼是 helloworld

雖然真的要算的話也是可以,但因為每個人的 salt 都不同,所以要把所有使用者的密碼都算出來的話可能需要租一台超級電腦來算。如果駭客得到使用者的密碼後無法獲得相對應的利益,那其實就是白花錢而已,所以很多駭客看到資料庫內的密碼有加鹽就會放棄了

換句話說,如果你是網站開發者,在儲存密碼時至少要加鹽再雜湊,而且隨機產生的 salt 盡量要含有特殊字元,這樣才能保證使用者的密碼安全

方案 5— 用 Bcrypt 慢雜湊演算法

如果覺得上述的加鹽雜湊還是不夠安全的話,這邊還有另一種更安全的方法:就是把 SHA1 換成像 Bcrypt 這類的慢雜湊演算法。雖然 Bcrypt 的名字裡面有個 crypt,但他並不是加密法,而是跟 SHA1 一樣是雜湊演算法,唯一的差別是他計算很慢

計算慢有什麼好處呢?前面有提到 SHA1 的雜湊值之所以可以被查表查出來,就是因為現今的電腦計算太快了,就連建個表反查也不需要太多時間

而 Bcrypt 則是可以透過設定疊代次數讓他變慢,以疊代五次的 Bcrypt 來說,他的計算速度大概比 SHA1 慢 1000 倍。也就是說,假如你原本用 SHA1 計算三天就能反查出所有使用者的密碼,現在卻要花大概八年的時間才可以

下圖是使用者的密碼經過 Bcrypt 的結果,hash 出來總共有 60 個字元,很長很長

當使用者要登入、註冊時也是跟先前一樣的做法,就把密碼直接丟進去 Bcrypt 雜湊,雖然雜湊的過程會比 SHA1 慢一千倍,但整體而言單次雜湊還是非常快的,所以並不會有什麼問題

而且用 Bcrypt 還有另一個好處,因為以後電腦的運算速度還會更快,這時只要把疊代次數設定得更高就好了,疊代次數每增加 1 需要的時間就變兩倍,所以只要疊代次數一直加上去,就非常非常難被破解

總結

以目前電腦的運算能力來說,身為網站的開發人員,至少要做到方案四的 加鹽後雜湊 才能保證使用者的密碼不會輕易被破解,但因為運算速度只會越來越快,有些雜湊法如 MD5 跟 SHA1 甚至可以用 GPU 來大幅加速運算。所以如果可以的話,建議還是用方案五的 Bcrypt 慢雜湊,才不會讓使用者的密碼暴露在風險之中

身為使用者,因為我們無法確定所有網站的後端都是用足夠安全的方式保存密碼,畢竟就連 Facebook 這麼大的公司都曾經存過明碼。所以唯一能做的就是把密碼設得複雜一點不同網站使用不同的密碼,並且用 KeePass 這類的密碼管理軟體,才不會被那些爛網站雷到

另外最後再提醒一下,在資安領域沒有所謂絕對的安全,你只能不斷提高攻擊者的成本,當那個成本高到攻擊者無法負荷時(像是破解一個密碼要租超級電腦連續計算十年),那就可以說是足夠安全了XD

延伸閱讀

--

--

Larry Lu
Starbugs Weekly 星巴哥技術專欄

我是 Larry 盧承億,傳說中的 0.1 倍工程師。我熱愛技術、喜歡與人分享,專長是 JS 跟 Go,平常會寫寫技術文章還有參加各種技術活動,歡迎大家來找我聊聊~