Endianness и C API: в частности OpenSSL - PullRequest
3 голосов
/ 10 мая 2010

У меня есть алгоритм, который использует следующие вызовы OpenSSL:

HMAC_update() / HMAC_final() // ripe160
EVP_CipherUpdate() / EVP_CipherFinal() // cbc_blowfish

Эти алгоритмы переводят unsigned char * в "обычный текст". Мои входные данные взяты из C ++ std::string::c_str(), который происходит из объекта буфер протокола в виде кодированной строки UTF-8. Струны UTF-8 предназначены для нейтрино обратного порядка. Однако я немного параноик по поводу того, как OpenSSL может выполнять операции с данными.

Насколько я понимаю, алгоритмы шифрования работают с 8-разрядными блоками данных, и если unsigned char * используется для арифметики указателей, когда выполняются операции, алгоритмы должны быть нейтральными по порядку байтов и мне не нужно ни о чем беспокоиться. Моя неуверенность усугубляется тем фактом, что я работаю на машине с прямым порядком байтов и никогда не занимался кросс-архитектурным программированием.

Мои убеждения / рассуждения основаны / основаны на следующих двух свойствах

  1. std :: string (не wstring) внутренне использует 8-битный ptr, и результирующий c_str() ptr будет работать одинаково независимо от архитектуры ЦП.
  2. Алгоритмы шифрования либо по дизайну, либо по реализации, не зависят от порядка байтов.

Я знаю, что лучший способ получить окончательный ответ - использовать QEMU и провести несколько кроссплатформенных юнит-тестов (которые я планирую сделать). Мой вопрос - это запрос комментариев к моим рассуждениям, и, возможно, он поможет другим программистам, столкнувшимся с подобными проблемами.

Ответы [ 4 ]

7 голосов
/ 10 мая 2010

Строка UTF-8 и std :: string оба определены как последовательность символов. Криптоалгоритмы определены для работы с последовательностью байтов / октетов (в C байты - это те же символы, и если ваш байт не является октетом, значит, вы находитесь в необычной реализации, и вам, возможно, придется быть немного осторожнее). имеем дело с UTF-8). Единственный разумный способ представить последовательность байтов в непрерывной памяти - это первый по младшему адресу, а последующие по старшим адресам (массив C). Криптоалгоритмам все равно, что представляют байты, так что все в порядке.

Endian-ness имеет значение, только когда вы имеете дело с чем-то вроде int, которое по своей сути не является последовательностью байтов. В абстрактном виде это просто «что-то», которое содержит значения от INT_MIN до INT_MAX. Когда вы представляете такого зверя в памяти, конечно, это должно быть количество байтов, но нет единого способа сделать это.

На практике endian-ness имеет важное значение в C, если вы (возможно, через то, что вы называете) переосмыслите char * как int *, или наоборот, или определите протокол, в котором int представляется с использованием последовательности символы. Если вы имеете дело только с массивами символов или только с массивами целых, это не имеет значения, поскольку endianness является свойством целых чисел и других типов, больших чем char.

4 голосов
/ 11 мая 2010

Некоторые криптографические алгоритмы , в частности хеш-функции (которые используются в HMAC), определены для работы с произвольной последовательностью битов. Однако на реальных физических компьютерах и с большинством протоколов данные представляют собой последовательность октетов : число битов кратно восьми, и биты могут обрабатываться группами по восемь битов. Группа из восьми битов номинально является «октетом», но термин «байт» встречается чаще. Октет имеет числовое значение от 0 до 255 включительно. В некоторых языках программирования (например, Java) числовое значение подписывается (между -128 и +127), но это та же концепция.

Обратите внимание, что в контексте языка программирования C (как определено в стандарте ISO 9899: 1999, он же «стандарт C»), байт определяется как элементарная адресуемая единица памяти, воплощенный по типу unsigned char. sizeof возвращает размер в байтах (таким образом, sizeof(unsigned char) обязательно равен 1). malloc() принимает размер в байтах. В C число битов в байте определяется макросом CHAR_BIT (определенным в <limits.h>) и больше или равно восьми. На большинстве компьютеров в байте C содержится ровно восемь битов (то есть байт C является октетом, и каждый называет его «байтом»). - это некоторые системы с большими байтами (часто с встроенным DSP), но если бы у вас была такая система, вы бы ее знали.

Таким образом, каждый криптографический алгоритм, который работает с произвольными последовательностями битов, фактически определяет, как биты внутренне интерпретируются в октеты (байты). Спецификации AES и SHA делают все возможное, чтобы сделать это правильно, даже в глазах придирчивых математиков. Для каждой практической ситуации ваши данные уже представляют собой последовательность байтов, и предполагается, что группировка битов в байты уже произошла; так что вы просто передаете байты реализации алгоритма, и все в порядке.

Следовательно, с практической точки зрения, криптографические алгоритмы реализации ожидают последовательность байтов в качестве ввода и производят последовательности байтов в качестве вывода.

