Converting a byte array to hexadecimal in Java and Android

16 Apr 2012

I needed to convert a SHA 512 digest byte array to a hexadecimal string representation the other day. I tried doing this with the built in Integer.hexadecimal function, but found it gave some weird output. The culprit was Java's way of handling bytes. Java uses signed bytes, so I didn't get the behavior I expected. In this post I will elaborate on it and show you how you can unsign a signed byte.

Signed bytes

We have two possibilities of displaying bytes, signed or unsigned. For instance:
1000 1111
This represents:
signed: -113
unsigned: 143
Or in hexadecimal notation with 32 bits:
FF FF FF 8F
8F (leading 0 are not written)
Now the problem arises when we want to convert every byte to hexadecimal. Normally one would do this by running this piece of code:

String s = "test";
        MessageDigest mda;
        try {
            mda = MessageDigest.getInstance("SHA-512");
            mda.reset();
    //important part
            byte[] array = mda.digest(s.getBytes());
            StringBuffer sb = new StringBuffer();
            for (int i=0;i<array.length;i++) {
                sb.append(Integer.toHexString((int) array[i]));
            }
            System.out.println(sb.toString());

So we take a byte array (mda is a MessageDigest instance). We create a stringbuffer to build our string of hexadecimal numbers with. We loop the byte array and take its Integer representation and convert it to its Hexadecimal representation. Now here is the tricky part, since Java uses signed integers we will get different hexadecimal values depending on the sign. If the value is negative, the hexadecimal representation will be filled up with leading 'f'. This means that for the representation of '11101110' we will get:

hex: ff ff ff ee
bin: 1111 1111 1111 1111 1111 1111 1110 1110
This is a 32-bit representation. Because there is a leading 1 meaning the number is negative. If we use the unsigned representation we will get:
ee
Now this might not look like a real problem, until you want to try and get the hexadecimal representation of a message digest. Because Java sees 'ee' it assumes it is negative so it converts it to 'ff ff ff ee' rather than just 'ee'. So to solve this problem we will have to 'AND' with
 000000ff
. This will work away all the leading 'f':
ANDing:
1 and 0 = 0
1 and 1 = 1
0 and 0 = 0
1111 1111 1111 1111 1111 1111 1110 1110 (FF FF FF EE)
0000 0000 0000 0000 0000 0000 1111 1111 (FF)
0000 0000 0000 0000 0000 0000 1110 1110 (EE) --> result
Before we implement this in Java, mind that there is a difference between
(byte) 0xFF
0xFF
To see the difference run this piece of code:

public class main {
    public static void main(String[] args) {
    int x = 0xFF;
    int y = (byte) 0xFF;
    System.out.println(x+"\n"+y);
    }
}

output:

255
-1
As you see the first 0xFF is a positive integer. The second 0xFF gets cast to a byte, it automatically gets signed.(byte) 0xFF has a leading 1 so the sign is negative (FF FF FF FF). So we now know we need to & with 0xFF (integer) to get the unsigned representation:

String s = "test";
        MessageDigest mda;
        try {
            mda = MessageDigest.getInstance("SHA-512");
            mda.reset();
    //important part
            byte[] array = mda.digest(s.getBytes());
            StringBuffer sb = new StringBuffer();
            for (int i=0;i<array.length;i++) {
                sb.append(Integer.toHexString((int) array[i] &amp; 0xFF));
            }
            System.out.println(sb.toString());

As noted by Mattias Severson there is a serious bug in my code for values between 0 and 15:

There is a bug in your code if you expect a valid SHA string as output. The problem is byte values between 0 and 15. For these values, Integer.toHexString() correctly return strings between "0" and "F". However, for your example to work as SHA string, those values need to be padded by "0" so that the resulting String always consists of two characters. Another solution would be to use the String.format() method, e.g.
So Mattias patched my code:

String s = "test";
        MessageDigest mda;
        try {
            mda = MessageDigest.getInstance("SHA-512");
            mda.reset();
    //important part
            byte[] array = mda.digest(s.getBytes());
            StringBuffer sb = new StringBuffer();
            for (byte b : array) {
                             sb.append(String.format("%02X", b)); // print the byte as a 0 padded, two digit, hexadecimal String
                         }
            System.out.println(sb.toString());

Thanks Mattias! Good Luck!