我正在寻找哈希字符串的哈希函数。出于我的目的(在导入期间识别已更改的对象),它应具有以下属性:
快
可以使用增量,即我可以像这样使用它:
Hasher h = new Hasher();
h.add("somestring");
h.add("another part");
h.add("eveno more");
Long hash = h.create();
在不影响其他属性的情况下,或在整个过程中将字符串保留在内存中。
防止碰撞。如果我在余生中每天100万次比较来自不同字符串的两个哈希值,那么我发现碰撞的风险应该是可以忽略的。
它不一定能够抵御恶意企图创建冲突。
我可以使用什么算法?在Java中存在自由实现的算法是首选。
澄清
哈希不必很长。例如,字符串就可以了。
要散列的数据将来自文件或数据库,其中包含许多10MB或最多几GB的数据,这些数据将分发到不同的哈希值。因此,将完整的字符串保留在内存中并不是一种选择。
答案 0 :(得分:3)
哈希是一个明智的话题,很难根据你的问题推荐任何这样的哈希。您可能想在https://security.stackexchange.com/上提出这个问题,以获得有关某些用例中哈希值可用性的专家意见。
到目前为止我所理解的是,大多数哈希都是在核心中逐步实现的;另一方面,执行时间并不容易预测。
我向您展示了两个Hasher
实现,这些实现依赖于#34; Java中存在的自由实现"。这两种实现的构建方式都可以在调用String
之前随意拆分add()
并获得相同的结果,只要不更改其中字符的顺序:
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* Created for https://stackoverflow.com/q/26928529/1266906.
*/
public class Hashs {
public static class JavaHasher {
private int hashCode;
public JavaHasher() {
hashCode = 0;
}
public void add(String value) {
hashCode = 31 * hashCode + value.hashCode();
}
public int create() {
return hashCode;
}
}
public static class ShaHasher {
public static final Charset UTF_8 = Charset.forName("UTF-8");
private final MessageDigest messageDigest;
public ShaHasher() throws NoSuchAlgorithmException {
messageDigest = MessageDigest.getInstance("SHA-256");
}
public void add(String value) {
messageDigest.update(value.getBytes(UTF_8));
}
public byte[] create() {
return messageDigest.digest();
}
}
public static void main(String[] args) {
javaHash();
try {
shaHash();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace(); // TODO: implement catch
}
}
private static void javaHash() {
JavaHasher h = new JavaHasher();
h.add("somestring");
h.add("another part");
h.add("eveno more");
int hash = h.create();
System.out.println(hash);
}
private static void shaHash() throws NoSuchAlgorithmException {
ShaHasher h = new ShaHasher();
h.add("somestring");
h.add("another part");
h.add("eveno more");
byte[] hash = h.create();
System.out.println(Arrays.toString(hash));
System.out.println(new BigInteger(1, hash));
}
}
这显然是" SHA-256"可以用其他常见的哈希算法替换; Java提供了相当多的内容。
现在你呼叫Long
作为返回值,这意味着你正在寻找64位哈希。如果这真的是故意的,请查看What is a good 64bit hash function in Java for textual strings?的答案。接受的答案是JavaHasher
的略微变体,因为String.hashCode()
的计算基本相同,但溢出边界较低:
public static class Java64Hasher {
private long hashCode;
public Java64Hasher() {
hashCode = 1125899906842597L;
}
public void add(CharSequence value) {
final int len = value.length();
for(int i = 0; i < len; i++) {
hashCode = 31*hashCode + value.charAt(i);
}
}
public long create() {
return hashCode;
}
}
你的观点:
快速
由于SHA-256比其他两个慢,我仍然会快速调用所有三种方法。
可以在不影响其他属性的情况下使用增量,或者在整个过程中将字符串保留在内存中。
我不能保证ShaHasher
的属性,因为我理解它是基于块的,我缺少源代码。我建议最多只有一个块,哈希和一些内部状态保持不变。其他两个显然只存储对add()
防止碰撞。如果我在余生中每天100万次比较来自不同字符串的两个哈希值,那么我发生碰撞的风险应该是可以忽略的。
对于每个哈希都存在冲突。给定良好的分布,哈希的比特大小是发生冲突的主要因素。 JavaHasher
用于例如HashMap
。 {{1}}并且似乎是&#34;无碰撞&#34;足以将相似的密钥分开相互远离。至于任何更深入的分析:做自己的测试或询问当地的安全工程师 - 抱歉。
我希望这是一个很好的起点,细节可能主要是基于意见的。
答案 1 :(得分:1)
不打算作为答案,只是为了证明哈希碰撞比人类直觉更倾向于假设。
以下微小程序生成2 ^ 31 个不同的字符串,并检查它们的任何哈希是否发生冲突。它通过保持每个可能的哈希值的跟踪位(所以你需要> 512MB堆来运行它)来做到这一点,将每个哈希值标记为&#34;使用&#34;因为他们遇到了。这需要几分钟才能完成。
public class TestStringHashCollisions {
public static void main(String[] argv) {
long collisions = 0;
long testcount = 0;
StringBuilder b = new StringBuilder(64);
for (int i=0; i>=0; ++i) {
// construct distinct string
b.setLength(0);
b.append("www.");
b.append(Integer.toBinaryString(i));
b.append(".com");
// check for hash collision
String s = b.toString();
++testcount;
if (isColliding(s.hashCode()))
++collisions;
// progress printing
if ((i & 0xFFFFFF) == 0) {
System.out.println("Tested: " + testcount + ", Collisions: " + collisions);
}
}
System.out.println("Tested: " + testcount + ", Collisions: " + collisions);
System.out.println("Collision ratio: " + (collisions / (double) testcount));
}
// storage for 2^32 bits in 2^27 ints
static int[] bitSet = new int[1 << 27];
// test if hash code has appeared before, mark hash as "used"
static boolean isColliding(int hash) {
int index = hash >>> 5;
int bitMask = 1 << (hash & 31);
if ((bitSet[index] & bitMask) != 0)
return true;
bitSet[index] |= bitMask;
return false;
}
}
您可以轻松调整字符串生成部分以测试不同的模式。