Endianness (неявно на уровне байтов) - это соглашение о том, как многобайтовые значения (значения, для кодирования которых требуется несколько байтов) разбиты на последовательности байтов (т. Е. Какой байт идет первым) , UTF-8 является нейтрально-байтовым в том смысле, что он уже определяет это расположение: когда символ должен быть закодирован в несколько байтов, UTF-8 указывает, какой из этих байтов идет первым, а какой последним. Вот почему UTF-8 является «нейтральным порядком байтов»: преобразование символов в байты является фиксированным соглашением, которое не зависит от того, как локальное оборудование предпочитает лучше читать или записывать байты. Порядковый номер чаще всего связан с тем, как целочисленные значения записываются в памяти.

О кроссплатформенном программировании: Ничто не заменит опыт. Таким образом, примерка нескольких платформ является хорошим способом. Вы уже многому научитесь, сделав свой код 64-битным чистым, то есть, если один и тот же код будет правильно работать на 32-битной и 64-битной платформах. Любой недавний ПК с Linux будет соответствовать всем требованиям. Системы с прямым порядком байтов в настоящее время довольно редки; вам понадобится старый Mac (с процессором PowerPC) или один из нескольких видов рабочих станций Unix (на ум приходят системы Sparc или Itanium под HP / UX). Более новые дизайны, как правило, принимают конвенцию little-endian.

О порядке байтов в C: Если ваша программа должна беспокоиться о порядке байтов, то есть вероятность, что вы делаете это неправильно. Endianness - это преобразование целых чисел (16-битных, 32-битных или более) в байты и обратно. Если ваш код беспокоится о порядке байтов, то это означает, что ваш код записывает данные в виде целых чисел и читает их как байты, или наоборот. В любом случае, вы делаете «псевдонимы типов»: доступ к некоторым частям памяти осуществляется через несколько указателей различных типов. Это плохо . Он не только делает ваш код менее переносимым, но также имеет тенденцию ломаться, когда просит компилятор оптимизировать код.

В правильной программе на C порядковый номер обрабатывается только для ввода-вывода, когда значения должны записываться или считываться из файла или сетевого сокета. То, что ввод / вывод следует протоколу, который определяет порядок использования порядка байтов (например, в TCP / IP часто используется правило с прямым порядком байтов). «Правильный» способ - написать несколько функций-оболочек:

uint32_t decode32le(const void *src)
{
    const unsigned char *buf = src;
    return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8)
        | ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24);
}

uint32_t decode32be(const void *src)
{
    const unsigned char *buf = src;
    return (uint32_t)buf[3] | ((uint32_t)buf[2] << 8)
        | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[0] << 24);
}

void encode32le(void *dst, uint32_t val)
{
    unsigned char *buf = dst;
    buf[0] = val;
    buf[1] = val >> 8;
    buf[2] = val >> 16;
    buf[3] = val >> 24;
}

void encode32be(void *dst, uint32_t val)
{
    unsigned char *buf = dst;
    buf[3] = val;
    buf[2] = val >> 8;
    buf[1] = val >> 16;
    buf[0] = val >> 24;
}

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

Затем вы используете эти функции всякий раз, когда хотите записать или прочитать 32-разрядные целые числа из буфера памяти, только что полученного из (или вскоре записанного в) файла или сокета. Это сделает ваш код не зависящим от порядка байтов (следовательно, переносимым) и более понятным, что облегчит чтение, разработку, отладку и обслуживание. И в крайне редкой ситуации, когда такое кодирование и декодирование становится узким местом (это может произойти, только если вы используете платформу с очень слабым ЦП и очень быстрым сетевым подключением, то есть вообще не ПК) вы все равно можете заменить реализацию этих функций некоторыми макросами, специфичными для архитектуры, возможно, встроенной сборкой, без изменения остальной части вашего кода.

2 голосов
/ 10 мая 2010

Кажется, реальные вопросы здесь:

"Могу ли я быть уверен, что моя закодированная строка UTF-8 будет одинаково внутренне представлена ​​на разных компьютерах?"

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

Поскольку вы запрашиваете только комментарии, я думаю, с вами все будет в порядке. Процедуры OpenSSL должны вести себя одинаково для двух идентичных блоков данных независимо от архитектуры компьютера.

0 голосов
/ 10 мая 2010

Один из способов убедиться в постоянстве - следовать стандарту IP сетевой порядок байтов .

Взгляните здесь на нужные вам функции. Они должны быть доступны в Windows и * nix с современными реализациями C ++.

Однако я считаю, что ваши рассуждения верны, и вам не нужно беспокоиться об этом в этом случае.

Редактировать: для ясности, комментарий порядка байтов в сети предполагает, что вы отправляете данные и беспокоитесь о том, как они будут получены на другом конце. Если отправка и получение находятся на одной машине, проблем не должно быть.

...