天天看點

《Java編碼指南:編寫安全可靠程式的75條建議》—— 指南13:使用散列函數存儲密碼

本節書摘來異步社群《java編碼指南:編寫安全可靠程式的75條建議》一書中的第1章,第1.13節,作者:【美】fred long(弗雷德•朗), dhruv mohindra(德魯•莫欣達), robert c.seacord(羅伯特 c.西科德), dean f.sutherland(迪恩 f.薩瑟蘭), david svoboda(大衛•斯沃博達),更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

程式以明文(未加密的文本資料)方式存儲密碼将會導緻密碼以多種方式被洩露的風險。盡管程式通常接收到的使用者密碼是明文的,但是程式應該確定密碼不以明文方式存儲。

一種可行的限制密碼暴露的技術是使用散列函數(hash function),它允許程式間接地比較輸入的密碼和原始密碼,而不需要以明文或可解密的方式存儲密碼字元串。這種方法最大限度地減少了密碼的暴露,而且沒有表現出任何實用上的缺點。

散列函數産生的值是散列值(hash value)或消息摘要(message digest)。散列加密過程在計算上可行,而它的逆向過程在計算上是不可行的。在實踐中,明文密碼可以編碼為散列值,但是散列值不能解碼為明文。要比較兩個密碼是否相等,需要對比它們的散列值是否相等。

總是為需要加密的密碼添加一個鹽(salt)是一個好的實踐。鹽是一個唯一的(通常是連續的)或随機生成的資料,與散列值存儲在一起。使用鹽有助于防止對散列值的蠻幹攻擊,提供的鹽需要足夠長,這樣才能生成足夠的熵(鹽值過短不能顯著緩解蠻幹攻擊)。每個密碼都應該有自己的鹽與之關聯。如果一個鹽被用于多個密碼,那麼兩個使用者将能夠看到他們的密碼是否相同。

對散列函數和鹽的長度的選擇,需要在安全性和性能之間做出權衡。通過選擇一個更強大的散列函數來增加有效蠻幹攻擊所需努力的同時,也增加了驗證密碼所需的時間。雖然增加鹽的長度可使蠻幹攻擊更為困難,但是卻需要占用額外的存儲空間。

java的messagedigest類提供了各種加密散列函數的實作。要避免使用有缺陷的函數,如消息摘要算法(message-digest algorithm,md5)。安全雜湊演算法sha-1和sha-2是由美國國家安全局維護的,目前它們被認為是安全的散列函數。在實踐中,許多應用程式使用sha-256,因為這個散列函數在被認為是安全的同時,還具有合理的性能。

下面的違規代碼示例使用對稱密鑰算法來加密和解密存儲在password.bin中的密碼。

import java.security.messagedigest;

import java.security.nosuchalgorithmexception;

public final class password {

 private void setpassword(string pass) throws exception {

  byte[] salt = generatesalt(12);

  messagedigest msgdigest = messagedigest.getinstance("sha-256");

  // encode the string and salt

  byte[] hashval = msgdigest.digest((pass+salt).getbytes());

  savebytes(salt, "salt.bin");

  // save the hash value to password.bin

  savebytes(hashval,"password.bin");

 }

 boolean checkpassword(string pass) throws exception {

  byte[] salt = loadbytes("salt.bin");

  byte[] hashval1 = msgdigest.digest((pass+salt).getbytes());

  // load the hash value stored in password.bin

  byte[] hashval2 = loadbytes("password.bin");

  return arrays.equals(hashval1, hashval2);

 private byte[] generatesalt(int n) {

  // generate a random byte array of length n

}<code>`</code>

即使攻擊者知道程式在存儲密碼時使用的是sha-256算法和一個12位元組的鹽,他也無法從password.bin和salt.bin中擷取實際的密碼。

盡管這種方式解決了上一個違規代碼示例中的解密問題,但是這個程式可能無意中就将密碼明文存儲在了記憶體中。java的字元串對象是不可變的,可以被java虛拟機複制和存儲在其内部。是以,java缺乏一種用于安全删除存儲在字元串中的密碼的機制。更多相關資訊參見指南1。

下面的合規解決方案通過使用位元組數組來存儲密碼,解決了上一個違規代碼示例中的問題。