本文介绍了几种将字节数组或byte[]
转换为十六进制 (基数16或十六进制)字符串表示形式的方法。
-
String.format
-
Integer.toHexString
- Apache Commons编解码器–
commons-codec
- Spring Security加密–
spring-security-crypto
- 按位移位和屏蔽。 (教育目的)
注意
Apache Commons-Codec和Spring Security Crypto模块都使用类似的5. Bitwise shifting and masking
技术将字节数组转换为十六进制字符串,请研究下面的源代码,这对于教育目的很有用。
1. String.format%02x
此String.format
是将字节数组转换为十六进制的最简单且显而易见的方法, %02x
用于小写十六进制, %02X
大写十六进制。
package com.mkyong.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 = "a";
System.out.println(hex(input.getBytes(StandardCharsets.UTF_8)));
}
}
输出量
61
2. Integer.toHexString
此Integer.toHexString(int i)
接受一个int
作为参数,并返回一个十六进制字符串。 密钥是将byte
转换为int
和带有0xff
掩码以防止符号扩展 。
package com.mkyong.crypto.bytes;
import java.nio.charset.StandardCharsets;
public class ByteToHexExample2 {
public static String hex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte aByte : bytes) {
int decimal = (int) aByte & 0xff; // bytes widen to int, need mask, prevent sign extension
// get last 8 bits
String hex = Integer.toHexString(decimal);
if (hex.length() % 2 == 1) { // if half hex, pad with zero, e.g \t
hex = "0" + hex;
}
result.append(hex);
}
return result.toString();
}
public static void main(String[] args) {
String input = "a";
System.out.println(hex(input.getBytes(StandardCharsets.UTF_8)));
}
}
输出量
61
3. Apache Commons编解码器
我们可以使用Hex.encodeHex
将byte[]
转换为十六进制字符串,或使用Hex.decodeHex
将十六进制字符串转换为byte[]
。
package com.mkyong.crypto.bytes;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import java.nio.charset.StandardCharsets;
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 = "a";
String hex = hex(input.getBytes(StandardCharsets.UTF_8));
System.out.println(hex); // 61
String unhex = unhex(hex);
System.out.println(unhex); // a
}
}
Maven。
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
4. Spring Security加密
在Spring Security中,我们可以使用Hex.encode
将byte[]
转换为十六进制字符串。
package com.mkyong.crypto.bytes;
import org.springframework.security.crypto.codec.Hex;
import java.nio.charset.StandardCharsets;
public class ByteToHexExample4 {
public static void main(String[] args) {
String input = "a";
char[] encode = Hex.encode(input.getBytes(StandardCharsets.UTF_8));
String hex = new String(encode);
System.out.println(hex); // 61
byte[] decode = Hex.decode(hex);
System.out.println(new String(decode)); // a
}
}
Maven。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
<version>5.3.2.RELEASE</version>
</dependency>
5.按位移位和屏蔽。
下面的源代码来自Spring Security Crypto模块, Apache Commons Codes使用类似的技术将字节数组转换为十六进制字符串,但有一些小的变化,例如不同的变量名或长度计算,其核心思想是相同的。
5.1十六进制编码。
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;
}
困难的部分是要理解以下两个语句。
HEX[(0xF0 & aByte) >>> 4];
HEX[(0x0F & aByte)];
5.1.1 HEX[(0xF0 & aByte) >>> 4]
(十六进制的前半部分)
例如,一个二进制字符a
为0110 0001
, bitwise AND
0xF0
,它变为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
逻辑右移4位0110 0000 >>> 4
,它变为0000 0110
。 阅读此Java >>和>>>按位移位运算符 。
0110 0000 |
???? 0110 | 0000 # >>> 4
0000 0110 | 0000 # >>> 4 (logical right shift, zero extension)
0000 0110 # result, the first half hex
将此二进制数0000 0110
转换为十进制,它是6
,查看变量static final char[] HEX
,索引6的值是6,十六进制的前半部分是6。
5.1.2 HEX[(0x0F & aByte)]
(后半十六进制)
相同的字符a
二进制为0110 0001
, bitwise AND
0x0F
。
0110 0001 # 0110 = first half hex, 0001 = second half hex
& # bitwise AND operator.
0000 FFFF # 0x0F = 0000 FFFF
0000 0001 # result 0000 0001
将此二进制数0000 00001
转换为十进制,为1
,再次查看变量static final char[] HEX
,索引1的值为1,十六进制的后半部分为1。
我们将十六进制的上半部分和下半部分相结合,即6 + 1,即为61
。 对于字符a
,十六进制为61
。
5.2十六进制解码。
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;
}
}
例如,相同的十六进制61
字符a
。
Character.digit
是JDK API,将char
转换为16或16进制并返回int
。
int msb = Character.digit(s.charAt(i), 16); // msb = 6
int lsb = Character.digit(s.charAt(i + 1), 16); // lsb = 1
注意
msb
和lsb
变量名称有些奇怪,我认为作者指的是前4位和后4位。
在Java int 6
,二进制值为0000 0110
; 对于int 1
二进制是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
最终的二进制文件是0110 0001
。
此代码段将二进制字符串转换为字符串,对于二进制0110 0001
,该字符串为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
谢谢阅读。
参考文献
翻译自: https://mkyong.com/java/java-how-to-convert-bytes-to-hex/