In Java, how do I convert a byte array to a string of hex digits while keeping leading zeros? [duplicate]
Asked Answered
G

28

171

I'm working with some example java code for making md5 hashes. One part converts the results from bytes to a string of hex digits:

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

However, it doesn't quite work since toHexString apparently drops off leading zeros. So, what's the simplest way to go from byte array to hex string that maintains the leading zeros?

Gunilla answered 1/12, 2008 at 20:24 Comment(0)
E
115

A simple approach would be to check how many digits are output by Integer.toHexString() and add a leading zero to each byte if needed. Something like this:

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();
}
Esprit answered 1/12, 2008 at 20:32 Comment(6)
Wouldn't it produce "10" for byte 0x01?Monocular
No, the 0 gets appended to hexString before the hex value does.Esprit
When I called Integer.toHexString((byte)0xff) it returned "ffffffff" because of sign extension. So one might need to take the last two characters of the returned string.Sewellel
Wouldn't this return extra zeroes? For example if byte array is {0,1,2,3}, it should return 0123, but it will return 00010203, or is it the desired result of a hash?Variolite
@juzerali: This question requires "while keeping leading zeros". If you don't want the leading zeros, there's no reason to use this code; just use the code from the question.Esprit
Is this able to handle large negative numbers without overflow?Edibles
D
142

Check out Hex.encodeHexString from Apache Commons Codec.

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

String hex = Hex.encodeHexString(bytes);
Developing answered 1/12, 2008 at 22:12 Comment(2)
And as long as you're doing md5 using Apache Commons Codec, take a look at DigestUtils.md5Hex()Pigmentation
DigestUtils does make things a bit easier, but it can be a hassle including it in your project. Personally I groan at the thought of messing with pom files.Gladiator
A
123

You can use the one below. I tested this with leading zero bytes and with initial negative bytes as well

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

If you want lowercase hex digits, use "x" in the format String.

Altigraph answered 3/6, 2009 at 10:9 Comment(3)
No external dependencies, nice & short. Plus, if you know you have 16 bytes / 32 hex digits, your solution condenses to an easy one-liner. Cool!Crank
Thank you. I needed this to convert a 16-byte IPv6 byte array to a zero-padded hex string in Scala: f"${BigInt(1, myIpv6ByteArray)}%032x".Wares
Nicely done sir this does a great job converting raw byte strings from ECDSA keys. It even converts the compression bytes at the front with no errors and no extra information about the key needed! Hold hand up for high fiveCrossbeam
E
115

A simple approach would be to check how many digits are output by Integer.toHexString() and add a leading zero to each byte if needed. Something like this:

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();
}
Esprit answered 1/12, 2008 at 20:32 Comment(6)
Wouldn't it produce "10" for byte 0x01?Monocular
No, the 0 gets appended to hexString before the hex value does.Esprit
When I called Integer.toHexString((byte)0xff) it returned "ffffffff" because of sign extension. So one might need to take the last two characters of the returned string.Sewellel
Wouldn't this return extra zeroes? For example if byte array is {0,1,2,3}, it should return 0123, but it will return 00010203, or is it the desired result of a hash?Variolite
@juzerali: This question requires "while keeping leading zeros". If you don't want the leading zeros, there's no reason to use this code; just use the code from the question.Esprit
Is this able to handle large negative numbers without overflow?Edibles
A
40

The method javax.xml.bind.DatatypeConverter.printHexBinary(), part of the Java Architecture for XML Binding (JAXB), was a convenient way to convert a byte[] to a hex string. The DatatypeConverter class also included many other useful data-manipulation methods.

In Java 8 and earlier, JAXB was part of the Java standard library. It was deprecated with Java 9 and removed with Java 11, as part of an effort to move all Java EE packages into their own libraries. It's a long story. Now, javax.xml.bind doesn't exist, and if you want to use JAXB, which contains DatatypeConverter, you'll need to install the JAXB API and JAXB Runtime from Maven.

Example usage:

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

Will result in:

000086003D
Arte answered 27/1, 2013 at 21:50 Comment(2)
For reversing, there's DatatypeConverter.parseHexBinary(hexString) as well.Tonnie
Keep in mind that starting with Java 11, java.xml package is not part of the JDK anymore.Chivalry
S
35

I liked Steve's submissions, but he could have done without a couple of variables and saved several lines in the process.

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

What I like about this is that it's easy to see exactly what it's doing (instead of relying on some magic BigInteger black box conversion) and you're also free from having to worry about corner cases like leading-zeroes and stuff. This routine takes every 4-bit nibble and turns it into a hex char. And it's using a table lookup, so it's probably fast. It could probably be faster if you replace v/16 and v%16 with bitwise shifts and AND's, but I'm too lazy to test it right now.

