下面的英文描述了String.hashCode()方法,在特定情況下,返回值為0的問題:
Java offers the HashMap and Hashtable classes, which use the
String.hashCode() hash function. It is very similar to DJBX33A (instead of 33, it uses the
multiplication constant 31 and instead of the start value 5381 it uses 0). Thus it is also
vulnerable to an equivalent substring attack. When hashing a string, Java also caches the
hash value in the hash attribute, but only if the result is different from zero.
Thus, the target value zero is particularly interesting for an attacker as it prevents caching
and forces re-hashing.
接下來我們來看一下String類的hashCode()方法:當下面代碼中的val[off++]返回值都是0的情況下,hashCode()的返回值也是0
public int hashCode() { int h = hash;//初始值為0 if (h == 0 && count > 0) {//count值為字符個數 int off = offset;//off值為0 char val[] = value;//字符數組 int len = count; for (int i = 0; i < len; i++) { h = 31*h + val[off++];//如果val[off++]的所有返回值都是ascii碼0會發生什么? } hash = h; } return h; }
我們知道hashmap存儲值的數據結構是數組+鏈表的結果,如果不同的key值,但是返回的hashcode()值都是0的話,hashmap的結構不會得到很好的應用,會造成所有的元素都存儲在數組的第一個元素的鏈表中,下面通過代碼來證明:
package com.mantu.advance; import java.util.HashMap; public class Lesson10HashmapLeak { public static void main(String[] args){ testHashMapNormal(); testHashMapBug(); } public static void testHashMapBug(){ HashMap<String,String> map = new HashMap<String,String>(100000); String xxx= asciiToString("0"); String temp = xxx; long beginTime = System.currentTimeMillis(); //System.out.println("開始時間:"+System.currentTimeMillis()); for(int i=0;i<100000;i++){ map.put(xxx, i+""); if((i%10000)==0){ xxx=temp; } else{ xxx=xxx+temp; } } System.out.println("testHashMapBug()耗時:"+(System.currentTimeMillis()-beginTime)+"毫秒"); } public static void testHashMapNormal(){ HashMap<String,String> map = new HashMap<String,String>(100000); String xxx= asciiToString("1"); String temp = xxx; long beginTime = System.currentTimeMillis(); //System.out.println("開始時間:"+System.currentTimeMillis()); for(int i=0;i<100000;i++){ map.put(xxx, i+""); if((i%10000)==0){ xxx=temp; } else{ xxx=xxx+temp; } } System.out.println("testHashMapNormal()耗時:"+(System.currentTimeMillis()-beginTime)+"毫秒"); } public static String asciiToString(String value) { StringBuffer sbu = new StringBuffer(); String[] chars = value.split(","); for (int i = 0; i < chars.length; i++) { sbu.append((char) Integer.parseInt(chars[i])); } return sbu.toString(); } }
最后的執行結果是:
正常key值的一組執行時間是:1887毫秒
key值對應的hashcode()值為0的執行時間是:7365毫秒