散列算法進行數據驗證與加密
散列算法
散列是信息的提煉,通常其長度要比信息小得多,且爲一個固定長度。加密性強的散列一定是不可逆的,這就意味着通過散列結果,無法推出任何部分的原始信息。任何輸入信息的變化,哪怕僅一位,都將導致散列結果的明顯變化,這稱之爲雪崩效應。散列還應該是防衝突的,即找不出具有相同散列結果的兩條信息。具有這些特性的散列結果就可以用於驗證信息是否被修改。
單向散列函數一般用於產生消息摘要,密鑰加密等,常見的有:
MD5(Message Digest Algorithm 5):
MD5的全稱是Message-Digest Algorithm 5(信息-摘要算法)。這種算法較爲古老,由於MD5的弱點被不斷髮現以及計算機能力不斷的提升,通過碰撞的方法有可能構造兩個具有相同MD5的信息,使MD5算法在目前的安全環境下有一點落伍。 |
SHA(Secure Hash Algorithm):
安全散列算法(英語:Secure Hash Algorithm,縮寫爲SHA)是一個密碼散列函數家族。能計算出一個數字消息所對應到的,長度固定的字符串(又稱消息摘要)的算法。且若輸入的消息不同,它們對應到不同字符串的機率很高。SHA家族的五個算法,分別是SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512,後四者有時並稱爲SHA-2。SHA-1在許多安全協議中廣爲使用,包括TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被視爲是MD5(更早之前被廣爲使用的散列函數)的後繼者。但SHA-1的安全性如今被密碼學家嚴重質疑。雖然至今尚未出現對SHA-2有效的攻擊,它的算法跟SHA-1基本上仍然相似;因此有些人開始發展其他替代的散列算法。 |
散列算法在信息安全方面的應用主要體現在以下的3個方面: 文件校驗、數字簽名和鑑權協議。
雖然散列算法並不是加密算法,由於擁有不可逆的特性也常用於密碼的加密保存。
干擾項 - 鹽(Salt)
相同的明文用同樣的加密方法(如MD5)進行加密會得到相同的密文。
如用MD5的方式加密“123456”,你總會得到密文“E10ADC3949BA59ABBE56E057F20F883E”。
那麼,當數據庫信息泄漏時,如果你的密碼設置的比較簡單,對方是很容易猜到你的密碼,或者通過彩虹表來破解你的密碼。
因此,你需要在明文中添加干擾項-鹽(Salt)。
對於只加密,但不解密的算法,如MD5,SHA1。我們需要把鹽和密文都存在數據庫中,用戶輸入密碼時,我們把用戶密碼和鹽組成新的明文,進行加密,然後得到密文,最後對比該密文是否與庫中密文匹配。
在生成鹽值時我們要注意一下幾點來提高破解難度:
1、不要使用太短的鹽值
如果鹽值太短,攻擊者可以構造一個查詢表包含所有可能的鹽值。
2、不要使用重複的鹽值
每次哈希加密都使用相同的鹽值是很容易犯的一個錯誤,這個鹽值要麼被硬編碼到程序裏,要麼只在第一次使用時隨機獲得。這樣加鹽的方式是做無用功,因爲兩個相同的密碼依然會得到相同的哈希值。攻擊者仍然可以使用反向查表法對每個值進行字典攻擊,只需要把鹽值應用到每個猜測的密碼上再進行哈希即可。
對於每個用戶的每個密碼,鹽值都應該是獨一無二的。每當有新用戶註冊或者修改密碼,都應該使用新的鹽值進行加密。並且這個鹽值也應該足夠長,使得有足夠多的鹽值以供加密。一個好的標準的是:鹽值至少和哈希函數的輸出一樣長;鹽值應該被儲存和密碼哈希一起儲存在賬戶數據表中。
3、不要將鹽值簡單的添加在前後
由於往往鹽與密文都是同時存儲在數據庫中的,所以很多情況都是鹽與祕文同時泄露。所以加鹽時最好不要過於簡單的將鹽直接加在明文前後。而是使用打散或多次插入的方式混入明文提高算法破解難度。
4、鹽值應該使用基於加密的僞隨機數生成器(Cryptographically Secure Pseudo-Random Number Generator – CSPRNG)來生成。
CSPRNG和普通的隨機數生成器有很大不同。物如其名,CSPRNG專門被設計成用於加密,它能提供高度隨機和無法預測的隨機數。我們顯然不希望自己的鹽值被猜測到,所以一定要使用CSPRNG。Java的java.security.SecureRandom類可以用來生成隨機數。
MessageDigest
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
MessageDigest示例
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 算法 MD5、SHA-1、SHA-256、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 算法 MD5、SHA-1、SHA-256、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秒的值。這樣,你的程序就可以儘可能保證安全,而又不影響到用戶體驗。
BCrypt示例
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
明文與密文匹配成功