This article shows you a few ways to convert byte arrays or byte[]
to a hexadecimal (base 16 or hex) string representative.
String.format
Integer.toHexString
- Apache Commons Codec –
commons-codec
- Spring Security Crypto –
spring-security-crypto
- Bitwise shifting and masking. (educational purposes)
Note
Both Apache Commons-Codec and Spring Security Crypto modules are using the similar
5. Bitwise shifting and masking
techniques to convert byte arrays to a hex string, please study the source code below, it is useful for educational purposes.
1. String.format %02x
This String.format
is the easiest and obvious way to convert a byte arrays into a hex, %02x
for lower case hex, %02X
upper case hex.
ByteToHexExample1.java
package com.favtuts.crypto.bytes; import java.nio.charset.StandardCharsets; public class ByteToHexExample1 { public static String hex(byte[] bytes) { StringBuilder result = new StringBuilder(); for (byte aByte : bytes) { result.append(String.format("%02x", aByte)); // upper case // result.append(String.format("%02X", aByte)); } return result.toString(); } public static void main(String[] args) { String input = "tuts.heomi.net"; // 666176747574732e636f6d System.out.println(hex(input.getBytes(StandardCharsets.UTF_8))); } }
2. Integer.toHexString
This Integer.toHexString(int i)
accepts an int
as argument and returns a hex string. The key is convert the byte
to an int
and mask with a 0xff
to prevent sign extension.
ByteToHexExample2.java
package com.favtuts.crypto.bytes; import java.nio.charset.StandardCharsets; public class ByteToHexExample2 { public static String hex(byte[] bytes) { StringBuilder result = new StringBuilder(); for (byte aByte : bytes) { // bytes widen to int, need mask, prevent sign extension // get last 8 bits int decimal = (int) aByte & 0xff; String hex = Integer.toHexString(decimal); // if half hex, pad with zero, e.g \t if (hex.length() % 2 == 1) { hex = "0" + hex; } result.append(hex); } return result.toString(); } public static void main(String[] args) { String input = "tuts.heomi.net"; // 666176747574732e636f6d System.out.println(hex(input.getBytes(StandardCharsets.UTF_8))); } }
3. Apache Commons Codec
We can use Hex.encodeHex
to convert byte[]
to a hex string, or Hex.decodeHex
to convert a hex string to byte[]
.
ByteToHexExample3.java
package com.favtuts.crypto.bytes; import java.nio.charset.StandardCharsets; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.binary.Hex; public class ByteToHexExample3 { public static String hex(byte[] bytes) { char[] result = Hex.encodeHex(bytes); return new String(result); } public static String unhex(String hex) throws DecoderException { return new String(Hex.decodeHex(hex)); } public static void main(String[] args) throws DecoderException { String input = "tuts.heomi.net"; String hex = hex(input.getBytes(StandardCharsets.UTF_8)); System.out.println(hex); // 666176747574732e636f6d String unhex = unhex(hex); System.out.println(unhex); // tuts.heomi.net } }
Maven pom.xml
<dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.14</version> </dependency>
4. Spring Security Crypto
In Spring Security, we can use Hex.encode
to convert byte[]
to a hex string.
ByteToHexExample4.java
package com.favtuts.crypto.bytes; import java.nio.charset.StandardCharsets; import org.springframework.security.crypto.codec.Hex; public class ByteToHexExample4 { public static void main(String[] args) { String input = "tuts.heomi.net"; char[] encode = Hex.encode(input.getBytes(StandardCharsets.UTF_8)); String hex = new String(encode); System.out.println(hex); // 666176747574732e636f6d byte[] decode = Hex.decode(hex); System.out.println(new String(decode)); // tuts.heomi.net } }
Maven pom.xml
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-crypto</artifactId> <version>5.3.2.RELEASE</version> </dependency>
5. Bitwise shifting and masking.
The below source code is from the Spring Security Crypto module, and the Apache Commons Codes uses similar techniques to convert byte arrays to hex strings, with some minor changes like differing variable name or length calculation, the core ideas are the same.
5.1 Hex encode.
Hex.java
package org.springframework.security.crypto.codec; //... private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public static char[] encode(byte[] bytes) { final int nBytes = bytes.length; char[] result = new char[2 * nBytes]; // 1 hex contains two chars // hex = [0-f][0-f], e.g 0f or ff int j = 0; for (byte aByte : bytes) { // loop byte by byte // 0xF0 = FFFF 0000 result[j++] = HEX[(0xF0 & aByte) >>> 4]; // get the top 4 bits, first half hex char // 0x0F = 0000 FFFF result[j++] = HEX[(0x0F & aByte)]; // get the bottom 4 bits, second half hex char // combine first and second half, we get a complete hex } return result; }
The hard part is to understand the following two statements.
HEX[(0xF0 & aByte) >>> 4];
HEX[(0x0F & aByte)];
5.1.1 HEX[(0xF0 & aByte) >>> 4]
(first half hex)
For example, a character a
, binary is 0110 0001
, after bitwise AND
a 0xF0
, it becomes 0110 0000
.
0110 0001 # 1 hex = 2 chars [0-f][0-f]
# In this case, hex = [0110][0001]
0110 0001 # 0110 = first half hex, 0001 = second half hex
&
FFFF 0000 # 0xF0 = FFFF 0000 , bitwise AND operator.
0110 0000 # result 0110 0000
Logical right shift 4 bits 0110 0000 >>> 4
, it becomes 0000 0110
. Read this Java >> and >>> bitwise shift operator.
0110 0000 |
???? 0110 | 0000 # >>> 4
0000 0110 | 0000 # >>> 4 (logical right shift, zero extension)
0000 0110 # result, the first half hex
Convert this binary 0000 0110
to decimal, it is a 6
, look at the variable static final char[] HEX
, the value of the index 6 is 6, and the first half of the hex is 6.
5.1.2 HEX[(0x0F & aByte)]
(second half hex)
The same character a
, binary is 0110 0001
, bitwise AND
a 0x0F
.
0110 0001 # 0110 = first half hex, 0001 = second half hex
& # bitwise AND operator.
0000 FFFF # 0x0F = 0000 FFFF
0000 0001 # result 0000 0001
Convert this binary 0000 00001
to decimal, it is 1
, look at the variable static final char[] HEX
again, the value of the index 1 is 1, and the second half of the hex is 1.
We combine the first half and second half of the hex, which is 6 + 1, it becomes 61
. For the character a
, the hex is 61
.
5.2 Hex decode.
Hex.java
package org.springframework.security.crypto.codec; //... private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; public static byte[] decode(CharSequence s) { int nChars = s.length(); if (nChars % 2 != 0) { throw new IllegalArgumentException( "Hex-encoded string must have an even number of characters"); } byte[] result = new byte[nChars / 2]; // 1 hex = 2 char for (int i = 0; i < nChars; i += 2) { // step 2, 1 hex = 2 char int msb = Character.digit(s.charAt(i), 16); // char -> hex, base16 int lsb = Character.digit(s.charAt(i + 1), 16); if (msb < 0 || lsb < 0) { throw new IllegalArgumentException( "Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position"); } result[i / 2] = (byte) ((msb << 4) | lsb); } return result; } }
For example, the same hex 61
, character a
.
The Character.digit
is the JDK APIs, convert a char
to a base 16 or hex and returns an int
.
int msb = Character.digit(s.charAt(i), 16); // msb = 6
int lsb = Character.digit(s.charAt(i + 1), 16); // lsb = 1
- The
msb
stands for most significant bit (or high-order bit or the leftmost bit). - The
lsb
stands for least significant bit (or the rightmost bit).
Note
The
msb
andlsb
variable names are a bit weird, and I think the author refers to the first 4 bits and last 4 bits.
In Java, int 6
, the binary is 0000 0110
; for int 1
the binary is 0000 0001
(byte) ((msb << 4) | lsb); // in this example, msb = 6, lsb = 1
# (byte) ((msb << 4) | lsb);
| 0000 0000 | 0000 0000 | 0000 0000 | 0000 0110 | # msb, 6 is an int, 32-bit
# (byte) ((msb << 4) | lsb);
# (byte) ((6 << 4) | 1);
# <<-- 4
0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0110 ???? | # 6 << 4
0000 | 0000 0000 | 0000 0000 | 0000 0000 | 0110 0000 | # left shift, ignore sign, zero extension.
| 0000 0000 | 0000 0000 | 0000 0000 | 0110 0000 | # final msb
# bitwise | operator, bitwise inclusive OR
| 0000 0000 | 0000 0000 | 0000 0000 | 0000 0001 | # lsb = 1
| 0000 0000 | 0000 0000 | 0000 0000 | 0110 0001 | # msb | lsb = 0110 0001
| 0110 0001 | # (byte) (msb|lsb) , down cast from int to byte 8-bit
The final binary is 0110 0001
.
This code snippet converts a binary string to a string, for binary 0110 0001
, the string is a
.
int charCode = Integer.parseInt("01100001", 2);
System.out.println(charCode); // 97, look ascii table
String str = Character.toString((char) charCode);
System.out.println(str); // output = a
Thanks for reading.
Download Source Code
$ git clone https://github.com/favtuts/java-core-tutorials-examples
$ cd java-crypto