Stanger answered 4/2, 2010 at 6:3 Comment(6)
Nice! Improves upon the "append is slow" thought from Steve by making it work for any arbitrary size byte array.Carraway
change v/16 to v >>> 4 and v%16 to v & 0x0F to improve the speed. Also, you can use j << 1 to multiply by 2 (though the compiler probably does that one for you).Chemisette
Or, even better add the value to '0' to get the character, so that no lookup table is required. e.g. hexChars[j << 1] = (byte)(v >>> 4 + '0')Chemisette
(my mistake! the ASCII table does not have a-f or A-F follow 0-9, the previous won't work)Chemisette
An inverse function, maybe someone need it. public static byte[] bytesFromHex(String hexString) { final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char[] hexChars = hexString.toCharArray(); byte[] result = new byte[hexChars.length / 2]; for (int j = 0; j < hexChars.length; j += 2) { result[j / 2] = (byte) (Arrays.binarySearch(hexArray, hexChars[j]) * 16 + Arrays.binarySearch(hexArray, hexChars[j + 1])); } return result; }Peruke
The bit shift version became the accepted answer over at stackoverflow.com/a/9855338Considerable
C
22

I found Integer.toHexString to be a little slow. If you are converting many bytes, you may want to consider building an array of Strings containing "00".."FF" and use the integer as the index. I.e.

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

This is faster and ensures the correct length. Just requires the array of strings:

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"};
Cogitate answered 15/6, 2009 at 17:15 Comment(1)
@Sewellel 0x000000FF == 0xFF, so your proposed change does nothing. The mask is just an int like any other number. 0xFF != -1Lyra
P
13

I would use something like this for fixed length, like hashes:

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

The 0 in the mask does the padding...

Pucker answered 7/4, 2017 at 11:28 Comment(1)
A one line solution using only standard Java!Cubism
R
12

I've been looking for the same thing ... some good ideas here, but I ran a few micro benchmarks. I found the following to be the fastest (modified from Ayman's above and about 2x as fast, and about 50% faster than Steve's just above this one):

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

Edit: Oops - missed that this is essentially the same as kgiannakakis's and so may strip off a leading 0. Still, modifying this to the following, it's still the fastest:

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;
}
Race answered 26/6, 2009 at 3:56 Comment(3)
This still isn't right. If the hash is {0, 0, 0, 0} for example, BigInteger's toString will just give "0". This code prepends another "0" and returns "00", but the result should be "00000000".Upcast
BigInteger.toString() is by far the slowest way I found doing it in Java, about 100x slower as a performant implementation, see https://mcmap.net/q/24231/-java-convert-a-byte-array-to-a-hex-string. Also in your answer a hash is calculated, but that was not part of the question.Tressietressure
It is not what the OP asked, but OK if you look for hash string and change the last to while(length < expected length) ... add zeros to front (most hashes have expected length, usually some power of two like 128)Mccusker
W
11
static String toHex(byte[] digest) {
    StringBuilder sb = new StringBuilder();
    for (byte b : digest) {
        sb.append(String.format("%1$02X", b));
    }

    return sb.toString();
}
Weirdo answered 14/4, 2010 at 14:55 Comment(2)
The default initial capacity of a StringBuilder is 16 chars. A MD5 hash consists of 32 chars. After appending the first 16 chars the internal array would be copied to a new array of length 34. Also String.format creates a new Formatter instance for every byte of the digest. And by default each Formatter instantiates a new StringBuilder to buffer its output. I even think it's easier to create just one Formatter with a StringBuffer of 32 chars initial capacity (new Formatter(new StringBuilder(32))) and use its format and toString methods.Edaedacious
Of course for variable digest lengths you would use an initial capacity of digest.length * 2.Edaedacious
P
6
String result = String.format("%0" + messageDigest.length + "s", hexString.toString())

That's the shortest solution given what you already have. If you could convert the byte array to a numeric value, String.format can convert it to a hex string at the same time.

Popularity answered 1/12, 2008 at 20:33 Comment(2)
Very elegant except for Java 1.5+ requirement. Though not an issue nowadays ...Darden
Doesn't work: Exception in thread "main" java.util.FormatFlagsConversionMismatchException: Conversion = s, Flags = 0Meleager
B
6

Guava makes it pretty simple too:

BaseEncoding.base16().encode( bytes );

It's a nice alternative when Apache Commons is not available. It also has some nice controls of the output like:

