169

我正在使用一些示例 java 代码来制作 md5 哈希。一部分将结果从字节转换为一串十六进制数字:

byte messageDigest[] = algorithm.digest();     
StringBuffer hexString = new StringBuffer();
for (int i=0;i<messageDigest.length;i++) {
    hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
    }

然而,由于 toHexString 显然去掉了前导零,所以它并不完全有效。那么,从字节数组到保持前导零的十六进制字符串的最简单方法是什么?

4

28 回答 28

138

Apache Commons Codec查看Hex.encodeHexString

import org.apache.commons.codec.binary.Hex;

String hex = Hex.encodeHexString(bytes);
于 2008-12-01T22:12:58.117 回答
116

你可以使用下面的那个。我用前导零字节和初始负字节测试了这个

public static String toHex(byte[] bytes) {
    BigInteger bi = new BigInteger(1, bytes);
    return String.format("%0" + (bytes.length << 1) + "X", bi);
}

如果您想要小写十六进制数字,请使用"x"字符串格式。

于 2009-06-03T10:09:07.433 回答
112

一种简单的方法是检查输出了多少位,Integer.toHexString()并在需要时为每个字节添加前导零。像这样的东西:

public static String toHexString(byte[] bytes) {
    StringBuilder hexString = new StringBuilder();

    for (int i = 0; i < bytes.length; i++) {
        String hex = Integer.toHexString(0xFF & bytes[i]);
        if (hex.length() == 1) {
            hexString.append('0');
        }
        hexString.append(hex);
    }

    return hexString.toString();
}
于 2008-12-01T20:32:19.893 回答
40

该方法javax.xml.bind.DatatypeConverter.printHexBinary()Java 架构 XML 绑定 (JAXB)byte[]的一部分,是一种将 a 转换为十六进制字符串的便捷方法。该类DatatypeConverter还包括许多其他有用的数据操作方法。

在 Java 8 及更早版本中,JAXB 是 Java 标准库的一部分。它在Java 9中被弃用,并在 Java 11中被删除,作为将所有 Java EE 包移动到它们自己的库中的努力的一部分。说来话长。现在,javax.xml.bind不存在,如果您想使用包含DatatypeConverter的 JAXB,您需要从 Maven安装JAXB APIJAXB Runtime 。

示例用法:

byte bytes[] = {(byte)0, (byte)0, (byte)134, (byte)0, (byte)61};
String hex = javax.xml.bind.DatatypeConverter.printHexBinary(bytes);

将导致:

000086003D
于 2013-01-27T21:50:58.610 回答
35

我喜欢史蒂夫的提交,但他可以在没有几个变量的情况下完成,并在此过程中节省了几行。

public static String toHexString(byte[] bytes) {
    char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    char[] hexChars = new char[bytes.length * 2];
    int v;
    for ( int j = 0; j < bytes.length; j++ ) {
        v = bytes[j] & 0xFF;
        hexChars[j*2] = hexArray[v/16];
        hexChars[j*2 + 1] = hexArray[v%16];
    }
    return new String(hexChars);
}

我喜欢的是它很容易准确地看到它在做什么(而不是依赖于一些神奇的 BigInteger 黑盒转换),而且你也不必担心诸如前导零之类的极端情况。该例程采用每个 4 位半字节并将其转换为十六进制字符。它使用表格查找,所以它可能很快。如果您将 v/16 和 v%16 替换为按位移位和 AND,它可能会更快,但我现在懒得测试它。

于 2010-02-04T06:03:37.043 回答
22

我发现 Integer.toHexString 有点慢。如果要转换许多字节,您可能需要考虑构建一个包含“00”..“FF”的字符串数组,并使用整数作为索引。IE

hexString.append(hexArray[0xFF & messageDigest[i]]);

这更快并确保正确的长度。只需要字符串数组:

String[] hexArray = {
"00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
"10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
"20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
"30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
"40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
"50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
"60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
"70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
"80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F",
"90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F",
"A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF",
"B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF",
"C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF",
"D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF",
"E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF",
"F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF"};
于 2009-06-15T17:15:58.290 回答
12

我一直在寻找同样的东西......这里有一些好主意,但我运行了一些微基准测试。我发现以下是最快的(从上面的 Ayman 修改,大约是 2 倍,比 Steve 的快大约 50%):

public static String hash(String text, String algorithm)
        throws NoSuchAlgorithmException {
    byte[] hash = MessageDigest.getInstance(algorithm).digest(text.getBytes());
    return new BigInteger(1, hash).toString(16);
}

编辑:哎呀-错过了这与kgiannakakis的基本相同,因此可能会去掉前导0。不过,将其修改为以下内容,它仍然是最快的:

