像HashCalc一样获取HexString的SHA1

问题描述 投票:1回答:3

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

java hash hex sha1 sha
3个回答
3
投票

免责声明

OP(Pimp Trizkit)是发现the right solution的人。我只是用他的解决方案(稍加改动)来展示结果并获得乐趣。版权所有)

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


其中一个手动解决方案: (重要!这是我的第一个答案,但它只是从文本字符串中获取哈希值,而不是像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

UPDATE

基于OP found solution,这里是代码,展示从十六进制字符串而不是文本字符串获取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

btw检查六角形字符串是否在byteToHexString4()内是偶数长度,因此可以单独使用。


更新2

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

DatatypeConverter.printHexBinary(byte[] val)


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

}

1
投票

我想到了!感谢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'};移动到全局(或“范围内的一个”)使其更快。

© www.soinside.com 2019 - 2024. All rights reserved.