byte[] bytes = new byte[] { 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
BaseEncoding.base16().lowerCase().withSeparator( ":", 2 ).encode( bytes );
// "0a:0b:0c:0d:0e:0f"
Brindled answered 22/1, 2015 at 20:17 Comment(0)
P
5

This solution is a little older school, and should be memory efficient.

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);
}
Promethium answered 2/12, 2008 at 15:21 Comment(0)
B
5

Another option

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();
}
Bergamot answered 3/6, 2009 at 21:4 Comment(0)
I
4

In order to keep leading zeroes, here is a small variation on what has Paul suggested (eg md5 hash):

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

Oops, this looks poorer than what's Ayman proposed, sorry for that

Iatry answered 13/7, 2010 at 10:2 Comment(0)
S
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();
}
Sagitta answered 8/10, 2010 at 8:57 Comment(1)
I'd be very interested to see how this compares to Jemenake's solutionIndoctrinate
C
3

It appears concat and append functions can be really slow. The following was MUCH faster for me (than my previous post). Changing to a char array in building the output was the key factor to speed it up. I have not compared to Hex.encodeHex suggested by Brandon DuRette.

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); }
Cogitate answered 29/6, 2009 at 18:26 Comment(2)
That two megabyte allocation (= new char[10000000];) is completely unnecessary and wasteful.Indoctrinate
Twenty megabyte, lol. Though bytes.length * 4 bytes would be sufficient.Edaedacious
H
2

This what I am using for MD5 hashes:

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

EDIT: I've tested and I've noticed that with this also trailing zeros are cut. But this can only happen in the beginning, so you can compare with the expected length and pad accordingly.

Heaps answered 1/12, 2008 at 20:30 Comment(0)
U
2

You can get it writing less without external libraries:

String hex = (new HexBinaryAdapter()).marshal(md5.digest(YOUR_STRING.getBytes()))
Unrighteous answered 20/9, 2012 at 14:7 Comment(0)
R
2

This solution requires no bit-shifting or -masking, lookup tables, or external libraries, and is about as short as I can get:

byte[] digest = new byte[16];       

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

fmt.toString()
Repulse answered 21/9, 2012 at 0:16 Comment(0)
P
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);
}
Poetaster answered 1/12, 2008 at 20:41 Comment(0)
W
0

IMHO all the solutions above that provide snippets to remove the leading zeroes are wrong.

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

According to this snippet, 8 bits are taken from the byte array in an iteration, converted into an integer (since Integer.toHexString function takes int as argument) and then that integer is converted to the corresponding hash value. So, for example if you have 00000001 00000001 in binary, according to the code, the hexString variable would have 0x11 as the hex value whereas correct value should be 0x0101. Thus, while calculating MD5 we may get hashes of length <32 bytes(because of missing zeroes) which may not satisfy the cryptographically unique properties that MD5 hash does.

The solution to the problem is replacing the above code snippet by the following snippet:

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);
}
Worthen answered 27/1, 2012 at 10:42 Comment(0)
M
0

This will give two-char long string for a byte.

public String toString(byte b){
    final char[] Hex = new String("0123456789ABCDEF").toCharArray();
    return  "0x"+ Hex[(b & 0xF0) >> 4]+ Hex[(b & 0x0F)];
}
Mattiematting answered 30/1, 2013 at 22:24 Comment(0)
G
0

And how can you convert back again from ascii to byte array ?

i followed following code to convert to ascii given by Jemenake.

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);
}
Geophilous answered 12/6, 2013 at 10:53 Comment(0)
H
0

my variant

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

it works for me.

Hatband answered 29/8, 2013 at 19:58 Comment(0)
P
0

Is that a faulty solution? (android 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;

So basically it replaces spaces with 0.

Percolation answered 24/4, 2014 at 8:7 Comment(0)
D
0

I'm surprised that no one came up with the following solution:

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());
Dorthydortmund answered 17/2, 2016 at 20:34 Comment(0)
W
0

Or you can do this:

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

Its Short, simple and basically just a format change.

Windstorm answered 23/12, 2018 at 10:29 Comment(2)
Nearly there.. except the byte values are signed (-128 -> 127), so you need the following change: byteContent = String.format("%02x",b&0xff);Crouton
StringBuilder byteContet = new StringBuilder(); Looks like a typo. (byteContet)Trademark
S
-1

This is also equivalent but more concise using Apache util HexBin where the code reduces to

HexBin.encode(messageDigest).toLowerCase();
Shushubert answered 27/8, 2012 at 14:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.