Зачем нужны массивы байтов byte[]?
Сабж.
Часто вижу в примерах как используют byte[], например при отправке POST запроса.
Я не понимаю зачем это нужно, это рудимент низкоуровневого программирования?
На сколько я знаю String это массив Char, каждый символ представлен в памяти своим ASCII кодом, что происходит когда эта строка преобразовывается в byte[]?
Вот к примеру это используется в отправке POST запроса, но на той стороне у меня из коробки контроллер принимает модель и сразу создается объект. Майкрософт рекомендует использовать TPL в новых разработках вместо Thread, так как Thread это более низкий уровень.
TPL отдает на откуп работу с потоками в CLI, почему так же не сделать с byte[]?
Вообщем я запутался.
Пример где мне кажется странным использование массива байтов
https://docs.microsoft.com/ru-ru/dotnet/standard/security/encrypting-data
class Class1
{
static void Main()
{
//Initialize the byte arrays to the public key information.
byte[] modulus =
{
214,46,220,83,160,73,40,39,201,155,19,202,3,11,191,178,56,
74,90,36,248,103,18,144,170,163,145,87,54,61,34,220,222,
207,137,149,173,14,92,120,206,222,158,28,40,24,30,16,175,
108,128,35,230,118,40,121,113,125,216,130,11,24,90,48,194,
240,105,44,76,34,57,249,228,125,80,38,9,136,29,117,207,139,
168,181,85,137,126,10,126,242,120,247,121,8,100,12,201,171,
38,226,193,180,190,117,177,87,143,242,213,11,44,180,113,93,
106,99,179,68,175,211,164,116,64,148,226,254,172,147
};
byte[] exponent = { 1, 0, 1 };
//Create values to store encrypted symmetric keys.
byte[] encryptedSymmetricKey;
byte[] encryptedSymmetricIV;
//Create a new instance of the RSA class.
RSA rsa = RSA.Create();
//Create a new instance of the RSAParameters structure.
RSAParameters rsaKeyInfo = new RSAParameters();
//Set rsaKeyInfo to the public key values.
rsaKeyInfo.Modulus = modulus;
rsaKeyInfo.Exponent = exponent;
//Import key parameters into rsa.
rsa.ImportParameters(rsaKeyInfo);
//Create a new instance of the default Aes implementation class.
Aes aes = Aes.Create();
//Encrypt the symmetric key and IV.
encryptedSymmetricKey = rsa.Encrypt(aes.Key, RSAEncryptionPadding.Pkcs1);
encryptedSymmetricIV = rsa.Encrypt(aes.IV, RSAEncryptionPadding.Pkcs1);
}
}
Открытый ключ это просто число, зачем оно в байтах?
Вот пример с метанита, отправка POST запроса, зачем я тут перевожу строку в байты? Не ну я понимаю что метод принимает массив байтов, но почему он не может принять просто строку и у себя там сделать с ней все что нужно?
WebRequest request = WebRequest.Create("https://localhost:44391/api/Order/");
request.Method = "POST"; // для отправки используется метод Post
// данные для отправки
string data = _data;
// преобразуем данные в массив байтов
byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(data);
// устанавливаем тип содержимого - параметр ContentType
request.ContentType = "application/json";
// Устанавливаем заголовок Content-Length запроса - свойство ContentLength
request.ContentLength = byteArray.Length;
//записываем данные в поток запроса
using (Stream dataStream = request.GetRequestStream())
{
dataStream.Write(byteArray, 0, byteArray.Length);
}
WebResponse response = await request.GetResponseAsync();
using (Stream stream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(stream))
{
d = reader.ReadToEnd();
}
}
response.Close();
Ответы (3 шт):
Ваш вопрос распадается на две части.
Во-первых, по поводу криптографии. Здесь представление ключей в виде чисел, хотя и возможно, ничего бы не дало. Для нас, пользователей криптографии, вся работа заключается в преобразовании наборов байт. Мы не пользуемся ни возможностями сложения/умножения, ни десятичным представлением ни ключей шифрования, ни шифруемых сообщений. Фактически криптография работает с наборами байт, и дополнительные операции, которые нам позволяют числа, привели бы лишь к вопросам «а что будет, если сложить два ключа» и. т. п.
Хотя ключи и можно представить числами достаточно большой разрядности, это не несёт смысла точно так же, как не несёт смысла представление файлов в виде чисел.
По поводу второй части, вы правы, API, которое вы приводите в пример, несколько устарело. Сейчас принято писать как-то так:
var client = new HttpClient() { BaseAddress = new Uri("http://httpbin.org") };
var request = new RequestObject(...);
var result = await client.PostAsJsonAsync("/post", request);
var reply = await result.Content.ReadFromJsonAsync<Response>();
Как вы видите, детали наподобие сериализации объектов в JSON, и составление нужного HttpContent'а спрятаны за удобными методами расширения, которые всё сделают за вас.
Впрочем, если вы захотите, низкоуровневые возможности и полный контроль никто у вас не отнимает, и вы можете составить HttpContent сами.
String это массив Char, каждый символ представлен в памяти своим ASCII кодом
Неверно, string хранится в памяти как последовательность двухбайтовых char в кодировке UTF-16, а не ASCII.
Вас никто не заставляет вручную перекодировать в массив байт, особенно если вы используете современные сетевые API. WebRequest устарел, при чем сильно.
private static readonly HttpClient client = new HttpClient();
public async Task<string> PostJsonAsync(string url, string json)
{
using HttpContent content = new StringContent(json, Encoding.UTF8, "application/json");
using var response = await client.PostAsync(url, content);
return await response.Content.ReadAsStringAsync();
}
Видите - я ничего не перекодирую. Но это не значит, что перекодировка не происходит. Просто она где-то спрятана внутри кода .NET.
В памяти строка хранится в UTF-16, а отправляется как UTF-8. При чем и хранится в памяти строка как байты, и отправляется как байты. Мы же все знаем, что есть только байты, и ничего кроме байтов в памяти быть не может. Как и по каналу связи ничего кроме байтов отправлено или принято быть не может. Просто .NET удобный, и не требует вручную выполнять перекодировки.
Можно ли string отправить без перекодировок "как есть"? Легко, правда потребуется .NET Core или .NET 5, потому что в старых фреймворках придется поплясать с бубном вокруг небезопасного кода, чтобы получилось.
Можно конечно через Encoding.Unicode.GetBytes(text), но я не буду использовать для наглядности, хотя результат в массиве байт будет идентичен.
string text = "Hello World";
ReadOnlySpan<byte> bytes = MemoryMarshal.Cast<char, byte>(text);
using HttpContent content = new ByteArrayContent(bytes.ToArray());
// и далее то же самое как выше
Если бы конструктор ByteArrayContent принимал в качестве аргумента ReadOnlySpan, можно было бы отправить в сеть прямо содержимое памяти строки, не то что без преобразования - а даже без копирования данных, "как есть". Конечно можно обойти это ограничение через StreamContent, создав собственный Stream, который будет читать непосредственно память из string, но это получится много кода и просто ради того чтобы показать, что это возможно, я не буду его писать.
По поводу, зачем число в байты преобразовывать, оно типа и так байты. Ну вот например, есть Int32 - 4 байта. Так как Socket умеет отправлять только байты, то чтобы отправить это число по сети, надо его в байты и преобразовать. Если особо извратнуться, можно как и со строкой - отправить эти байты читая содержимое ровно той ячейки оперативной памяти, где лежит этот Int32. Правда есть проблема, в определенных ситуациях некая локальная переменная с тем же интом может быть на низком уровне вообще не быть в памяти, а например храниться в регистре процессора, а отправить по сети можно только память, и поэтому в качестве источника данных для отправки по сети и используется массив байт, как самый примитивный указатель на участок памяти с данными в .NET.
Что касается криптографии. Самое длинное целое число в .NET как примитив - Int64 - 8 байт. А 8 байт для представления ключей и прочих криптографических данных - это даже не смешно. Даже древний хэш MD5 - 16 байт. Потом массивы байт можно легко очистить, своевременно избавившись от секретных данных из памяти. Кстати именно потому что строку string нельзя очистить (она не мутабельна) - в ней и не рекомендуется хранить ключ или пароль для шифрования. Поэтому в криптографии везде массивы. Я кстати недавно писал код для алгоритма шифрования пароля Apache MD5, так я замучался, чтобы свести к минимуму копирование байт в памяти туда-сюда, как минимум ради производительности, в этом мне сильно помогли те же спаны Span<T>/ReadOnlySpan<T>.
Рассмотрим сначала более простой пример. Вы хотите переслать Int32 по сети или записать в файл. Когда вы работаете с абстрактным типом данных, коим является Int32, то вас как программиста, не интересует, как именно он представлен в памяти. Это деталь реализации, которая может отличаться от платформы к платформе. Вам важно какие значения он может принимать и какие операции поддерживает.
Так уж исторически сложилось (об этом чуть позже), что по сети можно пересылать только байты. Просто скопировав представление Int32 в байтах из памяти и переслав по сети возникнет проблема на принимающей стороне. А что если на принимающей стороне другой способ представления Int32? Там вообще может быть не .NET приложение, а 8-битный микроконтроллер, который умеет работать только с байтами. Это одна из причин почему по сети пересылаются байты - для интероперабельности, т.е. совместимости разных систем. Байты используются как наименьший элемент, который понимают все операционные системы.
То есть проблема в том, что представить 32 разрядный int в виде 4 байт можно не одним способом, а как минимум двумя, которые отличаются порядком байт. И вам нужно задать какой из способов вы хотите выбрать. Тут вы используете кодирование абстрактного типа Int32 в байты. Принимающая и передающая сторона должны договориться, как интерпретировать поток байт, который передается по сети, или хранится в файле.
Со строками точно та же проблема, только еще хуже. Во-первых, строка сама по себе это еще более абстрактная вещь чем число (числа в некотором диапазоне можно хотя бы хранить прямо в памяти). А строка состоит из символов, а символ напрямую в памяти хранить нельзя, можно его закодировать какой-то последовательностью байт. И опять же, способ кодирования - это деталь реализации.
Точно так же как и с Int32 просто послать строку по сети нельзя, можно только послать строку преобразованную в байты используя какой-то способ кодирования. Тут язык программирования и/или библиотеки могут предоставить средства, как указано в других ответах, чтобы упростить жизнь и не делать вручную перекодирование, но указать кодировку (явно или неявно) придется обязательно.