【问题标题】:Get SHA1 of HexString like HashCalc does像 HashCalc 一样获取 HexString 的 SHA1
【发布时间】:2013-03-18 03:58:33
【问题描述】:

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”,它返回不同的结果。解决方案很可能包括改变我处理这些十六进制数字的方式。 --> 原来是真的

【问题讨论】:

  • 这可能会有所帮助stackoverflow.com/a/6706816/655756
  • 不幸的是,这个答案“几乎”没有意义。当我将aff 放入 HashCalc 时,它给了我一个错误“十六进制字符串的长度必须是偶数!”因为它需要两个十六进制数字来表示一个字节。另外,我不确定如何将该库添加到我的 NetBeans! DOH!
  • 也许散列 8 字节 000000000000009a 与散列 1 字节 9a 不同?
  • e8eef065fb7295044d65b305bab18a9a645d1abf9a 的正确哈希值作为字符串而不是十六进制数字....使用 HashCalc(上面有问题的链接)进行测试。它有两个选项“文本字符串”和“十六进制字符串”。我需要复制“十六进制字符串”而不是“文本字符串”的结果,即e8eef065fb7295044d65b305bab18a9a645d1abf
  • @LouisWasserman - 那是转折点。看我的回答。

标签: java hash hex sha1 sha


【解决方案1】:

免责声明

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

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


手动解决方案之一:
重要!这是我的第一个答案,但它仅用于从 文本字符串 中获取散列,而不是像 OP 所要求的那样从 hex 字符串 . 请参阅下面的更新):

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 found solution,下面的代码展示了从十六进制字符串而不是文本字符串获取SHA-1哈希。此外,它还提供了几种手动将字节数组转换为十六进制字符串的方法(只是为了好玩)。对不起,我有心情))

查看我的 cmetsmain 方法和不同的 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)

【讨论】:

  • 哈哈,不错!感谢您的信任! Tho,我真的很想感谢 Louis 和 Kan 为我指明了正确的方向。我只是把他们的想法拼凑在一起。而且,正如我之前所说,我没有写byteArrayToHexString。不久前我发现了一些关于它的问题,他们进行了速度测试,发现这个可能是最快的。由于速度在我的应用程序中很重要,因此无论如何我都必须对其进行速度测试。
  • 毫无疑问,对于所有这些有趣的对话,我会再给你一个 +1 ! (好吧,这证明我是个极客,谈论编程很有趣???什么?)
  • +1 进行扩展讨论!哈哈,是的,我告诉大家它就像面向程序员的FaceBook。 (但要好 10^234 倍……FaceBook 会出问题……在这里,我们解决它们)
  • @PimpTrizkit 你是对的。抱歉,Mark
  • 你错过了最简单的 bytesToHexString 方式 - DatatypeConverter.printHexBinary
【解决方案2】:

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

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

}

【讨论】:

  • 当您使用设置为“HexString”的 HashCalc 时,13cba177bcfad90e7b3de70616b2e54ba4bb107f 是正确的哈希值。如果您将 HashCalc 设置为“TextString”,那么您是正确的。这不会在密码/安全环境中使用。
  • @PimpTrizkit 当只使用md.update(new byte[]{-102})。如果您需要将十六进制字符串解码为字节数组,请使用DatatypeConverter.parseHexBinary("9a")
  • 谢谢!我发现了我的问题,您在这里对DatatypeConverter 的评论显然是我所需要的!但是,您的实际答案更多是 sha-1 哈希的演示......一般......而不是解决方案。
【解决方案3】:

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

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

这是最终代码,其大小与原始代码惊人地相同:

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'}; 移动到全局(或“在范围内向上一个”)使其更快。

【讨论】:

  • +1 用于找出解决方案。但它仍然存在一个问题。如果您将奇数长度的十六进制字符串传递给DatatypeConverter.parseHexBinary() 方法,它将抛出IllegalArgumentException。请参阅我的第二次更新以了解改进的解决方案以及将字节数组转换为十六进制字符串的几种手动方法(抱歉,没办法)
  • 这实际上不是问题。
猜你喜欢
  • 2021-04-26
  • 1970-01-01
  • 2020-11-01
  • 1970-01-01
  • 2013-03-21
  • 2021-08-23
  • 2011-09-04
  • 1970-01-01
  • 2016-08-02
相关资源
最近更新 更多