本文向您展示了几种转换字节数组或byte[]
转换为十六进制(基数为 16 或十六进制)字符串代表的方法。
String.format
Integer.toHexString
- Apache Commons 编解码器 –
commons-codec
- Spring 安全加密 –
spring-security-crypto
- 按位移位和屏蔽。(教育目的)
注意
Apache Commons-Codec 和 Spring Security Crypto 模块都使用类似的5. Bitwise shifting and masking
技术将字节数组转换为十六进制字符串,请研究下面的源代码,它对教育目的很有用。
1. 字符串格式 %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
as 参数并返回一个十六进制字符串。关键是将 转换byte
为 anint
并使用 a0xff
进行掩码以防止符号扩展。
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
}
}
马文。
<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
}
}
马文。
<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
a之后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
转成十进制,就是a 6
,看变量static final char[] HEX
,索引6的值是6,十六进制的前半部分是6。
5.1.2 HEX[(0x0F & aByte)]
(后半十六进制)
同一个字符a
,二进制是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
把这个二进制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;
}
}
例如,相同的 hex 61
、 character a
。
这Character.digit
是 JDK API,将 a 转换char
为基数 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
; 对于 int1
二进制文件是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
.
此代码段将二进制字符串转换为字符串,对于 binary 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
谢谢阅读。