散列是信息的提煉,一般其長度要比信息小得多,且爲一個固定長度。加密性強的散列必定是不可逆的,這就意味着經過散列結果,沒法推出任何部分的原始信息。任何輸入信息的變化,哪怕僅一位,都將致使散列結果的明顯變化,這稱之爲雪崩效應。散列還應該是防衝突的,即找不出具備相同散列結果的兩條信息。具備這些特性的散列結果就能夠用於驗證信息是否被修改。html
單向散列函數通常用於產生消息摘要,密鑰加密等,常見的有:java
MD5(Message Digest Algorithm 5):算法
MD5的全稱是Message-Digest Algorithm 5(信息-摘要算法)。這種算法較爲古老,因爲MD5的弱點被不斷髮現以及計算機能力不斷的提高,經過碰撞的方法有可能構造兩個具備相同MD5的信息,使MD5算法在目前的安全環境下有一點落伍。數據庫 |
SHA(Secure Hash Algorithm):apache
安全散列算法(英語:Secure Hash Algorithm,縮寫爲SHA)是一個密碼散列函數家族。能計算出一個數字消息所對應到的,長度固定的字符串(又稱消息摘要)的算法。且若輸入的消息不一樣,它們對應到不一樣字符串的機率很高。SHA家族的五個算法,分別是SHA-一、SHA-22四、SHA-25六、SHA-384,和SHA-512,後四者有時並稱爲SHA-2。SHA-1在許多安全協議中廣爲使用,包括TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被視爲是MD5(更早以前被廣爲使用的散列函數)的後繼者。但SHA-1的安全性現在被密碼學家嚴重質疑。雖然至今還沒有出現對SHA-2有效的攻擊,它的算法跟SHA-1基本上仍然類似;所以有些人開始發展其餘替代的散列算法。 |
散列算法在信息安全方面的應用主要體如今如下的3個方面: 文件校驗、數字簽名和鑑權協議。api
雖然散列算法並非加密算法,因爲擁有不可逆的特性也經常使用於密碼的加密保存。安全
相同的明文用一樣的加密方法(如MD5)進行加密會獲得相同的密文。oracle
如用MD5的方式加密「123456」,你總會獲得密文「E10ADC3949BA59ABBE56E057F20F883E」。less
那麼,當數據庫信息泄漏時,若是你的密碼設置的比較簡單,對方是很容易猜到你的密碼,或者經過彩虹表來破解你的密碼。dom
所以,你須要在明文中添加干擾項-鹽(Salt)。
對於只加密,但不解密的算法,如MD5,SHA1。咱們須要把鹽和密文都存在數據庫中,用戶輸入密碼時,咱們把用戶密碼和鹽組成新的明文,進行加密,而後獲得密文,最後對比該密文是否與庫中密文匹配。
在生成鹽值時咱們要注意一下幾點來提升破解難度:
一、不要使用過短的鹽值
若是鹽值過短,攻擊者能夠構造一個查詢表包含全部可能的鹽值。
二、不要使用重複的鹽值
每次哈希加密都使用相同的鹽值是很容易犯的一個錯誤,這個鹽值要麼被硬編碼到程序裏,要麼只在第一次使用時隨機得到。這樣加鹽的方式是作無用功,由於兩個相同的密碼依然會獲得相同的哈希值。攻擊者仍然可使用反向查表法對每一個值進行字典攻擊,只須要把鹽值應用到每一個猜想的密碼上再進行哈希便可。
對於每一個用戶的每一個密碼,鹽值都應該是獨一無二的。每當有新用戶註冊或者修改密碼,都應該使用新的鹽值進行加密。而且這個鹽值也應該足夠長,使得有足夠多的鹽值以供加密。一個好的標準的是:鹽值至少和哈希函數的輸出同樣長;鹽值應該被儲存和密碼哈希一塊兒儲存在帳戶數據表中。
三、不要將鹽值簡單的添加在先後
因爲每每鹽與密文都是同時存儲在數據庫中的,因此不少狀況都是鹽與祕文同時泄露。因此加鹽時最好不要過於簡單的將鹽直接加在明文先後。而是使用打散或屢次插入的方式混入明文提升算法破解難度。
四、鹽值應該使用基於加密的僞隨機數生成器(Cryptographically Secure Pseudo-Random Number Generator – CSPRNG)來生成。
CSPRNG和普通的隨機數生成器有很大不一樣。物如其名,CSPRNG專門被設計成用於加密,它能提供高度隨機和沒法預測的隨機數。咱們顯然不但願本身的鹽值被猜想到,因此必定要使用CSPRNG。Java的java.security.SecureRandom類能夠用來生成隨機數。
MessageDigest是java.security提供的一個散列算法類。
java se 8中支持如下類型的散列算法:
Algorithm Name | Description |
---|---|
MD2 | The MD2 message digest algorithm as defined in RFC 1319. |
MD5 | The MD5 message digest algorithm as defined in RFC 1321. |
SHA-1 SHA-224 SHA-256 SHA-384 SHA-512 |
Hash algorithms defined in the FIPS PUB 180-4. Secure hash algorithms - SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 - for computing a condensed representation of electronic data (message). When a message of any length less than 2^64 bits (for SHA-1, SHA-224, and SHA-256) or less than 2^128 (for SHA-384 and SHA-512) is input to a hash algorithm, the result is an output called a message digest. A message digest ranges in length from 160 to 512 bits, depending on the algorithm. |
API:https://docs.oracle.com/javase/8/docs/api/index.html
import org.apache.commons.codec.binary.Hex; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; /** * 散列算法(MessageDigest實現) */ public class HashAlgorithm { public static void main(String[] args){ HashAlgorithm hashAlgorithm = new HashAlgorithm(); //生成鹽值 String salt = hashAlgorithm.getSalt(); System.out.println("鹽值:" + salt); //使用明文生成散列值 String message = "ceshimima"; String hash = hashAlgorithm.getHash(message,salt,"SHA-512"); System.out.println("散列值:" + hash); //驗證實文與散列是否匹配 boolean b = hashAlgorithm.verification(message,salt,"SHA-512",hash); if(b){ System.out.println("明文與散列匹配成功"); }else{ System.out.println("明文與散列匹配失敗"); } } /** * 生成鹽值 * @return */ public String getSalt(){ //使用SecureRandom類生成僞隨機數做爲鹽值 SecureRandom random = new SecureRandom(); byte bytes[] = new byte[20]; random.nextBytes(bytes); String salt = Hex.encodeHexString(bytes); return salt; } /** * 給明文加鹽 * @param message 明文 * @return */ private String addSalt(String message,String salt){ //明文加在鹽值的中間,增長破解難度 StringBuilder saltb = new StringBuilder(salt); saltb.insert(15,message); return saltb.toString(); } /** * 生成散列 * @param message 明文 * @param salt 鹽值 * @param algorithm 算法 MD五、SHA-一、SHA-25六、SHA-512等 * @return */ public String getHash(String message,String salt,String algorithm){ //明文加鹽 String salt_message = addSalt(message,salt); try { //使用MessageDigest生成散列 MessageDigest md = MessageDigest.getInstance(algorithm); //添加明文 md.update(salt_message.getBytes()); //生成散列值 byte[] toChapter1Digest = md.digest(); return Hex.encodeHexString(toChapter1Digest); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } /** * 驗證實文與散列是否匹配 * @param message 明文 * @param salt 鹽值 * @param algorithm 算法 MD五、SHA-一、SHA-25六、SHA-512等 * @param hash 散列 * @return */ public boolean verification(String message,String salt,String algorithm,String hash){ //使用明文從新生成散列與散列值比較 String hash1 = getHash(message,salt,algorithm); boolean b = hash1.equals(hash); return b; } }
執行結果
鹽值:e4cf17d1437377256970dc80965f3c6e5e5d772f 散列值:d90cfa4a922b385e0c5f1816156b98895a0917334efd66379033ebb661ffbdd675fd860c634c41164965e8bf4e489563265404c955f528f3d81a3974ee997aed 明文與散列匹配成功
加鹽使攻擊者沒法採用特定的查詢表和彩虹錶快速破解大量哈希值,可是卻不能阻止他們使用字典攻擊或暴力攻擊。高端的顯卡(GPU)和定製的硬件能夠每秒進行數十億次哈希計算,所以這類攻擊依然能夠很高效。爲了下降攻擊者的效率,咱們可使用一種叫作密鑰擴展的技術。
這種技術的思想就是把哈希函數變得很慢,因而即便有着超高性能的GPU或定製硬件,字典攻擊和暴力攻擊也會慢得讓攻擊者沒法接受。最終的目標是把哈希函數的速度降到足以讓攻擊者望而卻步,但形成的延遲又不至於引發用戶的注意。
密鑰擴展的實現是依靠一種CPU密集型哈希函數。不要嘗試本身發明簡單的迭代哈希加密,若是迭代不夠多,是能夠被高效的硬件快速並行計算出來的,就和普通哈希同樣。應該使用標準的算法,好比PBKDF2或者bcrypt。
這類算法使用一個安全因子或迭代次數做爲參數,這個值決定了哈希函數會有多慢。對於桌面軟件或者手機軟件,獲取參數最好的辦法就是執行一個簡短的性能基準測試,找到使哈希函數大約耗費0.5秒的值。這樣,你的程序就能夠儘量保證安全,而又不影響到用戶體驗。
jBCrypt官網:http://www.mindrot.org/projects/jBCrypt/
import org.mindrot.jbcrypt.BCrypt; /** * 散列算法(BCrypt實現) */ public class BCryptHashAlgorithm { public static void main(String[] args){ BCryptHashAlgorithm hashAlgorithm = new BCryptHashAlgorithm(); //使用BCrypt算法加密 String password = "ceshimima"; String ciphertext = hashAlgorithm.encryption(password); System.out.println("密文:" + ciphertext); //驗證實文與密文是否匹配 boolean b = hashAlgorithm.verification(password, ciphertext); if(b){ System.out.println("明文與密文匹配成功"); }else{ System.out.println("明文與密文匹配失敗"); } } /** * 加密方法 * @param password 密碼明文 * @return 密碼密文 */ public String encryption(String password){ //建立鹽值(log_rounds表明重複處理的輪次。輪次越多破解難度越大,可是效率也越低。 // 其複雜度是指數級遞增,也就是傳4的時候是2的4次方,默認傳輸爲10的時候是2的10次方) String salt = BCrypt.gensalt(12); //生成密文 String hashed = BCrypt.hashpw(password,salt); return hashed; } /** * 驗證方法 * @param password 密碼明文 * @param ciphertext 密碼密文 * @return 是否一致 true 一致 false 不一致 */ public boolean verification(String password,String ciphertext){ //驗證實文與密文是否匹配 //BCrypt的鹽值是包含於密文之中的,因此不須要另外保存和傳遞鹽值 boolean b = BCrypt.checkpw(password, ciphertext); return b; } }
執行結果
密文:$2a$12$MpHWUewiJOHrLRyZAc5tReGFfo102cggHTKsqf4N/hFfr3XvMz8Oa 明文與密文匹配成功