當然在談安全。
前端安全是Web安全的一部分,常見的安全問題會有XSS、CSRF、SQL注入等,然而這些已經在程師界得到了相當高的重視并且有了很成熟的解決方案。 所以我們今天只談前端“加密”,一個部分人認為沒有意義的工作。 有爭議的事情總是那么因崔斯汀,接下來就讓我們談談前端傳輸中的數據“加密” 。
前端傳輸的數據我們應該用什么算法加密,如何組織整個加密過程呢? 一般有幾種做法:
JavaScript 加密后傳輸
瀏覽器插件內進行加密傳輸
Https 傳輸
在這里其實 HTTPS 是終極解決方案,所以文章對于仍在使用 HTTP 建站的小伙伴更有幫助。本文中將討論第一種方法,也就是使用 JavaScript "加密"。使用雙引號是因為這里面分為兩種手段,一是使用數據摘要進行消息雜湊,二是使用非對稱加密算法對明文進行加密。
嚴格意義來說第一種手段并非加密,而是一種信息摘要的應用,為了闡述方便下文統統使用加密一詞。在進行下文之前,需要簡單的介紹幾個概念:
一、哈希與加密
上圖中我們可以明顯看到哈希和加密是兩個不同的東西,主要有兩點不同:
哈希算法通常用于數據摘要,生成相同長度的文本。而加密算法生成的密文長度與明文長度有關。
哈希算法是不可逆的,而加密算法是可逆的。
在加密算法中又分為對稱加密(symmetric encryption)和非對稱加密(asymmetric encryption)。非對稱加密算法中,加密密鑰和解密密鑰是不同的,分為私鑰和公鑰,我們熟知的 RSA 就是一種非對稱加密算法。而對稱加密中,加密和解密都是用同一個密鑰,如 AES / DES。從性能上來說,非對稱加密相對于對稱加密要慢很多。在 HTTPS 中,認證過程使用了非對稱加密算法,非認證過程中使用了對稱加密算法。
二、前端加密的意義
在 HTTP 協議下,數據是明文傳輸,傳輸過程中網絡嗅探可直接獲取其中的數據。 如用戶的密碼和信用卡相關的資料,一旦被中間人獲取,會給用戶帶來極大的安全隱患。另一方面在非加密的傳輸過程中,攻擊者可更改數據或插入惡意的代碼等。HTTPS 的誕生就是為了解決中間人攻擊的問題,但如今 HTTPS 的使用情況在國內并不樂觀,基本是因為成本或者性能的考量。
Q:那么問題來了,如果仍然使用 HTTP 協議怎么樣一定程度保證用戶的數據安全?
A:前端加密,即在數據發送前將數據進行哈希或使用公鑰加密。如果數據被中間人獲取,拿到的則不再是明文。
設想在如今公共 WIFI 流行的今天,一旦某一個 AP 的流量被攻擊者劫持,如果密碼和用戶名都是明文,那么攻擊者將輕而易舉的使用這些資料進行 Replay 登錄。更糟糕的是很多用戶在不同的網站使用相同的賬號信息,用戶的隱私蕩然無存。作為網站服務提供者,網站設計和開發人員應該為用戶提供相對安全的服務,這是一種責任也是一種情懷。
另外如果前端使用哈希算法,不僅可以幫助后端分擔部分計算壓力,還可以增加撞庫成本。具體的討論將在下文繼續。
三、如何加密
前面說過使用 JavaScript 加密有兩種方式,一是使用哈希進行信息摘要,一種是使用非對稱加密。在國內的知名站點中,這兩種方式都有人使用。接下來我們分別來深入了解加密過程,探討下如何應對不同的場景。
1.哈希“加密”
為了避免傳輸過程中的明文風險,前端可以在發送敏感數據前對其加密,如密碼。由于哈希算法的不可逆性,中間人無法從截取的數據中得知用戶的密碼信息。但是這里有一個問題,攻擊者仍然可以使用拿到的哈希值進行直接登錄。使用前端加密如何避免中間人重放攻擊?帶著這個問題,我們回顧下后臺如何驗證用戶。
很多站點后臺對于用戶密碼處理也是用哈希算法,一方面因為不需要將密文解密成明文來比對密碼,另一方面是一旦加密算法和密鑰泄露,那么整個用戶資料庫就相當于明文存儲了。如果前端傳過來的是明文,那么在注冊時將其哈希,存入數據庫。登錄時,將密碼哈希和數據庫對應的數據比對,若一致則說明密碼正確。
如果我們使用 Salt 是否可以解決問題?如果前端傳過來的是加了 salt 的哈希值,我們需要后端存有同一份 salt ,用其和數據庫的加密密碼哈希,然后與前端傳過來的哈希值比對。兩個過程的對比見下圖:
很容易知道,如果 Salt 不是每次登陸不同,那么攻擊者仍可以使用加密后的密碼進行直接登陸,所以必須使用動態 Salt。動態 Salt 有很多方法,可以是動態的 Token,也可以直接使用現成的驗證碼。 這當中使用驗證碼是對后臺系統改動較小且效果不錯的,我們來看看怎么樣結合驗證碼進行前端加密。
2.結合驗證碼進行前端加密
前端先將密碼哈希,然后和用戶輸入的驗證碼進行哈希,得到的結果作為密碼字段發送給服務器。服務器先確認驗證碼正確,然后再進行密碼驗證,否則直接返回驗證碼錯誤信息。
這種實踐保證了密碼在傳輸過程中的資料安全,即使攻擊者拿到了數據也無法重放。圖形化驗證碼更是增加了難度。另一方面該實踐大大增加了撞庫的成本。
前端加密一定程度保障了傳輸過程中的資料安全,那么會不會有對兩端(客戶端和服務器)有安全幫助呢? 有幫助,使用一些前端加密手段,可以增加拖庫后的數據破解難度。但是之前介紹的驗證碼方法不具有這樣的功能,因為數據庫存的仍是明文密碼哈希后的結果,那么攻擊者可以繞過前端加密,可以直接暴力破解。
3.如何增加拖庫后的數據破解難度
使用怎么樣的前端加密方法可以增加拖庫后的數據破解難度?從兩個方面分別去思考,一是降低破解速度,二是需要前端加密結果影響數據庫的數據存儲。數據被暴力破解出來的時間與哈希算法速度負相關。
我們知道暴力破解即是使用相同的算法把常用的字符組跑一遍,如果結果相同那么就猜中明文了。如果算法越快,便能更快的破解。例如 MD5 加密一次耗費1微秒,那么攻擊者一秒鐘就可以猜大約 100萬個詞組。所以為了減慢破解速度,我們需要增加破解的時間,一個直接的方法就是使用更慢的算法,我們可以把常用的MD5算法替換為 bcrypt,PBKDF2 等慢算法。
對于需要前端加密結果影響數據庫的數據存儲,即為后端加密把前端加密結果當做明文密碼,那么存在庫里的密碼便是前端哈希加上后端加密的結果。由于慢的算法會增加服務器計算壓力,當我們把一部分哈希工作拿到前端,即減慢了加密速度,減輕了服務器壓力,也順帶完成了前端加密的工作。
加密速度減慢一定程度會降低用戶體驗,這也是一部分站點未啟用 https 的原因之一。但是因為我們的前端加密只會用在不常使用的登錄和注冊上,所以不會影響網站整體的體驗。
綜上,我們可以使用更慢的算法,加之視前端哈希結果為明文密碼,便可增加拖庫破解的成本。既然增加了破解的成本,撞庫的成本也同樣增加了。為了避免攻擊者先前使用前后端加密生成的新字典,我們需要加鹽,并不定期更新鹽值。下圖文是使用過期 Salt 的方法描述:
前端加密使用定期刷新的 Salt,哈希后生成密文,再經過后端加密后即為用于比對的密文。數據庫中的密碼哈希值跟著 Salt 進行變化,前后端需要有一套salt的更新機制。
回顧前端哈希的方法,解決了中間人攻擊重放、加大了拖庫破解的難度。雖然這些方法都不能完全保證用戶安全,但是作為站點服務者,應該視用戶為上帝嘛。這些方法也不過就是幾行代碼的事,但是卻能一定程度的避免了用戶賬號被盜等風險。
4.非對稱加密
上文中我們討論了前端的哈希加密以及應用的場景。對于十足的安全控來說,這樣的措施對于有心的攻擊者基本沒有用。那么我們可以采用更強的措施保障傳輸中的敏感數據安全。
之前有說過加密算法分為對稱加密和非對稱加密,因為前端的透明性,使用JavaScript來進行對稱加密是不可能的了,那么只剩下了非對稱加密這個選擇。經過筆者幾天的調研發現,某鵝某浪的部分登錄頁面采用非對稱加密對密碼加密。這些站點目前都還是使用 http 協議,這樣的安全措施一定程度保證了用戶的密碼安全。
5.總結
前端使用非對稱加密原理很簡單,前后端共用一套加密解密算法,前端使用公鑰對數據加密,后端使用私鑰將數據解密為明文。中間攻擊人拿到密文,如果沒有私鑰的話是沒辦法破解的。
可能有人會指出加密算法一旦被破解,這一套安全措施就沒用了。況且 JavaScript 的生成安全隨機數還是個問題,所以其實這是用不了 https 的權衡之計。在沒有 https 的情況下,使用 JavaScript 非對稱加密或者 插件加密通訊是比較好的替代方法,后者優于前者。
豈于安全,不只情懷
作為一個前端開發者,我們眼里不應該只充斥各種框架,更不能被旁人一句沒有意義的話給擊退。任何解決方案都有其適用的范圍,世界上根本就沒有銀彈,安全問題亦是如此。所以根據實際的情況采取符合自己的安全解決方案很重要,即使辦法不是 100% 有效,也不能放棄那 1% 可以為用戶保障安全的機會。