Ошибка .NET при преобразовании строки в байт []?

#.net #string #bytearray

#.net #строка #массивы

Вопрос:

У меня возникла проблема с извлечением зашифрованных данных из поля NVARCHAR в нашей базе данных SQL Server (2008R2), где оказалось, что для некоторых записей строковое значение данных в моем приложении C # .NET отличается от значения в записи базы данных. Это было довольно сложно доказать, но в конце концов я обнаружил, что, посмотрев на представление строк в байтах [], различия действительно были.

Продолжая играть, я смог создать это тестовое приложение, которое меня немного беспокоит. Я взял массив байт (преобразованный из шестнадцатеричного для простоты настройки), преобразовал его в строку с кодировкой Unicode и обратно в массив байт и увидел, что результирующий массив байт отличается от исходного! В приведенном ниже коде первая шестнадцатеричная строка выполняется с ошибкой, в то время как вторая работает.

Что-то не так с моим методом здесь (и я не имею в виду попытку преобразовать массивы байтов в строки) или потенциально что-то не так в .NET framework?

 using System;

namespace ByteArrayTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WindowWidth = 80;
            Console.Clear();

            foreach (string s in new string[]
                {
                    "00CD6C8300C2A2C09B9E6B1F258F7B1101000000AB4CB23EBE32F0DD",
                    "00CD6C8300C2A2C09B9E6B1F258F7B1101000000E12617F83C3F7F6A"
                }
            )
            {
                byte[] b1 = System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary.Parse(s).Value;
                string tmp = System.Text.Encoding.Unicode.GetString(b1);

                byte[] b2 = System.Text.Encoding.Unicode.GetBytes(tmp);

                Console.WriteLine("Orig: {0}", s);

                string s2 = BitConverter.ToString(b2).Replace("-", "");
                Console.WriteLine("Conv: {0}", s2);

                Console.WriteLine(s == s2 ? "EQUAL :-)" : "** NOT EQUAL **");
                Console.WriteLine();
            }

            Console.WriteLine("Press ENTER to exit...");
            Console.ReadLine();
        }
    }
}
  

Я использую VS2010 и протестировал это в .NET framework 4 и 3.5, и результаты этого таковы:

 Orig: 00CD6C8300C2A2C09B9E6B1F258F7B1101000000AB4CB23EBE32F0DD
Conv: 00CD6C8300C2A2C09B9E6B1F258F7B1101000000AB4CB23EBE32FDFF
** NOT EQUAL **

Orig: 00CD6C8300C2A2C09B9E6B1F258F7B1101000000E12617F83C3F7F6A
Conv: 00CD6C8300C2A2C09B9E6B1F258F7B1101000000E12617F83C3F7F6A
EQUAL :-)
  

С уважением,

Ответ №1:

Если вы пытаетесь сохранить произвольные непрозрачные двоичные данные, которые на самом деле не являются текстом в поле NVARCHAR, вам следует использовать кодировку base64 для их кодирования. Попытка просто обработать это как текстовую кодировку в UTF-16 (что вы здесь и делаете) — принципиально плохая идея, и с большой вероятностью приведет к потере данных. В качестве одного примера того, где это могло произойти, вы могли бы получить строку, которая содержит половину суррогатной пары без другой половины.

Я предполагаю, что ваши «зашифрованные данные» были сохранены простым вызовом, Encoding.Unicode.GetString(bytes) где bytes находятся зашифрованные данные? Если да, то это определенно не тот путь, которым нужно идти. Использовать:

 string text = Convert.ToBase64String(bytes);
  

вместо этого и при извлечении данных используйте

 byte[] bytes = Convert.FromBase64String(text);
  

В качестве альтернативы, используйте поле базы данных, которое в первую очередь предназначено для двоичных данных.

РЕДАКТИРОВАТЬ: (Скопировано из моего комментария) Приведенный вами пример завершается с ошибкой в конце, преобразуя U DDF0 в U FFFD. На самом деле это именно тот сценарий, о котором я упоминал выше — U DDF0 является «низким суррогатом», но у него нет соответствующего «высокого суррогата», поэтому кодирование.getString преобразует этот символ в U FFFD, который является «символом замены», который является (из диаграммы Юникода)

используется для замены входящего символа, значение которого неизвестно или непредставимо в Unicode

IIRC, вы можете указать, что Encoding делает IIRC, когда он обнаруживает неверные двоичные данные (что фактически и является тем, что вы ему предоставляете), и потенциально заставить его выдавать исключение вместо этого.

Комментарии:

1. «В качестве альтернативы используйте поле базы данных, которое в первую очередь предназначено для двоичных данных». — звучит как хороший совет!

2. Спасибо, Джон, Да, мы понимаем, что поля базы данных для хранения зашифрованных данных, вероятно, должны быть VARBINARY-типами, но, сказав это, я думаю, мне больше интересно узнать о конкретном примере, который я опубликовал выше.

3. @Mitch: Бывают случаи, когда использование строкового представления упрощает жизнь — например, его просто вырезать и вставить. Но да, заставить хранилище отражать то, что вы пытаетесь сохранить, обычно является хорошим планом 🙂

4. @MattA: Я не на компьютере с . На данный момент установлена сеть, поэтому я не могу легко определить. Если бы вы могли сократить пример до одного образца данных, в идеале меньшего размера (инициализированного как прямой массив байтов, чтобы еще проще было тестировать что-то вроде ideone.com ) это помогло бы.

5. @MattA: Я только что попробовал ваш тест под ideone.com , и он показал РАВНОЕ значение дважды. Что это показывает на вашем компьютере?