public static String hash(String text, String algorithm)
        throws NoSuchAlgorithmException {
    byte[] hash = MessageDigest.getInstance(algorithm).digest(text.getBytes());
    BigInteger bi = new BigInteger(1, hash);
    String result = bi.toString(16);
    if (result.length() % 2 != 0) {
        return "0" + result;
    }
    return result;
}
于 2009-06-26T03:56:03.730 回答
11
static String toHex(byte[] digest) {
    StringBuilder sb = new StringBuilder();
    for (byte b : digest) {
        sb.append(String.format("%1$02X", b));
    }

    return sb.toString();
}
于 2010-04-14T14:55:04.773 回答
11

我会使用这样的东西来固定长度,比如哈希:

md5sum = String.format("%032x", new BigInteger(1, md.digest()));

0面具中的填充物......

于 2017-04-07T11:28:28.187 回答
6
String result = String.format("%0" + messageDigest.length + "s", hexString.toString())

鉴于您已经拥有的,这是最短的解决方案。如果您可以将字节数组转换为数值,String.format则可以同时将其转换为十六进制字符串。

于 2008-12-01T20:33:15.930 回答
6

Guava也让它变得非常简单:

BaseEncoding.base16().encode( bytes );

当 Apache Commons 不可用时,这是一个不错的选择。它还具有一些不错的输出控制,例如:

byte[] bytes = new byte[] { 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
BaseEncoding.base16().lowerCase().withSeparator( ":", 2 ).encode( bytes );
// "0a:0b:0c:0d:0e:0f"
于 2015-01-22T20:17:14.710 回答
5

这个解决方案有点老了,应该是内存效率高的。

public static String toHexString(byte bytes[]) {
    if (bytes == null) {
        return null;
    }

    StringBuffer sb = new StringBuffer();
    for (int iter = 0; iter < bytes.length; iter++) {
        byte high = (byte) ( (bytes[iter] & 0xf0) >> 4);
        byte low =  (byte)   (bytes[iter] & 0x0f);
        sb.append(nibble2char(high));
        sb.append(nibble2char(low));
    }

    return sb.toString();
}

private static char nibble2char(byte b) {
    byte nibble = (byte) (b & 0x0f);
    if (nibble < 10) {
        return (char) ('0' + nibble);
    }
    return (char) ('a' + nibble - 10);
}
于 2008-12-02T15:21:12.580 回答
5

另外一个选项

public static String toHexString(byte[]bytes) {
    StringBuilder sb = new StringBuilder(bytes.length*2);
    for(byte b: bytes)
      sb.append(Integer.toHexString(b+0x800).substring(1));
    return sb.toString();
}
于 2009-06-03T21:04:01.900 回答
4

为了保持前导零,以下是 Paul 建议的一个小变化(例如 md5 哈希):

public static String MD5hash(String text) throws NoSuchAlgorithmException {
    byte[] hash = MessageDigest.getInstance("MD5").digest(text.getBytes());
    return String.format("%032x",new BigInteger(1, hash));
}

糟糕,这看起来比 Ayman 提议的要差,对此感到抱歉

于 2010-07-13T10:02:03.340 回答
4
static String toHex(byte[] digest) {
    String digits = "0123456789abcdef";
    StringBuilder sb = new StringBuilder(digest.length * 2);
    for (byte b : digest) {
        int bi = b & 0xff;
        sb.append(digits.charAt(bi >> 4));
        sb.append(digits.charAt(bi & 0xf));
    }
    return sb.toString();
}
于 2010-10-08T08:57:37.570 回答
3

看来 concat 和 append 函数可能真的很慢。以下对我来说要快得多(比我以前的帖子)。在构建输出时更改为 char 数组是加快输出速度的关键因素。我没有与 Brandon DuRette 建议的 Hex.encodeHex 进行比较。

public static String toHexString(byte[] bytes) {
    char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    char[] hexChars = new char[10000000];
    int c = 0;
    int v;
    for ( j = 0; j < bytes.length; j++ ) {
        v = bytes[j] & 0xFF;
        hexChars[c] = hexArray[v/16];
        c++;
        hexChars[c] = hexArray[v%16];
        c++;
    }
    return new String(hexChars, 0, c); }
于 2009-06-29T18:26:13.513 回答
2

这是我用于 MD5 哈希的内容:

public static String getMD5(String filename)
        throws NoSuchAlgorithmException, IOException {
    MessageDigest messageDigest = 
        java.security.MessageDigest.getInstance("MD5");

    InputStream in = new FileInputStream(filename);

    byte [] buffer = new byte[8192];
    int len = in.read(buffer, 0, buffer.length);

    while (len > 0) {
        messageDigest.update(buffer, 0, len);
        len = in.read(buffer, 0, buffer.length);
    }
    in.close();

    return new BigInteger(1, messageDigest.digest()).toString(16);
}

编辑:我已经测试过,我注意到这也减少了尾随零。但这只能在开始时发生,因此您可以与预期的长度进行比较并相应地填充。

于 2008-12-01T20:30:05.547 回答
2

如果没有外部库,你可以减少它的编写:

String hex = (new HexBinaryAdapter()).marshal(md5.digest(YOUR_STRING.getBytes()))
于 2012-09-20T14:07:33.367 回答
2

这个解决方案不需要位移或掩码、查找表或外部库,并且尽可能短:

byte[] digest = new byte[16];       

Formatter fmt = new Formatter();    
for (byte b : digest) { 
  fmt.format("%02X", b);    
}

fmt.toString()
于 2012-09-21T00:16:59.877 回答
1
byte messageDigest[] = algorithm.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
    String hexByte = Integer.toHexString(0xFF & messageDigest[i]);
    int numDigits = 2 - hexByte.length();
    while (numDigits-- > 0) {
        hexString.append('0');
    }
    hexString.append(hexByte);
}
于 2008-12-01T20:41:14.773 回答
0

