Как преобразовать массив байтов в шестнадцатеричную строку и обратно в C#?
Преобразование byte[] в hex-строку и обратно в C#
Отличный вопрос! Преобразование массива байтов в шестнадцатеричную строку — одна из самых частых задач при работе с криптографией, сетевыми протоколами или двоичными данными. Рассмотрим несколько надёжных способов, начиная с самых современных.
Способ 1: Convert.ToHexString и Convert.FromHexString (.NET 5+)
Начиная с .NET 5, в стандартной библиотеке появились встроенные методы, которые делают именно то, что вам нужно — без дефисов и лишних символов:
using System;
byte[] hashBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("test"));
// Преобразование byte[] -> hex-строка (без разделителей)
string hexString = Convert.ToHexString(hashBytes);
Console.WriteLine(hexString); // Вывод: "9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08"
// Преобразование hex-строка -> byte[]
byte[] restoredBytes = Convert.FromHexString(hexString);
// restoredBytes будет идентичен hashBytes
Важно: Эти методы работают только в .NET 5 и выше (включая .NET Core 3.1, но там они в пространстве имен System не доступны, нужно добавлять NuGet-пакет). Для старых версий используйте следующие способы.
Способ 2: BitConverter.ToString с удалением дефисов
Если вы используете старую версию .NET Framework, можно адаптировать ваш текущий код:
byte[] hashBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("test"));
// Убираем дефисы через Replace
string hexString = BitConverter.ToString(hashBytes).Replace("-", "");
// Или через ToLower, если нужны строчные буквы:
// string hexString = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
// Обратное преобразование — придется писать свой метод
byte[] restoredBytes = HexStringToByteArray(hexString);
// Вспомогательный метод для конвертации hex-строки в byte[]
static byte[] HexStringToByteArray(string hex)
{
if (hex.Length % 2 != 0)
throw new ArgumentException("Строка должна содержать четное количество символов");
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return bytes;
}
Способ 3: Ручная реализация для максимальной производительности
Если вы работаете с большими массивами данных (мегабайты и гигабайты), лучше использовать ручную реализацию без выделения множества промежуточных строк. Вот оптимизированный вариант:
// Преобразование byte[] -> hex-строка (быстро, без лишних аллокаций)
static string ByteArrayToHexString(byte[] bytes)
{
char[] result = new char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
int value = bytes[i];
result[i * 2] = GetHexChar(value >> 4); // старший полубайт
result[i * 2 + 1] = GetHexChar(value & 0x0F); // младший полубайт
}
return new string(result);
}
static char GetHexChar(int nibble)
{
return (char)(nibble < 10 ? '0' + nibble : 'A' + nibble - 10);
}
// Обратное преобразование (hex-строка -> byte[])
static byte[] HexStringToByteArrayFast(string hex)
{
if (hex.Length % 2 != 0)
throw new ArgumentException("Строка должна содержать четное количество символов");
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = (byte)((GetHexValue(hex[i * 2]) << 4) | GetHexValue(hex[i * 2 + 1]));
}
return bytes;
}
static int GetHexValue(char c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
throw new ArgumentException($"Недопустимый символ: {c}");
}
Что выбрать?
- .NET 5+ — используйте
Convert.ToHexString/Convert.FromHexString. Это самый простой и быстрый вариант. - .NET Framework 4.x — используйте
BitConverter.ToStringсReplace("-", "")и ручной метод для обратного преобразования. - Большие данные — ручная реализация из способа 3 даст лучшую производительность.
Теперь ваш код для сохранения и загрузки хеша будет выглядеть так:
// Сохранение
byte[] hashBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes("test"));
string hexString = Convert.ToHexString(hashBytes);
File.WriteAllText("hash.txt", hexString);
// Загрузка
string loadedHex = File.ReadAllText("hash.txt");
byte[] loadedHash = Convert.FromHexString(loadedHex);
<<Альтернативный подход: использование StringBuilder и Span
Если вы предпочитаете более читаемый код, чем ручное манипулирование символами, но при этом хотите избежать лишних выделений памяти, можно использовать StringBuilder с фиксированной ёмкостью. Этот метод особенно удобен, когда нужно конвертировать массив байтов в строку с произвольным форматированием (например, с разделителями или разным регистром).
Пример с StringBuilder:
using System.Text;
static string ByteArrayToHexString(byte[] bytes)
{
var sb = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
{
sb.Append(b.ToString("X2")); // "X2" — шестнадцатеричный формат с ведущим нулём
}
return sb.ToString();
}
// Обратное преобразование можно оставить из первого ответа
static byte[] HexStringToByteArray(string hex)
{
int length = hex.Length;
byte[] bytes = new byte[length / 2];
for (int i = 0; i < length; i += 2)
{
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return bytes;
}
Использование Span для высокой производительности (.NET Core 2.1+)
Для максимальной эффективности при работе с большими объёмами данных можно использовать Span и Span:
static string ByteArrayToHexString(ReadOnlySpan bytes)
{
Span chars = stackalloc char[bytes.Length * 2];
for (int i = 0; i < bytes.Length; i++)
{
byte b = bytes[i];
chars[i * 2] = "0123456789ABCDEF"[b >> 4];
chars[i * 2 + 1] = "0123456789ABCDEF"[b & 0x0F];
}
return new string(chars);
}
Недостаток: stackalloc может вызвать переполнение стека при очень больших массивах (больше ~256 КБ). Для таких случаев лучше использовать ArrayPool или массив в куче.
В целом, для 99% задач достаточно встроенного Convert.ToHexString — он оптимизирован и протестирован Microsoft.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Конкурентность и многопоточность в JavaScript: Полное руководство