计算多部分数据的md5哈希值(多个字符串)

时间:2011-01-24 17:52:44

标签: java algorithm hash

我正在尝试创建多个字符串的[单个] md5哈希[在Java中]。 那是我想要的

md5(string1, string2, string3, ..., stringN)

目前我正在尝试将所有字符串与一些很少使用的分隔符(如#)连接起来。 那是

md5(string1#string2#...#stringN)

这看起来很hacky,我担心一些奇怪的字符串实际上有分隔符作为它的一部分。 最好的方法是什么?

11 个答案:

答案 0 :(得分:10)

这可能会更好:

md5(md5(string1) + md5(string2) + ... + md5(stringN))

它消除了分隔符问题,但很难说它有多好。

答案 1 :(得分:4)

如果分隔符是字符串的一部分,则无关紧要。您可能甚至不需要分隔符,因为您不会将连接的字符串分解为部分

答案 2 :(得分:3)

我之前遇到过类似的问题,我能想到的最好的解决方案是使用不可打字的ascii字符作为分隔符。看看“男子ascii”并选择一个。我最喜欢的是'\ a',它是“铃声”的ASCII符号。

答案 3 :(得分:3)

如果你想确保只是将文本从一个字符串移动到下一个字符串就不会发生冲突,我推荐这个方案:

md5(<len1>+str1+<len2>+str2...)

这里,len1是str1长度的固定长度表示。对于md5,最合适的是使用一个四字节的int值(假设你知道你不会有超过2 ** 31的字符串)。或者,使用“十进制长度#”,即(在Python表示法中)

md5(str(len(str1))+"#"+str(len(str2))+"#"+str2+...)

这不会通过将文本从一个字符串移动到另一个字符串来产生冲突,因为长度会发生变化。

答案 4 :(得分:2)

不要将它们分开。这是一种哈希方法:将它们分开是没有用的......

MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bytes = ...;
for (String toHash: stringsToHash) {
  md5.update(toHash.getBytes("UTF-8"));
}
md5.digest(bytes);

答案 5 :(得分:2)

对于字符串,我认为+ Coly Klein添加非类型字符的解决方案是最好的。

如果您想要一个适用于二进制数据的解决方案,或者您不确定该字符串是否包含这些字符,您可以使用递归哈希,例如:

md5(md5(str1)+md5(str2)+md5(str3)+...+)

取决于此解决方案可以要求大量资源的数据量(不久前我对一个程序进行了分析,发现97%的时间是计算sha1的,所以我必须警告你......)

答案 6 :(得分:2)

将所有答案放在一起,这是一个具有单一公共和静态方法的类,它以有效的方式解决提出的问题。随意评论,批评或使用此代码(公共领域和所有)...

import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * MD5Summer is a utility class that abstracts the complexity of computing
 * the MD5 sum of an array of Strings.
 * <p>
 * Submitted as an answer to the StackOverflow bounty question:
 * <a href="http://stackoverflow.com/questions/4785275/compute-md5-hash-of-multi-part-data-multiple-strings">
 * compute md5 hash of multi part data (multiple strings)</a>
 * <p>
 * This solution uses the 'fast' "byte[] to hex string" mechanism described here in
 * <a href="http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java">
 * Convert from byte array to hex string in java</a>.
 * <p>
 * The MD5 sum is always calculated by converting the inputStrings to bytes based on
 * the UTF-8 representation of those Strings. Different platforms using this class
 * will thus always calculate the same MD5sum for the same Java Strings.
 * <p>
 * Using a ThreadLocal for storing the MessageDigest instance significantly reduces the amount of time spent
 * obtaining a Digest instance from the java.security subsystem.
 * <p>
 * <i>Copyright - This code is released in to the public domain</i>
 */
public final class MD5Summer {

    /**
     * Calculate the MD5 sum on the input Strings.
     * <p>
     * The MD5 sum is calculated as if the input values were concatenated
     * together. The sum is returned as a String value containing the
     * hexadecimal representation of the MD5 sum.
     * <p>
     * The MD5 sum is always calculated by converting the inputStrings to bytes based on
     * the UTF-8 representation of those Strings. Different platforms using this class
     * will thus always calculate the same MD5sum for the same Java Strings.
     * 
     * @param values The string values to calculate the MD5 sum on.
     * @return the calculated MD5 sum as a String of hexadecimal.
     * @throws IllegalStateException in the highly unlikely event that the MD5 digest is not installed.
     * @throws NullPointerException if the input, or any of the input values is null.
     */
    public static final String digest(final String ...values) {
        return LOCAL_MD5.get().calculateMD5(values);
    }

    /**
     * A Thread-Local instance of the MD5Digest saves construct time significantly,
     * while avoiding the need for any synchronization.
     */
    private static final ThreadLocal<MD5Summer> LOCAL_MD5 = new ThreadLocal<MD5Summer>() {
        @Override
        protected MD5Summer initialValue() {
            return new MD5Summer();
        }   
    };

    private static final char[] HEXCHARS = "0123456789abcdef".toCharArray();
    private static final Charset UTF8 = Charset.forName("UTF-8");


    private final MessageDigest md5digest;

    /**
     * Private constructor - cannot create instances of this class from outside
     */
    private MD5Summer () {
        // private constructor making only thread-local instances possible.
        try {
            md5digest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            // MD5 should always be available.
            throw new IllegalStateException("Unable to get MD5 MessageDigest instance.", e);
        }
    }

    /**
     * Private implementation on the Thread-local instance.
     * @param values The string values to calculate the MD5 sum on.
     * @return the calculated MD5 sum as a String of hexadecimal bytes.
     */
    private String calculateMD5(final String ... values) {
        try {
            for (final String val : values) {
                md5digest.update(val.getBytes(UTF8));
            }
            final byte[] digest = md5digest.digest();
            final char[] chars = new char[digest.length * 2];
            int c = 0;
            for (final byte b : digest) {
                chars[c++] = HEXCHARS[(b >>> 4) & 0x0f];
                chars[c++] = HEXCHARS[(b      ) & 0x0f];
            }
            return new String(chars);
        } finally {
            md5digest.reset();
        }
    }

}

我已将此程序的结果与Linux的md5sum程序进行了比较,并进行了以下小测试:

public class MD5Tester {
//    [rolf@rolfl ~/md5data]$ echo "Frodo Baggins" >> frodo
//    [rolf@rolfl ~/md5data]$ echo "Bilbo Baggins" >> bilbo
//    [rolf@rolfl ~/md5data]$ cat frodo bilbo 
//    Frodo Baggins
//    Bilbo Baggins
//    [rolf@rolfl ~/md5data]$ cat frodo bilbo | md5sum 
//    a8a25988435405b9a62634c887287b40 *-
//    [rolf@rolfl ~/md5data]$ 


    public static void main(String[] args) {
        String[] data = {"Frodo Baggins\n", "Bilbo Baggins\n"};
        String md5data = MD5Summer.digest(data);
        System.out.println("Expect a8a25988435405b9a62634c887287b40");
        System.out.println("Got    " + md5data);
        if (!"a8a25988435405b9a62634c887287b40".equals(md5data)) {
            System.out.println("Data does not match!!!!");
        }
    }
}

答案 7 :(得分:2)

在附加字符串之前,您可以使用base64对String进行编码。 然后在md5函数中拆分你的字符串并解码它。 例如

public class MutilMd5 {

public static void main(String[] args) throws Base64DecodingException {
    String s1 = "12#3";
    String s2 = "#12345";

    multMd5(Base64.encode(s1.getBytes()) + "#" + Base64.encode(s2.getBytes()));

}

public static void multMd5(String value) throws Base64DecodingException {
    String md5 = "";
    String[] encodeStrings = value.split("#");
    if (encodeStrings != null) {
        for (String encodeString : encodeStrings) {
            System.out.println(new String(Base64.decode(encodeString.getBytes())));
            md5 = md5 + DigestUtils.md5Hex(encodeString);
        }
    }

    System.out.println(md5);
}

}

输出

12#3

12345

13094636ff02b51be53c496d04d39bc2375704c2e00da07d2c9acc7646b2a844

答案 8 :(得分:1)

您可以使用MessageDigest类生成代码。如果我是你,如果在预处理阶段我知道每个String的长度,我会将它们作为唯一的String传递。如果您传递的字符串具有不同的随机长度,我将逐个哈希,但我需要知道原始实体和收据实体是否同步良好,以了解它们的消息长度相互之间传递。

private static final char[] HEXADECIMAL = { '0', '1', '2', '3',
    '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

public  String hash(String stringToHash)  {
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] bytes = md.digest(stringToHash.getBytes());
        StringBuilder sb = new StringBuilder(2 * bytes.length);
        for (int i = 0; i < bytes.length; i++) {
            int low = (int)(bytes[i] & 0x0f);
            int high = (int)((bytes[i] & 0xf0) >> 4);
            sb.append(HEXADECIMAL[high]);
            sb.append(HEXADECIMAL[low]);
        }
        return sb.toString();
    } catch (NoSuchAlgorithmException e) {
        //exception handling goes here
        return null;
    }
}

答案 9 :(得分:1)

根据我的理解,您希望散列字符串列表,确保没有两个不同的列表给出相同的结果。这可以在不考虑哈希函数的情况下解决。

您需要一个函数String f(List<String> l),其中没有两个输入值产生相同的输出(从List<String>String的{​​{3}}函数。有了这个,您可以将输出提供给您的哈希函数,并确保在哈希函数本身确保不会发生冲突(注意MD5在几年前被破坏,因此它可能不是一个合适的选择)。以下是实现f的两种方法:

转换为字符集

的susbset

最直接的方法是将每个输入映射到不包含分隔符的字符串字符集的子集:

public static String hex(String s) {
    try {
        String o = "";
        for(byte b: s.getBytes("utf-8"))
        o += String.format("%02x", b&0xff);
        return o;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

public static String f(String... l) {
    if (l.length == 0) return "";
    String o = hex(l[0]);
    if (l.length == 1) return o;
    for (int i = 1; i < l.length; i++) o += "#" + hex(l[i]);
    return o;
}
f("a#","b") => 6123#62
f("a","#b") => 61#2362

长度前缀

这也很简单,但缺点是它不能被重写为在流中工作。

public static String prefix(String s) {
    return s.length() + "." + s;
}

public static String f(String... l) {
    if (l.length == 0) return "";
    String o = prefix(l[0]);
    if (l.length == 1) return o;
    for (int i = 1; i < l.length; i++) o += "#" + prefix(l[i]);
    return o;
}
f("a#","b") => 2.a##1.b
f("a","#b") => 1.a#2.#b

答案 10 :(得分:1)

由于您使用字符串作为输入,我们知道字符串不具有NULL字符,因此可以使用NULL字符作为所有字符串的分隔符。验证时检查输入是否为NULL字符。

md5(String1+NULL+String2+NULL+String3....)

节省多个Md5的时间。