恕我直言,上述提供片段以删除前导零的所有解决方案都是错误的。

byte messageDigest[] = algorithm.digest();
for (int i = 0; i < messageDigest.length; i++) {
    hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}    

根据这个片段,在迭代中从字节数组中取出 8 位,转换为整数(因为 Integer.toHexString 函数将 int 作为参数),然后将该整数转换为相应的哈希值。因此,例如,如果您有二进制的 00000001 00000001,根据代码,hexString 变量的十六进制值将是 0x11,而正确的值应该是 0x0101。因此,在计算 MD5 时,我们可能会得到长度小于 32 字节的散列(因为缺少零),这可能不满足 MD5 散列所做的加密唯一属性。

该问题的解决方案是将上面的代码段替换为以下代码段:

byte messageDigest[] = algorithm.digest();
for (int i = 0; i < messageDigest.length; i++) {
    int temp=0xFF & messageDigest[i];
    String s=Integer.toHexString(temp);
    if(temp<=0x0F){
        s="0"+s;
    }
    hexString.append(s);
}
于 2012-01-27T10:42:10.467 回答
0

这将为一个字节提供两个字符的长字符串。

public String toString(byte b){
    final char[] Hex = new String("0123456789ABCDEF").toCharArray();
    return  "0x"+ Hex[(b & 0xF0) >> 4]+ Hex[(b & 0x0F)];
}
于 2013-01-30T22:24:33.897 回答
0

你怎么能再次从 ascii 转换回字节数组?

我按照以下代码转换为 Jemenake 给出的 ascii。

public static String toHexString(byte[] bytes) {
    char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    char[] hexChars = new char[bytes.length * 2];
    int v;
    for ( int j = 0; j < bytes.length; j++ ) {
        v = bytes[j] & 0xFF;
        hexChars[j*2] = hexArray[v/16];
        hexChars[j*2 + 1] = hexArray[v%16];
    }
    return new String(hexChars);
}
于 2013-06-12T10:53:12.007 回答
0

我的变种

    StringBuilder builder = new StringBuilder();
    for (byte b : bytes)
    {
        builder.append(Character.forDigit(b/16, 16));
        builder.append(Character.forDigit(b % 16, 16));
    }
    System.out.println(builder.toString());

这个对我有用。

于 2013-08-29T19:58:20.430 回答
0

这是一个错误的解决方案吗?(安卓Java)

    // Create MD5 Hash
    MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
    digest.update(s.getBytes());
    byte[] md5sum = digest.digest();
    BigInteger bigInt = new BigInteger(1, md5sum);
    String stringMD5 = bigInt.toString(16);
    // Fill to 32 chars
    stringMD5 = String.format("%32s", stringMD5).replace(' ', '0');
    return stringMD5;

所以基本上它用0替换空格。

于 2014-04-24T08:07:50.387 回答
0

我很惊讶没有人提出以下解决方案:

StringWriter sw = new StringWriter();
com.sun.corba.se.impl.orbutil.HexOutputStream hex = new com.sun.corba.se.impl.orbutil.HexOutputStream(sw);
hex.write(byteArray);
System.out.println(sw.toString());
于 2016-02-17T20:34:31.767 回答
0

或者你可以这样做:

byte[] digest = algorithm.digest();
StringBuilder byteContet = new StringBuilder();
for(byte b: digest){
 byteContent = String.format("%02x",b);
 byteContent.append(byteContent);
}

它简短,简单,基本上只是格式更改。

于 2018-12-23T10:29:33.640 回答
-1

这也是等效的,但使用 Apache util HexBin更简洁,其中代码简化为

HexBin.encode(messageDigest).toLowerCase();
于 2012-08-27T14:20:36.257 回答