天天看點

如何設計一個安全的賬戶系統

如何設計一個安全的賬戶系統

最近有個虛拟練習項目,涉及到系統安全保障的設計,于是對安全保障這塊做了一些更深入的了解。發現了很多有趣的東西,開闊了眼界。中間查了一些資料,于是我打算重新整理,用更加循序漸進,大家都能懂的方式,說一說如何設計一個安全的系統。

著名的安全事件

首先來看看最近幾年比較著名的拖庫撞庫後密碼洩露的事件:

  1. 2011年12月,國内最大的程式員社群 CSDN 遭拖庫,600萬個賬戶資訊洩露。
  2. 2014年3月,攜程旅行網的系統存技術漏洞,漏洞可能導緻使用者的姓名、身份證号碼、銀行卡類别、銀行卡卡号、銀行卡CVV碼以及銀行卡6位Bin洩露。
  3. 2014年5月,小米論壇涉及800萬使用者資訊遭洩露,資訊包括使用者名、密碼、注冊IP、郵箱等。
  4. 2014年12月,12306遭撞庫攻擊,13萬使用者資訊洩露,包括使用者賬号、明文密碼、身份證、郵箱等敏感資訊。
  5. 2015年10月,網易郵箱遭攻擊,近5億條使用者資訊被洩露,包括使用者名、密碼、密碼保護資訊、登陸IP以及使用者生日等多個原始資訊。

除了密碼洩露事件,資料被實體删除的事件也是發生:

2015年5月,攜程網及APP陷入癱瘓,資料庫遭實體删除疑似離職員工報複。

這麼多大公司大網站的系統都遭到攻擊,洩露使用者資訊,更别說其他小網站了。這些攻擊都可以從技術上來進行防範的,但是我們看到即使是大公司,安全方面也是那麼的薄弱。

防範方法

防範的方法簡單來說資料從使用者鍵盤敲出的那一刻,到伺服器背景存儲都要保持正确的姿勢。比如:

  1. 用正确的姿勢儲存密碼。
  2. 用正确的姿勢傳輸資料。
  3. 用正确的姿勢加密敏感資訊。
  4. 用正确的姿勢對資料進行備份和監控。

用正确的姿勢儲存密碼

這一步非常重要,也比較複雜。使用者在浏覽器裡輸入密碼,傳輸到伺服器端進行驗證,服務端将之前儲存的密碼資訊和使用者的輸入進行比對。

1. 低級錯誤:明文儲存密碼

安全性最低的是在服務端明文儲存使用者的密碼,一旦伺服器被入侵,資料被拖走(拖庫),所有使用者的密碼都直接的暴露在外面。這是最初級的做法,毫無安全性可言。假如你在一個網站或論壇注冊了一個賬号,該網站自動發了一封郵件告訴你注冊成功,裡面明文寫了你的密碼,趕緊把密碼改了然後再也不要通路這個網站。

2. 低級錯誤:可逆加密密碼

既然不能明文儲存密碼,那當然是加密儲存了。耍個小聰明,比如把密碼的字母倒着存,或者每個字母存後一個字母,或者進行異或混淆處理,表面上密碼看上去已經看不出來原始的密碼是什麼了,但實際上這個和明文儲存密碼并沒有本質差別,因為黑客既然可以入侵你的伺服器,自然可以拿到你的加密代碼,隻要按你的算法進行簡單的解密就可以得到原始密碼。

3. 錯誤方法:md5 加密密碼

在我還是一個初學者的時候,我已被告知不能用前兩種方式儲存密碼,當時的主流方法是使用 md5 加密密碼。(年代久遠,現在已絕非主流了。) md5 是一種不可逆的加密方法,即密碼被 md5 加密後是無法解密出原始密碼的,驗證密碼是否正确的方法是将使用者輸入的密碼 md5 加密後于資料庫裡儲存的 md5 機密後的結果進行比對。這樣,伺服器端在不知道真實使用者密碼的情況下也能對使用者密碼進行驗證了。

這是早期比較主流的做法,然而,這依然是非常不安全的。因為隻要枚舉所有短密碼進行 md5 加密,做成一個索引表,就能輕易的逆推出原始密碼。這種預先計算好的用于逆推加密散列函數的表就是“彩虹表”。随着“彩虹表”不斷變大,md5 的加密已經變得非常的不安全。2015年10月網易郵箱的使用者密碼洩露也被懷疑隻對密碼進行了 md5 加密。

