1

HashCalc在顶部有一个名为“数据格式”的字段,我将其切换为“HexString”,然后9a在数据字段中输入测试值。我在其上运行 SHA-1 哈希,答案是:13cba177bcfad90e7b3de70616b2e54ba4bb107f

(注意:在线哈希器会将“9a”作为字符串进行哈希处理,导致答案为e8eef065fb7295044d65b305bab18a9a645d1abf。这对于此应用程序是错误的)

现在,我需要将这种类型的散列嵌入到我的 Java 程序中。这是我到目前为止得到的(包装在 try/catch 中):

String ss = "9a";
ByteBuffer bb = ByteBuffer.allocate(8);  
byte[] ba = bb.putLong(Long.decode("0x"+ss).longValue()).array();
MessageDigest md = MessageDigest.getInstance("SHA-1");
String results = encodeHex(md.digest(ba));
System.out.println("sha:"+results);

但是,我的结果是E73C417858807239DD5BC30BA978C14D57F80834

我究竟做错了什么?

编辑:添加了十六进制标签,很明显数据必须是某种十六进制格式。因为 HashCalc 必须设置为“HexString”而不是“TextString”,它返回不同的结果。解决方案很可能包括改变我处理这些十六进制数字的方式。--> 原来是真的

4

3 回答 3

3

免责声明

OP(Pimp Trizkit)是找到正确解决方案的人。我只是使用他的解决方案(稍作改动)来展示结果并获得乐趣。版权所有)

此外,OP 提供的字节数组到十六进制字符串的转换算法比我的示例代码中的要快得多。请参阅的实施解决方案。
(有关详细信息,请阅读下面的评论)


手动解决方案之一:(
重要!这是我的第一个答案,但它仅用于从文本字符串中获取哈希,而不是从OP 要求的十六进制字符串中获取哈希值。请参阅下面的更新):

import java.security.MessageDigest;

public class TestHash {

    public static void main(String[] args) throws Exception {
        String password = "9a";

        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(password.getBytes());
        byte[] byteData = md.digest();
        // byte[] byteData = md.digest(password.getBytes());    // both updates and completes the hash computation

        // Method 1 of converting bytes to hex format
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < byteData.length; i++) {
            sb.append(Integer.toString((byteData[i] & 0xFF) + 0x100, 16).substring(1));
        }

        System.out.println("1) Hex format : " + sb.toString());

        // Method 2 of converting bytes to hex format
        StringBuffer hexString = new StringBuffer();
        for (int i = 0; i < byteData.length; i++) {
            String hex = Integer.toHexString(0xff & byteData[i]);
            // NB! E.g.: Integer.toHexString(0x0C) will return "C", not "0C"            
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        System.out.println("2) Hex format : " + hexString.toString());      
    }
}

输出:

1) Hex format : e8eef065fb7295044d65b305bab18a9a645d1abf
2) Hex format : e8eef065fb7295044d65b305bab18a9a645d1abf

更新

根据OP 找到的解决方案,这里是代码,展示了从十六进制字符串而不是文本字符串获取 SHA-1 哈希。此外,它还提供了几种手动将字节数组转换为十六进制字符串的方法(只是为了好玩)。对不起,我有心情))

有关重要时刻的解释,请参见main方法内部和不同方法内部的评论。bytesToHexString()

import java.security.MessageDigest;
import java.math.BigInteger;
import javax.xml.bind.DatatypeConverter;

public class TestHash3 {

    public static void main(String[] args) throws Exception {
        String hexString = "1234";

        /*
         * NB!
         * Before passing hex string to DatatypeConverter.parseHexBinary(),
         * we need to check if the hex sting is even-length, 
         * otherwise DatatypeConverter.parseHexBinary() will throw a
         * java.lang.IllegalArgumentException: hexBinary needs to be even-length
         */
        hexString = (hexString.length() % 2 == 0) ? hexString : "0" + hexString;
        byte[] bytes = DatatypeConverter.parseHexBinary(hexString);

        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] byteData = md.digest(bytes);

