Лучший разделитель для безопасного анализа массивов байтов из потока

У меня есть поток байтов, который возвращает последовательность массивов байтов, каждый из которых представляет одну запись.

Я хотел бы разобрать поток в список отдельных байтов []. В настоящее время я взломал трехбайтовый разделитель, чтобы я мог определить конец каждой записи, но у меня есть проблемы.

Я вижу, что есть стандартный символ разделителя записей Ascii.

30  036 1E  00011110    RS        Record Separator

Безопасно ли использовать byte[], полученный из этого символа, в качестве разделителя, если массивы байтов (которые были закодированы в UTF-8) были сжаты и/или зашифрованы? Меня беспокоит то, что вывод шифрования/сжатия может создать разделитель записей для какой-то другой цели. Обратите внимание, что отдельные записи byte[] сжимаются/зашифровываются, а не весь поток.

Я работаю на Java 8 и использую Snappy для сжатия. Я еще не выбрал библиотеку шифрования, но это, безусловно, один из самых надежных стандартных подходов с закрытым ключом.


person L. Blanc    schedule 14.08.2015    source источник
comment
Подходы с закрытым ключом не сильнее. Они просто используются для различных приложений, в основном для согласования ключей или передачи ключей. Вы должны кодировать длину каждого сообщения в потоке, а не пытаться выбрать разделитель. Это безопаснее и проще.   -  person erickson    schedule 14.08.2015
comment
@erickson, я думаю, вы пропустили запятую между сильным и закрытым ключом. Я говорил, что из стандартных алгоритмов с закрытым ключом я бы выбрал один из более сильных. Кроме того, вы путаете шифрование с закрытым и открытым ключом. Криптовалюта с открытым ключом используется для согласования и для передачи закрытых (симметричных) ключей.   -  person L. Blanc    schedule 14.08.2015
comment
Хорошо, хотел убедиться, что вы не придерживаетесь распространенного заблуждения, что асимметричные алгоритмы в чем-то более безопасны, чем симметричные. Симметричный или секретный ключ с гораздо меньшей вероятностью будет неправильно понят, чем закрытый ключ.   -  person erickson    schedule 14.08.2015


Ответы (2)


Вы не можете просто объявить байт в качестве разделителя, если вы работаете со случайными неструктурированными данными (которые очень похожи на сжатые/зашифрованные данные), потому что разделитель всегда может отображаться как обычный байт данных в таких данных.

Если размер данных уже известен, когда вы начинаете писать, просто сначала напишите размер, а затем данные. При обратном чтении вы знаете, что вам нужно сначала прочитать размер (например, 4 байта для int), а затем столько байтов, сколько указывает размер.

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

e.g.

final static byte ESCAPE = (byte) 0xBC;
final static byte EOF = (byte) 0x00;

OutputStream out = ...
for (byte b : source) {
    if (b == ESCAPE) {
        // escape data bytes that have the value of ESCAPE
        out.write(ESCAPE);
        out.write(ESCAPE);
     } else {
        out.write(b);
     }
}
// write EOF marker ESCAPE, EOF
out.write(ESCAPE);
out.write(EOF);

Теперь при чтении и чтении байта ESCAPE вы читаете следующий байт и проверяете наличие EOF. Если это не EOF, это экранированный ESCAPE, представляющий байт данных.

InputStream in = ...
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
while ((int b = in.read()) != -1) {
    if (b == ESCAPE) {
        b = in.read();
        if (b == EOF)
            break;
        buffer.write(b);
    } else {
         buffer.write(b);
    }
}

Если байты для записи распределены абсолютно случайным образом, это увеличит длину потока на 1/256, для доменов данных, которые не являются полностью случайными, вы можете выбрать байт, который появляется реже всего (статическим анализом данных или просто обоснованным предположением) .

Изменить: вы можете уменьшить накладные расходы на экранирование, используя более сложную логику, например. пример может создавать только ESCAPE + ESCAPE или ESCAPE + EOF. Остальные 254 байта никогда не могут следовать за ESCAPE в примере, поэтому их можно использовать для хранения допустимых комбинаций данных.

person Durandal    schedule 14.08.2015
comment
Вместо экранирования большинство протоколов, поддерживающих сообщения, длина которых заранее неизвестна, используют метод фрагментации, при котором каждый фрагмент кодируется по длине, а затем последний фрагмент имеет нулевую длину, чтобы сигнализировать о конце сообщения. - person erickson; 14.08.2015

Это совершенно небезопасно, вы никогда не знаете, что может оказаться в ваших данных. Возможно, вам следует подумать о чем-то вроде протобуфа или схеме типа «сначала записать длину записи, потом записать запись, потом промыть, намылить, повторить»?

Если у вас есть длина, вам не нужен разделитель. Ваша сторона чтения считывает длину, затем знает, сколько нужно прочитать для первой записи, а затем знает, что читать следующую длину - все это предполагает, что сами длины имеют фиксированную длину.

См. предложения разработчиков по потоковой передаче последовательности прототипов.

person bmargulies    schedule 14.08.2015
comment
Как оказалось, это протобуфы (без учета сжатия/шифрования), но поток протобуфов, а не отдельный протобуф. Существует ли стандартный способ разграничения потока отдельных протобуфов? Кроме того, учитывая предложенную схему длины записи, не нужно ли мне по-прежнему определять начало каждой записи? Не знаю, как это поможет. - person L. Blanc; 14.08.2015