4. 正确方法:加鹽 hash 儲存密碼

加鹽 hash 是指在加密密碼時,不隻是對密碼進行 hash ,而是對密碼進行調油加醋,放點鹽(salt)再加密,一方面,由于你放的這點鹽,讓密碼本身更長強度更高,彩虹表逆推的難度更大,也因你放的這點鹽,讓黑客進行撞庫時運算量更大,破解的難度更高。

如何進行加鹽就是一門很重要的學問了。md5 是一種 hash 算法,以下就拿 md5 來舉例。假如密碼是 123456 ,md5 的結果如下:

如何設計一個安全的賬戶系統

像 123456 這樣的簡單密碼,是很容易被逆推出來的。但是假如我們往簡單密碼裡加點鹽試試:

如何設計一個安全的賬戶系統

上面例子裡的 

#g5Fv;0Dvk

就是我們加的鹽。加完之後,密碼的強度更高了,彩虹表破解的難度加大了。或者進行加鹽兩次 md5 :

如何設計一個安全的賬戶系統

到這裡,你一定會有疑問,是不是把 md5 多做幾次,或者自定義一些組合的方式就更安全了。其實不是的,黑客既然能拿到資料庫裡的資料,也很有可能拿到你的代碼。

一個健壯的、牢不可破的系統應該是:

即使被拿走了資料和所有的代碼,也沒辦法破解裡面的資料。

這也是為什麼大家不必實作自己的加密算法,而是使用公開的加密算法的原因,比如:RSA、AES、DES 等等。既然無法保證加密代碼不被洩露,那就使用公開的加密算法,隻要保護好私鑰資訊,就算你知道我的加密方式也沒有任何幫助。

大部分情況下,使用 md5(md5(password) + salt) 方式加密基本上已經可以了:

如何設計一個安全的賬戶系統

其中,最關鍵的是 salt 從哪裡來? salt 該怎麼設定才能安全。有幾個重要的點:

  1. 不要使用固定不變的 salt。
  2. 每個使用者的 salt 都需要不同。
  3. salt 必須由服務端使用安全的随機函數生成。
  4. 用戶端運算需要的 salt 需要從服務端動态擷取。
  5. 用戶端加鹽 hash 的結果并不是最終服務端存盤的結果。

由于用戶端也需要執行加鹽 hash ,是以,salt 不能直接寫在用戶端,而是應該動态從服務端獲得。服務端生成随機的 salt 時,必須使用安全的随機函數,防止随機數被預測。

各語言安全的随機函數:

Platform CSPRNG
PHP mcrypt_create_iv, openssl_random_pseudo_bytes
Java java.security.SecureRandom
Dot NET (C#, VB) System.Security.Cryptography.RNGCryptoServiceProvider
Ruby SecureRandom
Python os.urandom
Perl Math::Random::Secure
C/C++ (Windows API) CryptGenRandom
Any language on GNU/Linux or Unix Read from /dev/random or /dev/urandom

就算 salt 值動态從服務端擷取,也有可能被中間人攔截擷取。同時,用戶端的加鹽 hash 的過程相當于是完全暴露的。一種更安全的做法是,用戶端使用 javascript 進行加鹽 hash,把結果傳到伺服器後,伺服器對結果再進行一次 

加鹽 hash

或者

加密 hash

(比如:HMAC) ,然後再和資料庫的結果進行比對。

如果需要達到更高的安全等級,可以考慮:

1. 使用更安全的 hash 函數用來抵抗碰撞攻擊,比如:SHA256, SHA512, RipeMD, WHIRLPOOL。

兩個不同的内容 hash 的結果可能相同,攻擊者在不知道真實密碼的情況下,使用其他密碼進行碰撞攻擊進而登入系統。使用更安全的 hash 函數可以減少這種情況的發生。

2. 可以使用一種大量消耗 cpu 的 hash 算法對抗暴力破解,比如PBKDF2 或者 bcrypt。

暴力破解就是枚舉所有可能的密碼進行嘗試驗證,使用大量消耗 cpu 的 hash 算法可以極大增加暴力破解的時間。

3. 比較加鹽 md5 結果時,使用時間恒定的比較函數。

在比較兩個字元串時,通常都一個字元一個字元進行比較,如果某個字元不比對就會立即傳回。攻擊者可以根據驗證的時間長短來判斷前幾位字元是否正确,然後逐漸修正最終得到正确的結果。

是以,在比較 hash 時,使用時間恒定的比較函數,可以讓攻擊者摸不着頭腦。比如下面這段代碼:

private static boolean slowEquals(byte[] a, byte[] b) { int diff = a.length ^ b.length; for(int i = 0; i < a.length && i < b.length; i++) diff |= a[i] ^ b[i]; return diff == 0; }
           

異或(^)操作可以用來判斷兩個字元是否相等,比如:

0 XOR 0 = 0 1 XOR 1 = 0 0 XOR 1 = 1 1 XOR 0 = 1
           

上面的函數枚舉每個字元進行異或判斷,然後将所有的結果取或運算,得到最終的結果,比較的時間是恒定的。

4. salt 的值不要和最終 hash 的結果存在同一個資料庫。

SQL 注入是常見的攻擊手段,被注入後資料庫裡的資料被暴露無遺。是以,應該将 salt 分開存儲,存到别的機器的資料庫裡,讓攻擊者拿不到 salt ,進而無法輕易破解資訊。

5. 最終存儲的結果使用基于 key 的 hash 函數,比如 HMAC。 key 從外部安全性極高的專屬服務中獲得。

有了這層加強,即使資料被拖庫,攻擊者也無法從 hash 的結果逆推回原始密碼。因為使用了加密的 hash 函數。基于 key 的 hash 函數隻是進行哈希運算時,除了傳入原始内容外,還需要傳入一個密鑰(key)。攻擊者沒有 key 幾乎不可能對資料進行解密。

key 可以儲存在極高安全性的通用的 key 管理系統,使用加密協定傳輸,對通路者進行驗證,隻允許特定的機器有權限通路。

用正确的姿勢傳輸資料

使用 HTTP 協定傳輸資料時,資料都是明文傳輸的,資料從發出到伺服器接收,中間可能被劫持,篡改。比如常見的 DNS 劫持,HTTP 劫持,中間人攻擊。

用正确的姿勢傳輸資料,目的就是為了保證傳輸的資料安全,簡單歸納為兩點:

  1. 需要確定進行通訊的服務端是官方的、正确的服務端,而不是跟一個假的服務端在通信。
  2. 確定資訊在網絡上傳輸時是加密的,隻有用戶端和服務端有能力對資料進行解密。
  3. 確定資訊在傳輸時不被篡改,或者資料被篡改時能立即發現。

1. 驗證服務端的合法性

《改變未來的九大算法》一書中提到了公鑰加密和數字簽名技術,這是進行安全通信的基礎技術保障。這裡涉及到了加密技術,先了解兩個最基礎的概念:

  1. 對稱加密:加密和解密時使用的是同一個密鑰。
  2. 非對稱加密:需要兩個密鑰來進行加密和解密:公開密鑰(public key,簡稱公鑰)和私有密鑰(private key,簡稱私鑰) ,公鑰加密的資訊隻有私鑰才能解開,私鑰加密的資訊隻有公鑰才能解開。

非對稱加密是實作驗證服務端合法性的基礎,常見的加密算法有 RSA、ECC等 。服務端生成一對公鑰和私鑰,公鑰是公開的所有人都知道,用戶端需要和服務端通信時,使用該公鑰進行資料加密,由于隻有真實合法的服務端才擁有對應的私鑰,所有隻有真實的服務端才能解密該資訊,然後傳回資料給用戶端時,使用用戶端自己生成的公鑰進行加密,這樣資料隻有對應的用戶端才能了解。

如何設計一個安全的賬戶系統

使用 HTTPS 時,數字證書裡包含了名稱和公鑰資訊,隻要認證該證書是合法的,并且對方能了解用該公鑰加密的資訊,就能确定是合法的服務端。

2. 確定通信的安全

既然使用非對稱加密的方式,可以保證雙方安全的通信,那是不是就一直使用非對稱加密傳輸資料就行了?理論上是可以的,但是非對稱加密的效率要比對稱加密的效率低很多。通常的做法是,通過非對稱加密的方法,協商出一個隻有雙方知道的對稱加密密鑰。

即使在不安全的通信環境下,也可以協商出一個隻有雙方才知道的對稱加密密鑰。在《改變未來的九大算法》一書裡,有一個經典的描述如何互動密鑰的例子(在所有溝通都是透明的情況下,如何協商出一個隻有你和阿諾德才知道的顔料顔色。):

如何設計一個安全的賬戶系統

ECDH就是基于上面原理設計的密鑰交換算法:

如何設計一個安全的賬戶系統

密鑰協商好後,雙方就可以使用該密鑰進行加密傳輸了,比如使用 AES 、 DES。

由于 ECDH 密鑰交換協定不驗證公鑰發送者的身份,是以無法阻止中間人攻擊。如果監聽者 Mallory 截獲了 Alice 的公鑰,就可以替換為他自己的公鑰,并将其發送給 Bob。Mallory 還可以截獲 Bob 的公鑰,替換為他自己的公鑰,并将其發送給 Alice。這樣,Mallory 就可以輕松地對 Alice 與 Bob 之間發送的任何消息進行解密。他可以更改消息,用他自己的密鑰對消息重新加密,然後将消息發送給接收者。

解決方法是,Alice 和 Bob 可以在交換公鑰之前使用數字簽名對公鑰進行簽名。

即使攻擊者不能解密傳輸的内容,但仍可以使用重播攻擊嘗試身份驗證或用于欺騙系統。重播攻擊是指攻擊者将資料包截取後,向目标主機重新發送一遍資料包。

防禦重播攻擊的方法主要有:

  1. 使用時間戳。資料包在一定時間範圍内才是有效的。
  2. 使用遞增的序号。收到重複的資料包時可以輕易的發現。
  3. 使用提問應答方式。收到資料包時可以判斷出來是否應答過。

HTTPS 正是使用了上述的原理,保證了通信的安全。是以,任何對安全有需求的系統都應該使用 HTTPS。如果是使用自有協定開發,比如 APP 或遊戲,應該使用上述的方法保障通信的安全。

用正确的姿勢加密敏感資訊

我們都知道,使用者的密碼不能明文儲存,而且要使用不可逆的加密算法,隻儲存最終的 hash 結果用來驗證是否正确。那使用者其他的敏感資訊呢?比如身份證、銀行卡、信用卡等資訊,該如何加密儲存而不被洩露呢?

對于身份證資訊,可以像密碼一樣隻儲存 hash 的結果,可以用于使用者輸入身份證号後進行驗證。假如需要給使用者顯示身份證資訊,隻需要儲存抹掉了幾位數字的身份證号。

假如你的系統涉及到支付,需要使用者的銀行卡,信用卡(卡号,CVV碼)等資訊時,必須遵循 PCI DSS(第三方支付行業資料安全标準)标準。PCI DSS 是由 PCI 安全标準委員會的創始成員(visa、mastercard、American Express、Discover Financial Services、JCB等)制定,力在使國際上采用一緻的資料安全措施,包括安全管理、政策、過程、網絡體系結構、軟體設計的要求的清單等,全面保障交易安全。

如果隻是銀行卡,還需要遵循 ADSS(銀聯卡收單機構賬戶資訊安全管理标準) 标準。

2014年3月攜程洩露使用者銀行卡資訊就是因為沒有遵循 PCI DSS标準。

用正确的姿勢對資料進行備份和監控

2015年5月的攜程資料被删事件,就是資料備份沒有做好的例子。資料備份是為了防止由于硬碟損壞或人為破壞導緻的資料丢失。主要措施有:磁盤 raid,實體備份(錄音帶庫),異地的邏輯備份。同時做好權限控制,并對通路記錄做好監控,及時發現問題,保留現場證據。

總結

本文總結了設計一個安全系統的基本原理和方法,并沒有舉出一個特定具體的方案,因為不同的系統對安全性的要求各有不同,設計者應該根據自身系統的特點進行具體設計。比如加鹽 hash 的具體實施方法,salt 值如何構成等等。

本文所述内容如有不實之處或者有争議的部分,歡迎交流指出。

附錄

常用的加密算法:

  1. 對稱加密:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES。
  2. 非對稱加密:RSA、ECC(橢圓曲線加密算法)、Diffie-Hellman、El Gamal、DSA(數字簽名用)
  3. Hash 算法:MD2、MD4、MD5、HAVAL、SHA-1、SHA256、SHA512、RipeMD、WHIRLPOOL、SHA3、HMAC

DES、3DES、AES 差別:

  1. DES:1976年由美國聯邦政府的國家标準局頒布,密鑰為 56 位。
  2. 3DES:DES加密算法的一種模式,它使用3條56位的密鑰對資料進行三次加密。
  3. AES:進階加密标準,是下一代的加密算法标準,速度快,安全級别高,用來替代原先的DES。密鑰長度可以是128,192或256比特。