Я работаю с примером кода Java для создания хэшей md5. Одна часть преобразует результаты из байтов в строку шестнадцатеричных цифр:
byte messageDigest[] = algorithm.digest();
StringBuffer hexString = new StringBuffer();
for (int i=0;i<messageDigest.length;i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}
Однако это не совсем работает, поскольку toHexString явно отбрасывает ведущие нули. Итак, как проще всего перейти от байтового массива к шестнадцатеричной строке, в которой сохраняются ведущие нули?




Вот что я использую для хешей 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();
}
Нет, 0 добавляется к hexString раньше шестнадцатеричного значения.
Когда я позвонил в Integer.toHexString((byte)0xff), он вернул "ffffffff" из-за расширения знака. Таким образом, может потребоваться взять последние два символа возвращаемой строки.
Разве это не вернет лишние нули? Например, если байтовый массив - {0,1,2,3}, он должен вернуть 0123, но он вернет 00010203, или это желаемый результат хеширования?
@juzerali: Этот вопрос требует «с сохранением ведущих нулей». Если вам не нужны начальные нули, нет причин использовать этот код; просто используйте код из вопроса.
String result = String.format("%0" + messageDigest.length + "s", hexString.toString())
Это самое короткое решение, учитывая то, что у вас уже есть. Если бы вы могли преобразовать массив байтов в числовое значение, String.format может одновременно преобразовать его в шестнадцатеричную строку.
Очень элегантно, за исключением требований Java 1.5+. Хотя сейчас это не проблема ...
Не работает: исключение в потоке "main" java.util.FormatFlagsConversionMismatchException: Conversion = s, Flags = 0
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);
}
Проверьте Hex.encodeHexString из Кодек Apache Commons.
import org.apache.commons.codec.binary.Hex;
String hex = Hex.encodeHexString(bytes);
И пока вы делаете md5 с использованием кодека Apache Commons, взгляните на DigestUtils.md5Hex ()
DigestUtils немного упрощает работу, но включение его в ваш проект может вызвать затруднения. Лично я стону при мысли о том, чтобы возиться с файлами pom.
Это решение немного старше школы и должно быть эффективным с точки зрения памяти.
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);
}
Если вам нужны шестнадцатеричные цифры в нижнем регистре, используйте "x" в формате String.
Никаких внешних зависимостей, красиво и коротко. Кроме того, если вы знаете, что у вас есть 16 байтов / 32 шестнадцатеричных цифры, ваше решение будет сжато до простого однострочника. Здорово!
Отлично работает, спасибо.
Спасибо. Мне это нужно, чтобы преобразовать 16-байтовый массив байтов IPv6 в шестнадцатеричную строку с нулями в Scala: f"${BigInt(1, myIpv6ByteArray)}%032x".
Другой вариант
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» .. «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"};
@Marvo 0x000000FF == 0xFF, поэтому предлагаемое вами изменение ничего не дает. Маска - это просто int, как и любое другое число. 0xFF! = -1
Я искал то же самое ... здесь несколько хороших идей, но я провел несколько микротестов. Я обнаружил, что следующее является самым быстрым (изменено из приведенного выше Эймана и примерно в 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;
}
Это все еще неправильно. Например, если хеш-код - {0, 0, 0, 0}, BigIntegertoString просто выдаст "0". Этот код добавляет еще один "0" и возвращает "00", но результат должен быть "00000000".
BigInteger.toString () - это, безусловно, самый медленный способ, который я нашел в Java, примерно в 100 раз медленнее, чем эффективная реализация, см. stackoverflow.com/a/58118078/774398. Также в вашем ответе вычисляется хеш, но это не было частью вопроса.
Это не то, о чем спрашивал OP, но хорошо, если вы ищете строку хеша и меняете последнее на while (length <ожидаемая длина) ... добавьте нули вперед (большинство хешей имеют ожидаемую длину, обычно некоторая степень двойки, например 128)
Похоже, что функции concat и append могут быть очень медленными. Следующее было НАМНОГО быстрее для меня (чем мой предыдущий пост). Переход на массив символов при построении вывода был ключевым фактором для его ускорения. Я не сравнивал с 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); }
Эти два мегабайта (= new char[10000000];) совершенно ненужны и расточительны.
Двадцать мегабайт, смеется. Хотя байтов bytes.length * 4 хватило бы.
Мне понравились материалы Стива, но он мог бы обойтись без пары переменных и в процессе сохранил несколько строк.
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-битный полубайт и превращает его в шестнадцатеричный символ. И он использует поиск по таблице, так что, вероятно, это быстро. Вероятно, это могло бы быть быстрее, если бы вы заменили v / 16 и v% 16 на битовые сдвиги и AND, но я слишком ленив, чтобы тестировать это прямо сейчас.
Хороший! Улучшает идею Стива «добавить медленно», заставляя ее работать с любым массивом байтов произвольного размера.
измените v / 16 на v >>> 4 и v% 16 на v & 0x0F, чтобы повысить скорость. Кроме того, вы можете использовать j << 1 для умножения на 2 (хотя компилятор, вероятно, сделает это за вас).
Или, что еще лучше, добавьте значение к «0», чтобы получить символ, чтобы таблица поиска не требовалась. например hexChars [j << 1] = (байт) (v >>> 4 + '0')
(моя ошибка! в таблице ASCII нет a-f или A-F после 0-9, предыдущее не сработает)
Обратная функция, может кому надо. общедоступный статический байт [] bytesFromHex (String hexString) {final char [] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char [] hexChars = hexString.toCharArray (); byte [] результат = новый байт [hexChars.length / 2]; for (int j = 0; j <hexChars.length; j + = 2) {result [j / 2] = (byte) (Arrays.binarySearch (hexArray, hexChars [j]) * 16 + Arrays.binarySearch (hexArray, hexChars [j + 1])); } вернуть результат; }
Версия с битовым сдвигом стала принятым ответом на stackoverflow.com/a/9855338
static String toHex(byte[] digest) {
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%1$02X", b));
}
return sb.toString();
}
По умолчанию начальная емкость StringBuilder составляет 16 символов. Хеш MD5 состоит из 32 символов. После добавления первых 16 символов внутренний массив будет скопирован в новый массив длиной 34. Также String.format создает новый экземпляр Formatter для каждого байта дайджеста. И по умолчанию каждый Formatter создает новый StringBuilder для буферизации своего вывода. Я даже думаю, что проще создать только один Formatter с StringBuffer с начальной емкостью 32 символа (new Formatter(new StringBuilder(32))) и использовать его методы format и toString.
Конечно, для переменной длины дайджеста вы должны использовать начальную емкость digest.length * 2.
Чтобы сохранить ведущие нули, вот небольшой вариант того, что предложил Пол (например, хеш md5):
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();
}
Мне было бы очень интересно посмотреть, как это соотносится с решением Jemenake.
IMHO все вышеперечисленные решения, которые предоставляют фрагменты для удаления начальных нулей, неверны.
byte messageDigest[] = algorithm.digest();
for (int i = 0; i < messageDigest.length; i++) {
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
}
Согласно этому фрагменту, 8 бит берутся из массива байтов в итерация, преобразованная в целое число (поскольку функция Integer.toHexString принимает int в качестве аргумента), а затем это целое число преобразуется в соответствующий хэш ценить. Так, например, если у вас есть 00000001 00000001 в двоичном формате, согласно код, переменная 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(), часть Архитектура Java для привязки XML (JAXB), был удобным способом преобразования byte[] в шестнадцатеричную строку. Класс DatatypeConverter также включает множество других полезных методов манипулирования данными.
В Java 8 и ранее JAXB был частью стандартной библиотеки Java. Это был устарел с Java 9 и удаленный с Java 11, как часть попытки переместить все пакеты Java EE в их собственные библиотеки. Это долгая история. Теперь javax.xml.bind не существует, и если вы хотите использовать JAXB, который содержит DatatypeConverter, вам необходимо установить JAXB API и Среда выполнения JAXB из Maven.
Пример использования:
byte bytes[] = {(byte)0, (byte)0, (byte)134, (byte)0, (byte)61};
String hex = javax.xml.bind.DatatypeConverter.printHexBinary(bytes);
В результате получится:
000086003D
Для реверса тоже есть DatatypeConverter.parseHexBinary(hexString).
Имейте в виду, что, начиная с Java 11, пакет java.xml больше не является частью JDK.
Это даст длинную строку из двух символов для байта.
public String toString(byte b){
final char[] Hex = new String("0123456789ABCDEF").toCharArray();
return "0x"+ Hex[(b & 0xF0) >> 4]+ Hex[(b & 0x0F)];
}
И как вы можете преобразовать обратно из ascii в байтовый массив?
Я выполнил следующий код для преобразования в ascii, предоставленный Jemenake.
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.
Гуава тоже делает это довольно просто:
BaseEncoding.base16().encode( bytes );
Это хорошая альтернатива, когда Apache Commons недоступен. Он также имеет несколько хороших элементов управления выводом, например:
byte[] bytes = new byte[] { 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
BaseEncoding.base16().lowerCase().withSeparator( ":", 2 ).encode( bytes );
// "0a:0b:0c:0d:0e:0f"
Я удивлен, что никто не придумал такого решения:
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 в маске выполняет заполнение ...
Однострочное решение, использующее только стандартную Java!
Или вы можете сделать это:
byte[] digest = algorithm.digest();
StringBuilder byteContet = new StringBuilder();
for(byte b: digest){
byteContent = String.format("%02x",b);
byteContent.append(byteContent);
}
Это коротко, просто и в основном просто смена формата.
Почти там .. за исключением того, что байтовые значения подписаны (-128 -> 127), поэтому вам нужно следующее изменение: byteContent = String.format("%02x",b&0xff);
Разве он не выдаст «10» для байта 0x01?