        System.out.println("1) SHA-1 hash for the hex string " + hexString + ": " +
                            bytesToHexString1(byteData));
        System.out.println("2) SHA-1 hash for the hex string " + hexString + ": " +
                            bytesToHexString2(byteData));
        System.out.println("3) SHA-1 hash for the hex string " + hexString + ": " +
                            bytesToHexString3(byteData));
        System.out.println("4) SHA-1 hash for the hex string " + hexString + ": " +
                            bytesToHexString4(byteData));
    }

    public static String bytesToHexString1(byte[] bytes) {
        StringBuffer hexBuffer = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            hexBuffer.append(Integer.toString((bytes[i] & 0xFF) + 0x100, 16).substring(1));
        }

        return hexBuffer.toString();
    }

    public static String bytesToHexString2(byte[] bytes) {
        StringBuffer hexBuffer = new StringBuffer(bytes.length * 2);
        for (byte b: bytes) {
            int n = b & 0xFF;   // casting to integer to avoid problems with negative bytes
            if (n < 0x10) {
                hexBuffer.append("0");
            }
            hexBuffer.append(Integer.toHexString(n));
        }

        return hexBuffer.toString();
    }       

    public static String bytesToHexString3(byte[] bytes) {
        StringBuffer hexBuffer = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String hexString = Integer.toHexString(0xff & bytes[i]);
            // NB! E.g.: Integer.toHexString(0x0C) will return "C", not "0C"            
            if (hexString.length() == 1) {
                hexBuffer.append('0');
            }
            hexBuffer.append(hexString);
        }

        return hexBuffer.toString();
    }

    public static String bytesToHexString4(byte[] bytes) {
        String hexString = new BigInteger(1, bytes).toString(16);

        /*
         * NB!
         * We need an even-length hex string to propely represent bytes in hexadecimal.
         * A hexadecimal representation of one byte consists of two hex digits.
         * If the value is less than 16 (dec), it is prepended with zero
         * E.g.:
         * 1  (byte)    ==> 01 (hex)    // pay attention to the prepended zero
         * 15 (byte)    ==> 0F (hex)
         * 16 (byte)    ==> 10 (hex)    // no need to prepend
         * 255(byte)    ==> FF (hex)
         *
         * BigInteger.toString(16) can return both even and odd-length hex strings.
         * E.g.:
         * byte[] bytes = {15, 16}  // two bytes
         * BigInteger(1, bytes).toString(16) will produce (NB!): f10
         * But we need (NB!): 0f10
         * So we must check if the resulting hex string is even-length,
         * and if not, prepend it with zero.
         */
        return ((hexString.length() % 2 == 0) ? hexString : "0" + hexString);
    }
}

输出:

1) SHA-1 hash for the hex string 1234: ffa76d854a2969e7b9d83868d455512fce0fd74d
2) SHA-1 hash for the hex string 1234: ffa76d854a2969e7b9d83868d455512fce0fd74d
3) SHA-1 hash for the hex string 1234: ffa76d854a2969e7b9d83868d455512fce0fd74d
4) SHA-1 hash for the hex string 1234: ffa76d854a2969e7b9d83868d455512fce0fd74d


顺便说一句,检查十六进制字符串内部是否为偶数长度,byteToHexString4()以便可以独立使用。


更新 2

用户@kan带来了另一种将字节数组转换为十六进制字符串的方法,非常简单的一种方法,是继 OP 方法之后第二快的方法。

DatatypeConverter.printHexBinary(byte[] val)

于 2013-03-27T21:30:59.630 回答
1

我使用一个小型实用程序类:

public abstract class Sha1Util
{
    private static final Charset UTF8 = Charset.forName("UTF-8");

    public static MessageDigest newSha1Digest()
    {
        try
        {
            return MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e)
        {
            throw new Error(e);
        }
    }

    public static void update(final MessageDigest digest, final String s)
    {
        digest.update(s.getBytes(UTF8));
    }

    public static String sha1sum(final MessageDigest digest)
    {
        return String.format("%040x", new BigInteger(1, digest.digest()));
    }
}

测试:

@Test
public void testSha1For9a()
{
    final MessageDigest md = SecUtil.newSha1Digest();
    SecUtil.update(md, "9a");// you could use several updates e.g. for salted passwords
    assertEquals("e8eef065fb7295044d65b305bab18a9a645d1abf", SecUtil.sha1sum(md));

}
于 2013-03-27T21:53:43.060 回答
1

我想到了!感谢Louis Wasserman在他对我的问题的评论中获得灵感。当我000000000000009a与 HashCalc 一起使用时,它返回的结果与我的函数相同!我意识到我在强制一定的数据长度( a 的长度long)。事实证明它需要是任意长度(就像 HashCalc ......正如我所说,我需要复制 HashCalc 行为,不仅仅是9a我们使用的小测试用例,而是所有可能的数据)。甚至比long. 所以这些其他解决方案在这里不起作用。

我通过将原始十六进制字符串转换为 char[] 数组然后将它们成对连接在一起并在循环中转换为一个字节,将每个字符串存储在传递给摘要的 byte[] 中来解决这个问题。这行得通!然后我看到Kan对他的回答的评论,并告诉我我只是在那里重新发明轮子。

所以这是最终的代码,它的大小与原始代码惊人的相同:

private String HexToSHA1(String ss) throws IllegalArgumentException {
    MessageDigest md = null;
    try { md = MessageDigest.getInstance("SHA-1"); }
    catch ( Exception e ) {}
    return byteArrayToHexString(md.digest(DatatypeConverter.parseHexBinary(ss)));
}

这运行得很快,不确定是否有更快的方法。当然,这也取决于您的byteArrayToHexString功能有多快。这是我的:

private String byteArrayToHexString(byte[] data) {
    char[] toDigits =  {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    int l = data.length;
    char[] out = new char[l << 1];
    for (int i = 0, j = 0; i < l; i++) {
        out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
        out[j++] = toDigits[0x0F & data[i]];
    }
    return new String(out);
}

而且它非常快。归功于某个地方的某人。我没写。

编辑:移动char[] toDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};到全局(或“在范围内”)使其更快。

于 2013-03-28T00:54:56.620 回答