Java中的快速增量哈希

时间:2014-11-14 11:02:34

标签: java algorithm hash

我正在寻找哈希字符串的哈希函数。出于我的目的(在导入期间识别已更改的对象),它应具有以下属性:

  1. 可以使用增量,即我可以像这样使用它:

    Hasher h = new Hasher();
    h.add("somestring");
    h.add("another part");
    h.add("eveno more");
    Long hash = h.create();
    

    在不影响其他属性的情况下,或在整个过程中将字符串保留在内存中。

  2. 防止碰撞。如果我在余生中每天100万次比较来自不同字符串的两个哈希值,那么我发现碰撞的风险应该是可以忽略的。

  3. 它不一定能够抵御恶意企图创建冲突。

    我可以使用什么算法?在Java中存在自由实现的算法是首选。

    澄清

    1. 哈希不必很长。例如,字符串就可以了。

    2. 要散列的数据将来自文件或数据库,其中包含许多10MB或最多几GB的数据,这些数据将分发到不同的哈希值。因此,将完整的字符串保留在内存中并不是一种选择。

2 个答案:

答案 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;
        }
    }

你的观点:

  1. 快速

    由于SHA-256比其他两个慢,我仍然会快速调用所有三种方法。

  2. 可以在不影响其他属性的情况下使用增量,或者在整个过程中将字符串保留在内存中。

    我不能保证ShaHasher的属性,因为我理解它是基于块的,我缺少源代码。我建议最多只有一个块,哈希和一些内部状态保持不变。其他两个显然只存储对add()

  3. 的调用之间的部分哈希
  4. 防止碰撞。如果我在余生中每天100万次比较来自不同字符串的两个哈希值,那么我发生碰撞的风险应该是可以忽略的。

    对于每个哈希都存在冲突。给定良好的分布,哈希的比特大小是发生冲突的主要因素。 JavaHasher用于例如HashMap。 {{1}}并且似​​乎是&#34;无碰撞&#34;足以将相似的密钥分开相互远离。至于任何更深入的分析:做自己的测试或询问当地的安全工程师 - 抱歉。

  5. 我希望这是一个很好的起点,细节可能主要是基于意见的。

答案 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;
    }

}

您可以轻松调整字符串生成部分以测试不同的模式。