常见哈希算法以及Hmac算法,BouncyCastle总结
常見哈希算法總結(jié)
(1)什么是哈希算法?
哈希算法(Hash)又稱摘要算法(Digest),它的作用是:對(duì)任意一組輸出數(shù)據(jù)進(jìn)行計(jì)算,得到一個(gè)固定長度的輸出摘要。哈希算法的目的;為了驗(yàn)證原始數(shù)據(jù)是否被篡改.
哈希算法最重要的特點(diǎn)是:
相同的輸入一定得到相同的輸出;
不同的輸入大概率得到不同的輸出.
? ? ? ? Java字符串的hashCode()就是一個(gè)哈希算法,它的輸入是任意字符串,輸出是固定的4字節(jié)int整數(shù):
"hello".hashCode(); // 0x5e918d2 "hello, java".hashCode(); // 0x7a9d88e8 "hello, bob".hashCode(); // 0xa0dbae2f(2)哈希碰撞
概念:兩個(gè)不同的輸入得到了相同的輸出。
"AaAaAa".hashCode(); // 0x7460e8c0 "BBAaBB".hashCode(); // 0x7460e8c0"通話".hashCode(); // 0x11ff03 "重地".hashCode(); // 0x11ff03碰撞能不能避免?答案是不能。碰撞是一定會(huì)出現(xiàn)的,因?yàn)檩敵龅淖止?jié)長度是固定的,String的hashCode()輸出是4字節(jié)整數(shù),最多只有4294967296種輸出,但輸入的數(shù)據(jù)長度是不固定的,有無數(shù)種輸入。所以,哈希算法是把一種無限的輸入集合映射到一個(gè)有限的輸出集合,必然會(huì)產(chǎn)生碰撞。
碰撞概率的高低關(guān)系到哈希算法的安全性。一個(gè)安全的哈希算法必須滿足:
碰撞概率低;
不能猜測(cè)輸出。
不能猜測(cè)輸出是指:輸出的任意一個(gè)bit的變化會(huì)造成輸出完全不同,這樣就很難從輸出反推輸入(只能依靠暴力窮舉).
假設(shè)一種哈希算法有如下規(guī)律:
hashA("java001") = "123456" hashA("java002") = "123457" hashA("java003") = "123458"那么很容易從輸出123459反推輸入,這種哈希算法就不安全。安全的哈希算法從輸出是看不出任何規(guī)律的。
hashB("java001") = "123456" hashB("java002") = "580271" hashB("java003") = ???常見哈希算法
? 常見的哈希算法有:根據(jù)碰撞概率,哈希算法的輸出長度越長,就越難產(chǎn)生碰撞,也就越安全。
| 算法 | 輸出長度(位) | 輸出長度(字節(jié)) |
| MD5 | 128bits | 16bytes |
| SHA-1 | 160bits | 20bytes |
| RipeMD-160 | 160bits | 20bytes |
| SHA-256 | 256bits | 32bytes |
| SHA-512 | 512bits | 64bytes |
? ? ? Java標(biāo)準(zhǔn)庫提供了常用的哈希算法,并且有一套統(tǒng)一的接口。我們以MD5算法為例,看看如何對(duì)輸入計(jì)算哈希:
public class main{public static void main(String[] args){//創(chuàng)建一個(gè)MessageDigest實(shí)例;MessageDigest md=MessageDigest.getInstance("MD5");//反復(fù)調(diào)用updata輸入數(shù)據(jù):md.update("hello".getBytes("UTF-8"));md.updata("world".getBytes("UTF-8"));byte[] results=md.digest();StringBuilder sb=new StringBuilder();for(byte bite:results){sb.append(String.format("%02x",bite));}System.out.println(sb.toString());} }? ? ? ? 使用MessageDigest時(shí),我們首先根據(jù)哈希算法獲取一個(gè)MessageDigest實(shí)例,然后,反復(fù)調(diào)用update(byte[])輸入數(shù)據(jù).當(dāng)輸入結(jié)束后,調(diào)用digest()方法獲得byte[]數(shù)組表示的摘要,最后,把它轉(zhuǎn)換成十六進(jìn)制的字符串。
案例:讀取一張圖片調(diào)用digest()方法獲得byte[]數(shù)組表示的摘要,最后,把它轉(zhuǎn)換成十六進(jìn)制的字符串。
import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException;public class Test08 {public static void main(String[] args) {try {byte[] imageByteArray=Files.readAllBytes(Paths.get("c:\\test\\img\\cz.jpg"));MessageDigest digest=MessageDigest.getInstance("MD5");digest.update(imageByteArray);byte[] resultByteArray=digest.digest();StringBuilder result=new StringBuilder();for(byte bite:resultByteArray) {result.append(String.format("%02x", bite));}System.out.println(result);System.out.println(result.length());} catch (NoSuchAlgorithmException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}} }SHA-1
? ? ? ? SHA-1也是一種哈希算法,它的輸出是160 bits,即20字節(jié)。SHA-`1是由美國國家安全局開發(fā)的,SHA算法實(shí)際上是一個(gè)系列,包括SHA-0(已廢棄),SHA-1,SHA-256,SHA-512等。
? ? ? ? 在Java中使用SHA-1,和MD5完全一樣,只需要把算法名稱改為"SHA-1":
import java.security.MessageDigest;public class main {public static void main(String[] args) {// 創(chuàng)建一個(gè)MessageDigest實(shí)例:MessageDigest md = MessageDigest.getInstance("SHA-1");// 反復(fù)調(diào)用update輸入數(shù)據(jù):md.update("Hello".getBytes("UTF-8"));md.update("World".getBytes("UTF-8"));// 20 bytes: db8ac1c259eb89d4a131b253bacfca5f319d54f2byte[] results = md.digest(); StringBuilder sb = new StringBuilder();for(byte bite : results) {sb.append(String.format("%02x", bite));}System.out.println(sb.toString());} }哈希算法的用途
? ? ? ? 檢驗(yàn)下載文件
因?yàn)橄嗤妮斎胗肋h(yuǎn)會(huì)得到相同的輸出,因此,如果輸入被修改了,得到的輸出就會(huì)不同。
? ? ? ? ?如何判斷下載到本地的軟件是原始的,未經(jīng)篡改的文件?我們只需要自己計(jì)算一下本地文件的哈希值,再與官網(wǎng)公開的哈希值對(duì)比,如果相同,說明文件下載正確,否則,說明文件已被篡改。
存儲(chǔ)用戶密碼
? ? ? ? 哈希算法的另一個(gè)重要用途是存儲(chǔ)用戶口令。如果直接將用戶的原始口令存儲(chǔ)到數(shù)據(jù)庫中,會(huì)產(chǎn)生極大的安全風(fēng)險(xiǎn):
? ? ? ? 數(shù)據(jù)庫管理員能夠看到用戶明文口令;
? ? ? ? 數(shù)據(jù)庫數(shù)據(jù)一旦泄露,黑客即可獲取用戶明文口令。
| username | password |
| bob | 123456789 |
| alice | sdfsdfsdf |
| tim | justdoit |
如何對(duì)用戶進(jìn)認(rèn)證?存儲(chǔ)用戶口令的哈希,系統(tǒng)計(jì)算用戶輸入的原始口令的MD5并與數(shù)據(jù)庫存儲(chǔ)的MD5對(duì)比,如果一致,說明口令正確,否則,口令錯(cuò)誤。
這樣,數(shù)據(jù)庫管理員看不到用戶的原始口令。即使數(shù)據(jù)庫泄露,黑客也無法拿到用戶的原始口令。想要拿到用戶的原始口令,必須使用暴力窮舉的方法,一個(gè)口令一個(gè)口令的試,直到某個(gè)口令計(jì)算的MD5恰好等于指定值。
使用哈希口令時(shí),還要注意防止彩虹表攻擊。什么是彩虹表:如果一個(gè)預(yù)先計(jì)算好的常用口令和他們的MD5的對(duì)照表,這個(gè)表就是彩虹表。如果用戶使用了常用口令,黑客從MD5一下就能反查到原始口令:
| 常用口令 | MD5 |
| hello123 | f30aa7a662c728b7407c54ae6bfd27d1 |
| ..... | ...... |
? ? ? ? 當(dāng)然,我們也可以采取特殊措施來抵御彩虹表攻擊;對(duì)每個(gè)口令額外添加隨機(jī)數(shù),這個(gè)方法稱之為加鹽(salt):
? ? ? ? digest=md5(salt+inputPassword)
案例:
public class Test09 {public static void main(String[] args) {String password="wbjxxmy";String salt=UUID.randomUUID().toString().substring(0, 5);System.out.println(salt);try {//獲取SHA-1算法的工具對(duì)象MessageDigest digest=MessageDigest.getInstance("SHA-1");digest.update(password.getBytes());digest.update(salt.getBytes());byte[] resultByteArray=digest.digest();System.out.println(Arrays.toString(resultByteArray));System.out.println(resultByteArray.length);StringBuilder result=new StringBuilder();for(byte b:resultByteArray) {result.append(String.format("%02x", b));}System.out.println(result);System.out.println(result.length()); } catch (NoSuchAlgorithmException e) {// TODO Auto-generated catch blocke.printStackTrace();}} }?Hmac算法
? ? ? ? Hmac算法總是和某種哈希算法配合起來用的。例如:我們使用MD5算法,對(duì)應(yīng)的就是Hmac MD5算法,它相當(dāng)于加鹽的MD5
好處:
HmacMD5使用的key長度是64字節(jié),更安全;
Hmac是標(biāo)準(zhǔn)算法,同樣適用于SHA-1等其他哈希算法;
Hmac輸出和原有的哈希算法長度一致。
可見,Hmac本質(zhì)上就是把key混入摘要的算法。驗(yàn)證此哈希時(shí),除了原始的輸入數(shù)據(jù),還要提供key.為了保證安全,我們不會(huì)指定key,而是通過Java標(biāo)準(zhǔn)庫的KeyGenerator生成一個(gè)安全的隨機(jī)key.
//獲取HmacMD5密鑰生成器 KeyGenerator keyGen=KeyGenerator.getInstance("HmacMD5"); //產(chǎn)生密鑰 SecretKey secreKey=keyGen.generateKey(); //打印隨機(jī)生成的密鑰: byte[] keyArray=sereKey.getEncoded(); StringBuilder key=StringBuilder(); for(byte bite:keyArray){key.append(String.format("%02x",bite)); } System.out.println(key); //使用HmacMD5加密 Mac mac=Mac.getInstance("HmacMD5"); mac.init(secreKey);//初始化 mac.update("HelloWorld".getBytes("UTF-8")); byte[] resultArray=mac.doFinal();StringBuilder result=new StringBuilder(); for(byte bite:resultArray){result.append(String.format("%02x",bite)); } System.out.println(result);和MD5相比,使用HmacMD5的步驟是:
1.通過名稱HmacMD5獲取KeyGenerator實(shí)例;
2.通過KeyGenerator創(chuàng)建一個(gè)SecretKey實(shí)例;
3.通過HmacMD5獲取Mac實(shí)例;
4.用SecretKey初始化Mac實(shí)例;
5.對(duì)Mac實(shí)例反復(fù)調(diào)用updata(byte[])輸入數(shù)據(jù);
6.調(diào)用Mac實(shí)例的doFinal()獲得最終的哈希值
? ? ? ? 有了Hmac計(jì)算的哈希和SecretKey,我們想要驗(yàn)證怎么辦?SecretKey不能從KeyGenerator生成,而是從一個(gè)byte[]數(shù)組恢復(fù)。
byte[] keyArray = {70, 31, 31, 113, -75, 45, 5, 112, -32, -32, 57, 59, -77, -52, -22, -67, -115, -15, -32, -57, 94, -78, 58, -115, 76, -104, 41, -120, -21, 28, 123, -13, -79, -17, 18, 63, -45, -14, -43, -33, -126, -115, 76, -87, -123, -16, -109, -127, -113, -114, 19, 96, 69, 73, -2, -75, -66, -88, -10, -9, -14, 104, -97, -69};SecretKey secreKey = new SecretKeySpec(keyArray, "HmacMD5"); Mac mac = Mac.getInstance("HmacMD5"); mac.init(secreKey); // 初始化key mac.update("HelloWorld".getBytes("UTF-8")); byte[] resultArray = mac.doFinal();StringBuilder result = new StringBuilder(); for(byte bite:resultArray) {result.append(String.format("%02x", bite)); } System.out.println(result);BouncyCastle
提供了很多哈希算法和加密算法的第三方開源庫。它提供了Java標(biāo)準(zhǔn)庫沒有的一些算法,例如:RipeMD160算法。
用法
1.添加jar包至classpath
?java標(biāo)準(zhǔn)庫的java.seurity包提供了一種標(biāo)準(zhǔn)機(jī)制,允許第三方提供無縫接入。我們使用BouncyCastle提供的RipeMD160算法,需先把BouncyCastle注冊(cè)一下
public class Main {public static void main(String[] args) throws Exception {// 注冊(cè)BouncyCastle提供的通知類對(duì)象BouncyCastleProviderSecurity.addProvider(new BouncyCastleProvider());// 獲取RipeMD160算法的"消息摘要對(duì)象"(加密對(duì)象)MessageDigest md = MessageDigest.getInstance("RipeMD160");// 更新原始數(shù)據(jù)md.update("HelloWorld".getBytes());// 獲取消息摘要(加密)byte[] result = md.digest();// 消息摘要的字節(jié)長度和內(nèi)容System.out.println(result.length); // 160位=20字節(jié)System.out.println(Arrays.toString(result));// 16進(jìn)制內(nèi)容字符串String hex = new BigInteger(1,result).toString(16);System.out.println(hex.length()); // 20字節(jié)=40個(gè)字符System.out.println(hex);} }總結(jié)
以上是生活随笔為你收集整理的常见哈希算法以及Hmac算法,BouncyCastle总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python正则去除换行符_删除换行符的
- 下一篇: 产品策划五:App界面设计风格