Написание потока ввода-вывода C++, использующего потоковый шифр RC4. Как я могу оптимизировать свою реализацию?

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

Теперь, поскольку RC4 использует ключ, который опирается на все предыдущие операции подкачки до заданной позиции «сообщения», как я могу включить возможность произвольного поиска любой позиции?

Очевидно, я мог бы выполнить поиск до позиции данного смещения поиска (отмеченного THIS BIT в следующем примере), прежде чем выполнять фактический процесс преобразования xor-ing, что-то вроде ,:

/**
 * @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.

Спасибо,

Бен


person Ben J    schedule 14.01.2014    source источник
comment
Обратите внимание, что повторное использование любых частей потока ключей полностью нарушает шифрование.   -  person ntoskrnl    schedule 14.01.2014
comment
@ntoskml, поэтому и RC4, и приведенная выше реализация используют псевдослучайно сгенерированный поток ключей, длина которого соответствует всей длине обрабатываемого потока iostream. Именно функция подкачки порождает эту псевдослучайность (не принимая во внимание предполагаемые слабости RC4). Никакие части ключевого потока никогда не используются повторно.   -  person Ben J    schedule 14.01.2014
comment
Я беспокоился, что поиск в обратном направлении позволит зашифровать разные данные одним и тем же потоком ключей, но, возможно, это не имеет смысла. (Я очень мало знаю о C++ и iostreams.)   -  person ntoskrnl    schedule 14.01.2014
comment
Является ли RC4 частью спецификации программы? Если вы хотите перейти прямо к любой части ключевого потока, я бы предложил AES в режиме счетчика (CTR) или что-то вроде Salsa20, оба из которых позволяют вам перейти прямо к любому требуемому блоку ключевого потока без вычисления любого из более ранних блоков. .   -  person rossum    schedule 18.01.2014
comment
Спасибо, Россум, да, я понял из другого «сайта стека», что RC4 не поддерживает произвольный доступ, а это, по сути, то, что я хочу. Я реализовал XTEA в режиме счетчика, который, как вы совершенно справедливо заметили, позволяет мне сразу перейти к нужному блоку.   -  person Ben J    schedule 20.01.2014


Ответы (2)


Измените все % 255 на & 0xff, намного быстрее:

i = (i + 1) % 256;

To:

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;
}    
person egur    schedule 14.01.2014
comment
Лучше, чем как % 255, так и & 0xff, было бы вместо этого делать все в unsigned char и просто делать i++, используя тот факт, что значения без знака (в отличие от их аналогов со знаком) переносятся при переполнении/недополнении. - person Iridium; 14.01.2014
comment
Я не хочу касаться функции чтения, если я могу помочь ей, поскольку она должна быть независимой от функции преобразования. Т. е. функция чтения потока должна работать с любой реализацией, а sbox — это только часть приведенной выше реализации трансформации; Я должен отметить, что функция преобразования не является частью класса MyStream. Кроме того, состояние массива sbox необходимо сбрасывать при каждом отдельном вызове преобразования, поэтому я не думаю, что имеет смысл хранить его как переменную-член. - person Ben J; 14.01.2014

После некоторых исследований выяснилось, что произвольный доступ к ключевому потоку 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.

Бен

person Ben J    schedule 20.01.2014