Как в Java преобразовать массив байтов в строку шестнадцатеричных цифр, сохранив ведущие нули?

Я работаю с примером кода 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 явно отбрасывает ведущие нули. Итак, как проще всего перейти от байтового массива к шестнадцатеричной строке, в которой сохраняются ведущие нули?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
165
0
184 231
28
Перейти к ответу Данный вопрос помечен как решенный

Ответы 28

Вот что я использую для хешей 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();
}

Разве он не выдаст «10» для байта 0x01?

n0rd 21.10.2009 00:21

Нет, 0 добавляется к hexString раньше шестнадцатеричного значения.

Michael Myers 21.10.2009 01:22

Когда я позвонил в Integer.toHexString((byte)0xff), он вернул "ffffffff" из-за расширения знака. Таким образом, может потребоваться взять последние два символа возвращаемой строки.

Marvo 28.07.2012 00:55

Разве это не вернет лишние нули? Например, если байтовый массив - {0,1,2,3}, он должен вернуть 0123, но он вернет 00010203, или это желаемый результат хеширования?

Juzer Ali 21.01.2013 12:23

@juzerali: Этот вопрос требует «с сохранением ведущих нулей». Если вам не нужны начальные нули, нет причин использовать этот код; просто используйте код из вопроса.

Michael Myers 21.01.2013 19:06
String result = String.format("%0" + messageDigest.length + "s", hexString.toString())

Это самое короткое решение, учитывая то, что у вас уже есть. Если бы вы могли преобразовать массив байтов в числовое значение, String.format может одновременно преобразовать его в шестнадцатеричную строку.

Очень элегантно, за исключением требований Java 1.5+. Хотя сейчас это не проблема ...

Fernando Miguélez 01.12.2008 23:43

Не работает: исключение в потоке "main" java.util.FormatFlagsConversionMismatchException: Conversion = s, Flags = 0

Noah Yetter 22.04.2009 08:35
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 ()

rescdsk 17.04.2012 18:13

DigestUtils немного упрощает работу, но включение его в ваш проект может вызвать затруднения. Лично я стону при мысли о том, чтобы возиться с файлами pom.

Conor Pender 03.09.2012 18:40

Это решение немного старше школы и должно быть эффективным с точки зрения памяти.

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 шестнадцатеричных цифры, ваше решение будет сжато до простого однострочника. Здорово!

Roboprog 06.04.2013 05:06

Отлично работает, спасибо.

Lev 10.09.2015 16:41

Спасибо. Мне это нужно, чтобы преобразовать 16-байтовый массив байтов IPv6 в шестнадцатеричную строку с нулями в Scala: f"${BigInt(1, myIpv6ByteArray)}%032x".

Mark Rajcok 19.10.2016 01:42

Другой вариант

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

ComputerDruid 14.07.2014 17:17

Я искал то же самое ... здесь несколько хороших идей, но я провел несколько микротестов. Я обнаружил, что следующее является самым быстрым (изменено из приведенного выше Эймана и примерно в 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".

Daniel Lubarov 02.02.2012 03:45

BigInteger.toString () - это, безусловно, самый медленный способ, который я нашел в Java, примерно в 100 раз медленнее, чем эффективная реализация, см. stackoverflow.com/a/58118078/774398. Также в вашем ответе вычисляется хеш, но это не было частью вопроса.

Patrick Favre 30.10.2019 16:57

Это не то, о чем спрашивал OP, но хорошо, если вы ищете строку хеша и меняете последнее на while (length <ожидаемая длина) ... добавьте нули вперед (большинство хешей имеют ожидаемую длину, обычно некоторая степень двойки, например 128)

comodoro 30.11.2019 16:06

Похоже, что функции 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];) совершенно ненужны и расточительны.

Anm 11.04.2011 22:26

Двадцать мегабайт, смеется. Хотя байтов bytes.length * 4 хватило бы.

Robert 26.03.2012 09:25

Мне понравились материалы Стива, но он мог бы обойтись без пары переменных и в процессе сохранил несколько строк.

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, но я слишком ленив, чтобы тестировать это прямо сейчас.

Хороший! Улучшает идею Стива «добавить медленно», заставляя ее работать с любым массивом байтов произвольного размера.

Ogre Psalm33 08.12.2010 17:13

измените v / 16 на v >>> 4 и v% 16 на v & 0x0F, чтобы повысить скорость. Кроме того, вы можете использовать j << 1 для умножения на 2 (хотя компилятор, вероятно, сделает это за вас).

Scott Carey 10.01.2012 04:01

Или, что еще лучше, добавьте значение к «0», чтобы получить символ, чтобы таблица поиска не требовалась. например hexChars [j << 1] = (байт) (v >>> 4 + '0')

Scott Carey 10.01.2012 05:07

(моя ошибка! в таблице ASCII нет a-f или A-F после 0-9, предыдущее не сработает)

Scott Carey 10.01.2012 05:20

Обратная функция, может кому надо. общедоступный статический байт [] 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])); } вернуть результат; }

cn1h 04.11.2012 00:16

Версия с битовым сдвигом стала принятым ответом на stackoverflow.com/a/9855338

simbo1905 20.12.2014 21:30
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.

Robert 26.03.2012 09:19

Конечно, для переменной длины дайджеста вы должны использовать начальную емкость digest.length * 2.

Robert 26.03.2012 09:30

Чтобы сохранить ведущие нули, вот небольшой вариант того, что предложил Пол (например, хеш 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.

Anm 11.04.2011 22:30

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).

Sanghyun Lee 01.05.2016 11:23

Имейте в виду, что, начиная с Java 11, пакет java.xml больше не является частью JDK.

AndreasB 31.12.2018 13:48

Это даст длинную строку из двух символов для байта.

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!

skomisa 16.12.2018 09:29

Или вы можете сделать это:

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);

RoyM 11.02.2019 15:57

Другие вопросы по теме