繁体   English   中英

Java中的快速增量哈希

[英]Fast Incremental Hash in Java

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

  1. 快速

  2. 可以增量使用,即我可以这样使用:

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

    在整个过程中不会损害其他属性或将字符串保留在内存中。

  3. 防止碰撞。 如果我在余生中每天比较来自不同字符串的两个哈希值每天一百万次,则发生碰撞的风险应该可以忽略不计。

不必为了防止恶意尝试造成冲突而确保安全。

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

澄清

  1. 哈希不必很长。 例如一个字符串就好了。

  2. 要散列的数据将来自文件或数据库,该文件或数据库具有10MB或最多几GB的数据,这些数据将被分发到不同的哈希中。 因此,将完整的字符串保留在内存中并不是真正的选择。

哈希是一个明智的话题,很难根据您的问题推荐任何此类哈希。 您可能想在https://security.stackexchange.com/上问这个问题,以获得有关某些用例中哈希的可用性的专家意见。

到目前为止,我了解到的是,大多数散列都是在核心中增量实现的。 另一方面,执行时间并不容易预测。

我向您介绍两个依赖于“ Java中现有的免费实现”的Hasher实现。 两种实现的构造方式都可以在调用add()之前任意拆分String并获得相同的结果,只要不更改它们中字符的顺序即可:

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位哈希值。 如果这确实是故意的,请查看Java文本字符串中什么是好的64位哈希函数的答案 可接受的答案是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. 防止碰撞。 如果我在余生中每天比较来自不同字符串的两个哈希值每天一百万次,则发生碰撞的风险应该可以忽略不计。

    对于每个哈希,都有冲突。 如果分布良好,则哈希的位大小是冲突发生频率的主要因素。 JavaHasher用在例如HashMap并且似乎“无冲突”,足以将彼此相似的键分散开。 至于任何更深入的分析:请自行进行测试或咨询您当地的安全工程师-对不起。

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

并非旨在作为答案,仅是为了证明哈希冲突比人类直觉倾向于的可能性大得多。

下面的微型程序生成2 ^ 31个不同的字符串,并检查它们的任何哈希是否冲突。 它通过为每个可能的哈希值保留一个跟踪位(因此您需要> 512MB的堆来运行它),以在遇到每个哈希值时将其标记为“已使用”来实现。 这需要几分钟才能完成。

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

}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM