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?

Eugene M
  • 47,557
  • 14
  • 38
  • 44

28 Answers28

142

Check out Hex.encodeHexString from Apache Commons Codec.

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

String hex = Hex.encodeHexString(bytes);
MultiplyByZer0
  • 6,302
  • 3
  • 32
  • 48
Brandon DuRette
  • 4,810
  • 5
  • 25
  • 31
  • 14
    And as long as you're doing md5 using Apache Commons Codec, take a look at [DigestUtils.md5Hex()](http://commons.apache.org/codec/apidocs/org/apache/commons/codec/digest/DigestUtils.html#md5Hex(byte[])) – rescdsk Apr 17 '12 at 14:13
  • 1
    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. – Conor Pender Sep 03 '12 at 14:40
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.

Ayman
  • 11,265
  • 16
  • 66
  • 92
  • 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! – Roboprog Apr 06 '13 at 01:06
  • Works great, thanks. – Lev Sep 10 '15 at 13:41
  • 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"`. – Mark Rajcok Oct 18 '16 at 22:42
  • 1
    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 five* – Chadd Frasier Feb 02 '22 at 16:31
114

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();
}
Michael Myers
  • 188,989
  • 46
  • 291
  • 292
  • Wouldn't it produce "10" for byte 0x01? – n0rd Oct 20 '09 at 20:21
  • 4
    No, the 0 gets appended to `hexString` before the hex value does. – Michael Myers Oct 20 '09 at 21:22
  • 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. – Marvo Jul 27 '12 at 20:55
  • 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? – Juzer Ali Jan 21 '13 at 08:23
  • 1
    @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. – Michael Myers Jan 21 '13 at 15:06
  • Is this able to handle large negative numbers without overflow? – piritocle Jan 26 '22 at 03:57
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
MultiplyByZer0
  • 6,302
  • 3
  • 32
  • 48
Gareth
  • 401
  • 4
  • 2
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.

Jemenake
  • 2,092
  • 1
  • 20
  • 16
  • Nice! Improves upon the "append is slow" thought from Steve by making it work for any arbitrary size byte array. – Ogre Psalm33 Dec 08 '10 at 14:13
  • 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). – Scott Carey Jan 10 '12 at 00:01
  • 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') – Scott Carey Jan 10 '12 at 01:07
  • (my mistake! the ASCII table does not have a-f or A-F follow 0-9, the previous won't work) – Scott Carey Jan 10 '12 at 01:20
  • 6
    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; } – cn1h Nov 03 '12 at 20:16
  • The bit shift version became the accepted answer over at http://stackoverflow.com/a/9855338 – simbo1905 Dec 20 '14 at 18:30
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"};
David Snabel-Caunt
  • 57,804
  • 13
  • 114
  • 132
  • 1
    @Marvo 0x000000FF == 0xFF, so your proposed change does nothing. The mask is just an int like any other number. 0xFF != -1 – ComputerDruid Jul 14 '14 at 13:17
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...

Usagi Miyamoto
  • 6,196
  • 1
  • 19
  • 33
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;
}
  • 4
    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"`. – Daniel Lubarov Feb 01 '12 at 23:45
  • BigInteger.toString() is by far the slowest way I found doing it in Java, about 100x slower as a performant implementation, see https://stackoverflow.com/a/58118078/774398. Also in your answer a hash is calculated, but that was not part of the question. – Patrick Oct 30 '19 at 13:57
  • 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) – comodoro Nov 30 '19 at 13:06
11
static String toHex(byte[] digest) {
    StringBuilder sb = new StringBuilder();
    for (byte b : digest) {
        sb.append(String.format("%1$02X", b));
    }

    return sb.toString();
}
Illarion Kovalchuk
  • 5,774
  • 8
  • 42
  • 54
  • 3
    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. – Robert Mar 26 '12 at 05:19
  • Of course for variable digest lengths you would use an initial capacity of `digest.length * 2`. – Robert Mar 26 '12 at 05:30
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"
kichik
  • 33,220
  • 7
  • 94
  • 114
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.

Ed Marty
  • 39,590
  • 19
  • 103
  • 156
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);
}
agentbillo
  • 51
  • 2
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();
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
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

F.X
  • 159
  • 1
  • 2
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();
}
max
  • 41
  • 1
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); }
2

You can get it writing less without external libraries:

String hex = (new HexBinaryAdapter()).marshal(md5.digest(YOUR_STRING.getBytes()))
arutaku
  • 5,937
  • 1
  • 24
  • 38
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()
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.

kgiannakakis
  • 103,016
  • 27
  • 158
  • 194
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);
}
Fernando Miguélez
  • 11,196
  • 6
  • 36
  • 54
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)];
}
Hatto
  • 65
  • 2
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);
}
Dhimant Jayswal
  • 123
  • 1
  • 10
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.

wbr
  • 31
  • 2
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.

Stan
  • 6,511
  • 8
  • 55
  • 87
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());
halber
  • 197
  • 2
  • 11
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.

KumarAnkit
  • 713
  • 1
  • 9
  • 26
  • 1
    Nearly there.. except the byte values are signed (-128 -> 127), so you need the following change: `byteContent = String.format("%02x",b&0xff);` – RoyM Feb 11 '19 at 12:57
  • `StringBuilder byteContet = new StringBuilder();` Looks like a typo. (`byteContet`) – Shafique Jamal Oct 31 '22 at 09:27
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);
}
Divij
  • 878
  • 2
  • 9
  • 18
-1

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

HexBin.encode(messageDigest).toLowerCase();
Stephan
  • 41,764
  • 65
  • 238
  • 329
neel
  • 184
  • 5