常见哈希算法以及Hmac算法,BouncyCastle总结
常見哈希算法總結
(1)什么是哈希算法?
哈希算法(Hash)又稱摘要算法(Digest),它的作用是:對任意一組輸出數據進行計算,得到一個固定長度的輸出摘要。哈希算法的目的;為了驗證原始數據是否被篡改.
哈希算法最重要的特點是:
相同的輸入一定得到相同的輸出;
不同的輸入大概率得到不同的輸出.
? ? ? ? Java字符串的hashCode()就是一個哈希算法,它的輸入是任意字符串,輸出是固定的4字節int整數:
"hello".hashCode(); // 0x5e918d2 "hello, java".hashCode(); // 0x7a9d88e8 "hello, bob".hashCode(); // 0xa0dbae2f(2)哈希碰撞
概念:兩個不同的輸入得到了相同的輸出。
"AaAaAa".hashCode(); // 0x7460e8c0 "BBAaBB".hashCode(); // 0x7460e8c0"通話".hashCode(); // 0x11ff03 "重地".hashCode(); // 0x11ff03碰撞能不能避免?答案是不能。碰撞是一定會出現的,因為輸出的字節長度是固定的,String的hashCode()輸出是4字節整數,最多只有4294967296種輸出,但輸入的數據長度是不固定的,有無數種輸入。所以,哈希算法是把一種無限的輸入集合映射到一個有限的輸出集合,必然會產生碰撞。
碰撞概率的高低關系到哈希算法的安全性。一個安全的哈希算法必須滿足:
碰撞概率低;
不能猜測輸出。
不能猜測輸出是指:輸出的任意一個bit的變化會造成輸出完全不同,這樣就很難從輸出反推輸入(只能依靠暴力窮舉).
假設一種哈希算法有如下規律:
hashA("java001") = "123456" hashA("java002") = "123457" hashA("java003") = "123458"那么很容易從輸出123459反推輸入,這種哈希算法就不安全。安全的哈希算法從輸出是看不出任何規律的。
hashB("java001") = "123456" hashB("java002") = "580271" hashB("java003") = ???常見哈希算法
? 常見的哈希算法有:根據碰撞概率,哈希算法的輸出長度越長,就越難產生碰撞,也就越安全。
| 算法 | 輸出長度(位) | 輸出長度(字節) |
| MD5 | 128bits | 16bytes |
| SHA-1 | 160bits | 20bytes |
| RipeMD-160 | 160bits | 20bytes |
| SHA-256 | 256bits | 32bytes |
| SHA-512 | 512bits | 64bytes |
? ? ? Java標準庫提供了常用的哈希算法,并且有一套統一的接口。我們以MD5算法為例,看看如何對輸入計算哈希:
public class main{public static void main(String[] args){//創建一個MessageDigest實例;MessageDigest md=MessageDigest.getInstance("MD5");//反復調用updata輸入數據: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時,我們首先根據哈希算法獲取一個MessageDigest實例,然后,反復調用update(byte[])輸入數據.當輸入結束后,調用digest()方法獲得byte[]數組表示的摘要,最后,把它轉換成十六進制的字符串。
案例:讀取一張圖片調用digest()方法獲得byte[]數組表示的摘要,最后,把它轉換成十六進制的字符串。
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字節。SHA-`1是由美國國家安全局開發的,SHA算法實際上是一個系列,包括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) {// 創建一個MessageDigest實例:MessageDigest md = MessageDigest.getInstance("SHA-1");// 反復調用update輸入數據: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());} }哈希算法的用途
? ? ? ? 檢驗下載文件
因為相同的輸入永遠會得到相同的輸出,因此,如果輸入被修改了,得到的輸出就會不同。
? ? ? ? ?如何判斷下載到本地的軟件是原始的,未經篡改的文件?我們只需要自己計算一下本地文件的哈希值,再與官網公開的哈希值對比,如果相同,說明文件下載正確,否則,說明文件已被篡改。
存儲用戶密碼
? ? ? ? 哈希算法的另一個重要用途是存儲用戶口令。如果直接將用戶的原始口令存儲到數據庫中,會產生極大的安全風險:
? ? ? ? 數據庫管理員能夠看到用戶明文口令;
? ? ? ? 數據庫數據一旦泄露,黑客即可獲取用戶明文口令。
| username | password |
| bob | 123456789 |
| alice | sdfsdfsdf |
| tim | justdoit |
如何對用戶進認證?存儲用戶口令的哈希,系統計算用戶輸入的原始口令的MD5并與數據庫存儲的MD5對比,如果一致,說明口令正確,否則,口令錯誤。
這樣,數據庫管理員看不到用戶的原始口令。即使數據庫泄露,黑客也無法拿到用戶的原始口令。想要拿到用戶的原始口令,必須使用暴力窮舉的方法,一個口令一個口令的試,直到某個口令計算的MD5恰好等于指定值。
使用哈希口令時,還要注意防止彩虹表攻擊。什么是彩虹表:如果一個預先計算好的常用口令和他們的MD5的對照表,這個表就是彩虹表。如果用戶使用了常用口令,黑客從MD5一下就能反查到原始口令:
| 常用口令 | MD5 |
| hello123 | f30aa7a662c728b7407c54ae6bfd27d1 |
| ..... | ...... |
? ? ? ? 當然,我們也可以采取特殊措施來抵御彩虹表攻擊;對每個口令額外添加隨機數,這個方法稱之為加鹽(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算法的工具對象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算法,對應的就是Hmac MD5算法,它相當于加鹽的MD5
好處:
HmacMD5使用的key長度是64字節,更安全;
Hmac是標準算法,同樣適用于SHA-1等其他哈希算法;
Hmac輸出和原有的哈希算法長度一致。
可見,Hmac本質上就是把key混入摘要的算法。驗證此哈希時,除了原始的輸入數據,還要提供key.為了保證安全,我們不會指定key,而是通過Java標準庫的KeyGenerator生成一個安全的隨機key.
//獲取HmacMD5密鑰生成器 KeyGenerator keyGen=KeyGenerator.getInstance("HmacMD5"); //產生密鑰 SecretKey secreKey=keyGen.generateKey(); //打印隨機生成的密鑰: 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實例;
2.通過KeyGenerator創建一個SecretKey實例;
3.通過HmacMD5獲取Mac實例;
4.用SecretKey初始化Mac實例;
5.對Mac實例反復調用updata(byte[])輸入數據;
6.調用Mac實例的doFinal()獲得最終的哈希值
? ? ? ? 有了Hmac計算的哈希和SecretKey,我們想要驗證怎么辦?SecretKey不能從KeyGenerator生成,而是從一個byte[]數組恢復。
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標準庫沒有的一些算法,例如:RipeMD160算法。
用法
1.添加jar包至classpath
?java標準庫的java.seurity包提供了一種標準機制,允許第三方提供無縫接入。我們使用BouncyCastle提供的RipeMD160算法,需先把BouncyCastle注冊一下
public class Main {public static void main(String[] args) throws Exception {// 注冊BouncyCastle提供的通知類對象BouncyCastleProviderSecurity.addProvider(new BouncyCastleProvider());// 獲取RipeMD160算法的"消息摘要對象"(加密對象)MessageDigest md = MessageDigest.getInstance("RipeMD160");// 更新原始數據md.update("HelloWorld".getBytes());// 獲取消息摘要(加密)byte[] result = md.digest();// 消息摘要的字節長度和內容System.out.println(result.length); // 160位=20字節System.out.println(Arrays.toString(result));// 16進制內容字符串String hex = new BigInteger(1,result).toString(16);System.out.println(hex.length()); // 20字節=40個字符System.out.println(hex);} }總結
以上是生活随笔為你收集整理的常见哈希算法以及Hmac算法,BouncyCastle总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python正则去除换行符_删除换行符的
- 下一篇: 产品策划五:App界面设计风格