Tuesday, October 28, 2008

Hex Operations in Java - convert byte array to hex, convert hex string to byte array

I found the following asHex method to convert a byte array into a hex string and wrote a lenient hex string to byte array conversion (that strips non alphanumeric characters from hex input string, since people may input colons, spaces, slashes (if copied/pasted from terminal window). HexUtil.java
/**
 * @author Gary S. Weaver
 */
public class HexUtil {

    // asHex method found at http://www.dreamincode.net/forums/showtopic46665.htm

    /**
     * Turns array of bytes into hex string
     *
     * @param buf Array of bytes to convert to hex string
     * @return Generated hex string
     */
    public static String asHex(byte buf[]) {
        StringBuffer sb = new StringBuffer(buf.length * 2);
        for (int i = 0; i < buf.length; i++) {
            if (((int) buf[i] & 0xff) < 0x10) {
                sb.append("0");
            }

            sb.append(Long.toString((int) buf[i] & 0xff, 16));
        }
        System.out.println("asHex(" + new String(buf) + ") returned '" + sb.toString() + "'");

        return sb.toString();
    }

    /**
     * Removes all non-alphanumeric characters from input hex string and converts to bytes.
     *
     * @param hex
     * @return
     */
    public static byte[] lenientHexToBytes(String hex) {
        byte[] result = null;
        if (hex != null) {
            // remove all non alphanumeric chars like colons, whitespace, slashes
            hex = hex.replaceAll("[^a-zA-Z0-9]", "");
            // from http://forums.sun.com/thread.jspa?threadID=546486
            // (using BigInteger to convert to byte array messes up by adding extra 0 if first byte > 7F and this method
            //  will not rid of leading zeroes like the flawed method byte[] bts = new BigInteger(hex, 16).toByteArray();)
            result = new byte[hex.length() / 2];
            for (int i = 0; i < result.length; i++) {
                result[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
            }
        }
        System.out.println("lenientHexToBytes(" + hex + ") returned '" + new String(result) + "'");

        return result;
    }
}
HexUtilTest.java
import junit.framework.TestCase;
import org.jasig.portlet.mail.util.encryption.impl.HexUtil;

/**
 * @author Gary S. Weaver
 */
public class HexUtilTest extends TestCase {

    private static final String ALL_POSSIBLE_HEX = "000102030405060708090a0b0c0d0e0f101112131415" +
            "161718191a1b1c1d1e1f202122232425262728292a2b" +
            "2c2d2e2f303132333435363738393a3b3c3d3e3f4041" +
            "42434445464748494a4b4c4d4e4f5051525354555657" +
            "58595a5b5c5d5e5f606162636465666768696a6b6c6d" +
            "6e6f707172737475767778797a7b7c7d7e7f80818283" +
            "8485868788898a8b8c8d8e8f90919293949596979899" +
            "9a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf" +
            "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5" +
            "c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadb" +
            "dcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1" +
            "f2f3f4f5f6f7f8f9fafbfcfdfeff";

    private static final String ALL_POSSIBLE_HEX_WITH_NONALPHANUMERIC_CHARS = "00 : 01 : 02 : 03 : 04 : 05 : 06 : " +
            "07 : 08 : 09 : \\ / 0a0b0c0d0e0f101112131415" +
            "161718191a1b1c1d1e1f202122232425262728292a2b" +
            "2c2d2e2f303132333435363738393a3b3c3d3e3f4041" +
            "42434445464748494a4b4c4d4e4f5051525354555657" +
            "58595a5b5c5d5e5f606162636465666768696a6b6c6d" +
            "6e6f707172737475767778797a7b7c7d7e7f80818283" +
            "8485868788898a8b8c8d8e8f90919293949596979899" +
            "9a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf" +
            "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5" +
            "c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadb" +
            "dcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1" +
            "f2f3f4f5f6f7f8f9fafbfcfdfeff";

    public void testAsHex() {
        assertEquals("Failed asHex", ALL_POSSIBLE_HEX, HexUtil.asHex(getAllPossibleBytes()));
    }

    public void testLenientHexToBytes() {
        assertEquals("Failed lenientHexToBytes with hex", asBinary(getAllPossibleBytes()), asBinary(HexUtil.lenientHexToBytes(ALL_POSSIBLE_HEX)));
        assertEquals("Failed lenientHexToBytes with hex string containing colons, spaces, and slashes", asBinary(getAllPossibleBytes()), asBinary(HexUtil.lenientHexToBytes(ALL_POSSIBLE_HEX_WITH_NONALPHANUMERIC_CHARS)));
    }

    public byte[] getAllPossibleBytes() {
        byte[] bytes = new byte[256];
        for (int i = 0; i < 256; i++) {
            bytes[i] = (byte) i;
        }
        return bytes;
    }

    public static String asBinary(byte[] in) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < in.length; i++) {
            byte b = in[i];
            sb.append(Integer.toBinaryString(b));
        }
        return sb.toString();
    }
}

No comments: