Написание C++ iostream, использующего потоковый шифр RC4. Как я могу оптимизировать мою реализацию?
Я реализую собственный iostream (то есть с чтением, записью, поиском и закрытием), который использует потоковый шифр RC4 для шифрования и дешифрования. Один из контрактов этого потока заключается в том, что он является двунаправленным, и вызывающий код должен иметь возможность произвольного поиска любой позиции в потоке перед выполнением любого фактического чтения или записи.
Теперь, поскольку RC4 использует ключ, который опирается на все предыдущие операции подкачки вплоть до заданной позиции "сказать", как я могу включить возможность произвольного поиска любой позиции?
Очевидно, что я мог бы искать до позиции данного смещения поиска (отмеченного ЭТОМ БИТОМ в следующем примере), прежде чем делать фактический процесс преобразования ксинга, что-то вроде:
/**
* @brief called from a stream's read or write function
* @param in the input buffer
* @param out the output buffer
* @param startPosition the current stream position (obtained via the streams
* tellg or tellp functions for read and write respectively)
* @param length the number of bytes to transform
*/
void transform(char *in, char *out,
std::ios_base::streamoff startPosition,
long length)
{
// need to reset sbox from member s_box each time this
// function is called
long sbox[256];
for (int i = 0; i<256; ++i) {
sbox[i]=m_sbox[i];
}
// ***THIS BIT***
// need to run the swap operation startPosition times
// to get sbox integer sequence in order
int i = 0, j = 0, k = 0;
for (int a=0; a < startPosition; ++a) {
i = (i + 1) % 256;
j = (j + sbox[i]) % 256;
swapints(sbox, i, j);
}
// now do the actual xoring process up to the length
// of how many bytes are being read or written
for (int a=0; a < length; ++a) {
i = (i + 1) % 256;
j = (j + sbox[i]) % 256;
swapints(sbox, i, j);
k = sbox[(sbox[i] + sbox[j]) % 256];
out[a] = in[a] ^ k;
}
}
и тогда преобразование будет вызвано из чтения или записи реализации потока, что-то вроде:
MyStream&
MyStream::read(char * const buf, std::streamsize const n)
{
std::ios_base::streamoff start = m_stream.tellg();
std::vector<char> in;
in.resize(n);
(void)m_stream.read(&in.front(), n);
m_byteTransformer->transform(&in.front(), buf, start, n);
return *this;
}
РЕДАКТИРОВАТЬ: поток не должен знать, как работает функция преобразования. Функция преобразования полностью независима, и я должен иметь возможность свободно менять различные реализации преобразования.
РЕДАКТИРОВАТЬ: функция swapints выглядит следующим образом:
void swapints(long *array, long ndx1, long ndx2)
{
int temp = array[ndx1];
array[ndx1] = array[ndx2];
array[ndx2] = temp;
}
Реальная проблема с вышеупомянутой функцией преобразования заключается в ее медлительности, потому что она должна выполнить начальные операции подкачки startPosition, прежде чем будет выполнено собственное преобразование xor. Это очень проблематично, когда выполняется много операций поиска. Теперь я слышал, что RC4 должен быть быстрым, но моя (вероятно, плохая реализация) предлагает иное, учитывая начальный набор операций подкачки.
Поэтому мой реальный вопрос: как можно оптимизировать приведенный выше код, чтобы уменьшить количество необходимых операций? В идеале я хотел бы исключить начальный ("ЭТОТ БИТ") набор операций подкачки
РЕДАКТИРОВАТЬ: оптимизация начальной настройки sbox, вероятно, тривиальна (например, используя memcpy, как предложено egur). Я думаю, что важная оптимизация заключается в том, как оптимизировать цикл, помеченный ЭТОМ БИТОМ. Возможно, все эти переменные можно запрограммировать более кратко, без необходимости в цикле for.
Спасибо,
Бен
2 ответа
После некоторых исследований выясняется, что произвольный доступ к ключевому потоку RC4 невозможен. Смотрите обсуждение по этой ссылке: crypto.stackexchange. Лучшей альтернативой (как указал Россум в своем комментарии) является использование блочного шифра в режиме счетчика.
В режиме счетчика вы выполняете шифрование последовательности чисел. Эта последовательность является инкрементной и является длиной всего потока данных. Итак, скажем, вы хотите зашифровать 8 байтов данных, начиная с позиции "16" исходного потока данных, используя 64-битный (8 байт) блочный шифр.
Необходимо зашифровать 8 байтов, поскольку вы одновременно обрабатываете 8 байтов простого текста. Поскольку позиция, которую мы хотим случайным образом сместить, равна 16, мы по существу зашифровываем "блок 3" этой числовой последовательности (байты от 0 до 7 == блок 1, байты с 8 по 15 == блок 2, байты с 16 по 23 == блок 3 и так далее...)
Например, используя алгоритм XTEA, который шифрует блоки по 8 байт с использованием 128-битного ключа, мы бы сделали что-то вроде:
Блок 3:
// create a plain text number sequence
uint8_t plainText[8];
plainText[0] = 16;
plainText[1] = 17;
.
.
.
plainText[7] = 23;
// encrypt the number sequence
uint8_t cipherText[8];
applyXTEATransformation(plainText, cipherText, keyOfLength128Bit);
// use the encrypted number sequence as a
// key stream on the data to be encrypted
transformedData[16] = dataToBeEncrypted[16] ^ cipherText[0];
transformedData[17] = dataToBeEncrypted[17] ^ cipherText[1];
.
.
.
transformedData[23] = dataToBeEncrypted[23] ^ cipherText[7];
tldr: я хотел сделать произвольный доступ на RC4, но обнаружил, что это невозможно, поэтому вместо этого использовал режим счетчика на блочном шифре XTEA.
Бен
Изменить все % 255
в & 0xff
, намного быстрее:
i = (i + 1) % 256;
Для того, чтобы:
i = (i + 1) & 0xFF;
Редактировать:
Вы тратите много времени на инициализацию sbox
, Вы должны пройти sbox
в качестве параметра функции преобразования, поэтому оригинальная копия обновляется между вызовами. То, что вы делаете сейчас, это инициализирует его снова и снова, и каждый раз это занимает больше времени, так как startPosition растет.
void transform(char *in, char *out,
long length,
unsigned char* sbox)
Временный sbox
должен быть членом MyStream
учебный класс. Функция чтения должна быть:
MyStream&
MyStream::read(char * const buf, std::streamsize const n)
{
std::ios_base::streamoff start = m_stream.tellg();
std::vector<char> in;
in.resize(n);
(void)m_stream.read(&in.front(), n);
// init m_TempSbox on first call
if (m_FirstCall) {
initTempSbox();
}
m_byteTransformer->transform(&in.front(), buf, n, m_TempSbox);
return *this;
}