在 Java 中,如何在保持前导零的同时将字节数组转换为十六进制数字字符串?

我正在处理一些制作 md5散列的示例 Java 代码。其中一部分将结果从字节转换为十六进制数字字符串:

byte messageDigest[] = algorithm.digest();
StringBuffer hexString = new StringBuffer();
for (int i=0;i<messageDigest.length;i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}

然而,由于 toHexString 显然会丢失前导零,因此它并不能很好地工作。那么,从字节数组到保持前导零的十六进制字符串最简单的方法是什么呢?

198680 次浏览

这就是我对 MD5散列所使用的方法:

public static String getMD5(String filename)
throws NoSuchAlgorithmException, IOException {
MessageDigest messageDigest =
java.security.MessageDigest.getInstance("MD5");


InputStream in = new FileInputStream(filename);


byte [] buffer = new byte[8192];
int len = in.read(buffer, 0, buffer.length);


while (len > 0) {
messageDigest.update(buffer, 0, len);
len = in.read(buffer, 0, buffer.length);
}
in.close();


return new BigInteger(1, messageDigest.digest()).toString(16);
}

编辑: 我已经测试过了,我注意到这个后面的零也被删除了。但是这只能在开始时发生,因此您可以相应地比较预期的长度和垫。

一个简单的方法是检查 Integer.toHexString()输出了多少位数字,如果需要的话,在每个字节上加一个前导零。就像这样:

public static String toHexString(byte[] bytes) {
StringBuilder hexString = new StringBuilder();


for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}


return hexString.toString();
}
String result = String.format("%0" + messageDigest.length + "s", hexString.toString())

考虑到你已经拥有的,这是最短的解决方案。如果可以将字节数组转换为数值,则 String.format可以同时将其转换为十六进制字符串。

byte messageDigest[] = algorithm.digest();
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < messageDigest.length; i++) {
String hexByte = Integer.toHexString(0xFF & messageDigest[i]);
int numDigits = 2 - hexByte.length();
while (numDigits-- > 0) {
hexString.append('0');
}
hexString.append(hexByte);
}

Apache Commons 编解码器Hex.encodeHexString

import org.apache.commons.codec.binary.Hex;


String hex = Hex.encodeHexString(bytes);

这个解决方案有点老派,应该具有内存效率。

public static String toHexString(byte bytes[]) {
if (bytes == null) {
return null;
}


StringBuffer sb = new StringBuffer();
for (int iter = 0; iter < bytes.length; iter++) {
byte high = (byte) ( (bytes[iter] & 0xf0) >> 4);
byte low =  (byte)   (bytes[iter] & 0x0f);
sb.append(nibble2char(high));
sb.append(nibble2char(low));
}


return sb.toString();
}


private static char nibble2char(byte b) {
byte nibble = (byte) (b & 0x0f);
if (nibble < 10) {
return (char) ('0' + nibble);
}
return (char) ('a' + nibble - 10);
}

你可以用下面这个。我用前导零字节和初始负字节测试了它

public static String toHex(byte[] bytes) {
BigInteger bi = new BigInteger(1, bytes);
return String.format("%0" + (bytes.length << 1) + "X", bi);
}

如果需要小写的十六进制数字,请使用格式为 String 的 "x"

另一个选择

public static String toHexString(byte[]bytes) {
StringBuilder sb = new StringBuilder(bytes.length*2);
for(byte b: bytes)
sb.append(Integer.toHexString(b+0x800).substring(1));
return sb.toString();
}

我发现 Integer.toHexString 有点慢。如果要转换许多字节,可能需要考虑构建一个包含“00”的 String 数组。.“ FF”并使用整数作为索引。也就是说。

hexString.append(hexArray[0xFF & messageDigest[i]]);

这样更快,并确保正确的长度。只需要字符串数组:

String[] hexArray = {
"00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
"10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
"20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
"30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
"40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
"50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
"60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
"70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
"80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F",
"90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F",
"A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF",
"B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF",
"C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF",
"D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF",
"E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF",
"F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF"};

我一直在寻找同样的东西... 这里有一些好的想法,但我运行了一些微观基准。我发现以下是最快的(从艾曼的上面修改,大约2倍的速度,大约50% 的速度比史蒂夫的只是高于这一个) :

public static String hash(String text, String algorithm)
throws NoSuchAlgorithmException {
byte[] hash = MessageDigest.getInstance(algorithm).digest(text.getBytes());
return new BigInteger(1, hash).toString(16);
}

编辑: 哎呀,错过了,这本质上是相同的 kgiannakakis 的,所以可能脱去一个前导0。尽管如此,将其修改如下,它仍然是最快的:

public static String hash(String text, String algorithm)
throws NoSuchAlgorithmException {
byte[] hash = MessageDigest.getInstance(algorithm).digest(text.getBytes());
BigInteger bi = new BigInteger(1, hash);
String result = bi.toString(16);
if (result.length() % 2 != 0) {
return "0" + result;
}
return result;
}

似乎 concat 和 append 函数的速度非常慢。下面的内容对我来说(比我之前的文章)要快得多。在构建输出时更改为字符数组是加快速度的关键因素。我还没有比较过 Brandon DuRette 建议的 Hex.encodeHex。

public static String toHexString(byte[] bytes) {
char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[10000000];
int c = 0;
int v;
for ( j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[c] = hexArray[v/16];
c++;
hexChars[c] = hexArray[v%16];
c++;
}
return new String(hexChars, 0, c); }

我喜欢史蒂夫的提交,但他可以做没有一对夫妇的变量,并节省了几行在这个过程中。

public static String toHexString(byte[] bytes) {
char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j*2] = hexArray[v/16];
hexChars[j*2 + 1] = hexArray[v%16];
}
return new String(hexChars);
}

我喜欢它的地方在于,它很容易看到它到底在做什么(而不是依赖于一些神奇的 BigInteger 黑盒转换) ,你也不用担心像前导零之类的角落情况。这个例程接受每一个4位字符并将其转换为一个十六进制字符。它使用表查找,所以可能很快。如果用位移和 AND 替换 v/16和 v% 16,可能会更快,但我现在懒得测试它。

static String toHex(byte[] digest) {
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%1$02X", b));
}


return sb.toString();
}

为了保持前导零,下面是 Paul 建议的一个小变体(例如 md5 hash) :

public static String MD5hash(String text) throws NoSuchAlgorithmException {
byte[] hash = MessageDigest.getInstance("MD5").digest(text.getBytes());
return String.format("%032x",new BigInteger(1, hash));
}

哎呀,这看起来比艾曼提议的更糟糕,对不起

static String toHex(byte[] digest) {
String digits = "0123456789abcdef";
StringBuilder sb = new StringBuilder(digest.length * 2);
for (byte b : digest) {
int bi = b & 0xff;
sb.append(digits.charAt(bi >> 4));
sb.append(digits.charAt(bi & 0xf));
}
return sb.toString();
}

恕我直言,以上提供去除前导零的代码片段的所有解决方案都是错误的。

byte messageDigest[] = algorithm.digest();
for (int i = 0; i < messageDigest.length; i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}

根据此代码片段,从 迭代,转换为一个整数(因为 Integer.toHexString 函数采用 Int 作为参数) ,然后将该整数转换为相应的哈希 所以,例如,如果您有000000100000001二进制,根据 在代码中,hexString 变量将0x11作为十六进制值,而 正确的值应该是0x0101。因此,在计算 MD5时,我们可能会得到散列 长度 < 32字节(因为缺少零) ,可能无法满足 MD5哈希所具有的加密唯一属性。

该问题的解决方案是将上面的代码段替换为 以下片段:

byte messageDigest[] = algorithm.digest();
for (int i = 0; i < messageDigest.length; i++) {
int temp=0xFF & messageDigest[i];
String s=Integer.toHexString(temp);
if(temp<=0x0F){
s="0"+s;
}
hexString.append(s);
}

这也是等价的,但是使用 Apache util HexBin更简洁,其中代码简化为

HexBin.encode(messageDigest).toLowerCase();

如果没有外部库,你可以让它写得更少:

String hex = (new HexBinaryAdapter()).marshal(md5.digest(YOUR_STRING.getBytes()))

这个解决方案不需要位移或屏蔽、查找表或外部库,并且大约是我能得到的最短的:

byte[] digest = new byte[16];


Formatter fmt = new Formatter();
for (byte b : digest) {
fmt.format("%02X", b);
}


fmt.toString()

强 > javax.xml.bind.DatatypeConverter.printHexBinary()方法是 JAXB (JAXB)的一部分,它是将 byte[]转换为十六进制字符串的一种方便的方法。DatatypeConverter类还包括许多其他有用的数据操作方法。

在 Java8和更早的版本中,JAXB 是 Java 标准库的一部分。它是 用 Java 9弃用,用 Java 11删除,作为将所有 JavaEE 包转移到它们自己的库的努力的一部分。说来话长.现在,javax.xml.bind不存在,如果您想使用包含 DatatypeConverter的 JAXB,您需要从 Maven 安装 JAXB APIJAXB 运行时

示例用法:

byte bytes[] = {(byte)0, (byte)0, (byte)134, (byte)0, (byte)61};
String hex = javax.xml.bind.DatatypeConverter.printHexBinary(bytes);

将导致:

000086003D

这将为一个字节提供两个字符的长字符串。

public String toString(byte b){
final char[] Hex = new String("0123456789ABCDEF").toCharArray();
return  "0x"+ Hex[(b & 0xF0) >> 4]+ Hex[(b & 0x0F)];
}

如何再次将 ascii 转换回字节数组?

我按照下面的代码转换为由 Jemenake 提供的 ascii。

public static String toHexString(byte[] bytes) {
char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for ( int j = 0; j < bytes.length; j++ ) {
v = bytes[j] & 0xFF;
hexChars[j*2] = hexArray[v/16];
hexChars[j*2 + 1] = hexArray[v%16];
}
return new String(hexChars);
}

我的变种

    StringBuilder builder = new StringBuilder();
for (byte b : bytes)
{
builder.append(Character.forDigit(b/16, 16));
builder.append(Character.forDigit(b % 16, 16));
}
System.out.println(builder.toString());

对我有用。

这是一个错误的解决方案吗? (android java)

    // Create MD5 Hash
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
digest.update(s.getBytes());
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
String stringMD5 = bigInt.toString(16);
// Fill to 32 chars
stringMD5 = String.format("%32s", stringMD5).replace(' ', '0');
return stringMD5;

所以基本上它用0代替空格。

我很惊讶竟然没有人想出以下的解决办法:

StringWriter sw = new StringWriter();
com.sun.corba.se.impl.orbutil.HexOutputStream hex = new com.sun.corba.se.impl.orbutil.HexOutputStream(sw);
hex.write(byteArray);
System.out.println(sw.toString());

我会使用固定长度的类似代码,比如散列:

md5sum = String.format("%032x", new BigInteger(1, md.digest()));

面具里的 0可以填充..。

或者你可以这样做:

byte[] digest = algorithm.digest();
StringBuilder byteContet = new StringBuilder();
for(byte b: digest){
byteContent = String.format("%02x",b);
byteContent.append(byteContent);
}

它简短,简单,基本上只是一个格式的变化。