Заставить RNG OpenSSL возвращать повторяющуюся последовательность байтов

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

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

Я не могу просто вызвать RAND_seed с фиксированным начальным числом перед каждым тестом, потому что (похоже) RNG автоматически берет себя из /dev/urandom, хочу я этого или нет, и в любом случае RAND_seed не сбрасывает RNG, это только добавляет семя в пул энтропии.

Есть какой-либо способ сделать это? (В крайнем случае, похоже, что я мог бы написать свой собственный механизм PRNG, но я хотел бы думать, что есть более простой вариант.)


person zwol    schedule 15.09.2011    source источник
comment
Вам не нужен ГПСЧ. Вам просто нужен предсказуемый поток значений; даже счетчик подойдет.   -  person Michael Foukarakis    schedule 22.09.2011


Ответы (2)


Вы можете перевести ГСЧ FIPS ANSI X9.31 в тестовый режим во время выполнения, но не ГСЧ SSLeay (по умолчанию). Если вы перекомпилируете OpenSSL с -DPREDICT, RNG по умолчанию выдаст предсказуемую последовательность чисел, но это не очень удобно.

Функция RAND_pseudo_bytes генерирует предсказуемый ряд чисел, что означает, что она не добавляет к себе энтропию автоматически, как RAND_bytes. Но, как вы заметили, к начальному значению можно добавить только энтропию, а не указать начальное значение явно, поэтому между запусками программы вы получите разные числа. Тоже не полезно.

Но написать свой собственный предсказуемый движок ГСЧ несложно. На самом деле, я проведу вас через это, создав движок rand с rand() из stdlib в его ядре:

#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <openssl/rand.h>

// These don't need to do anything if you don't have anything for them to do.
static void stdlib_rand_cleanup() {}
static void stdlib_rand_add(const void *buf, int num, double add_entropy) {}
static int stdlib_rand_status() { return 1; }

// Seed the RNG.  srand() takes an unsigned int, so we just use the first
// sizeof(unsigned int) bytes in the buffer to seed the RNG.
static void stdlib_rand_seed(const void *buf, int num)
{
        assert(num >= sizeof(unsigned int));
        srand( *((unsigned int *) buf) );
}

// Fill the buffer with random bytes.  For each byte in the buffer, we generate
// a random number and clamp it to the range of a byte, 0-255.
static int stdlib_rand_bytes(unsigned char *buf, int num)
{
        for( int index = 0; index < num; ++index )
        {
                buf[index] = rand() % 256;
        }
        return 1;
}

// Create the table that will link OpenSSL's rand API to our functions.
RAND_METHOD stdlib_rand_meth = {
        stdlib_rand_seed,
        stdlib_rand_bytes,
        stdlib_rand_cleanup,
        stdlib_rand_add,
        stdlib_rand_bytes,
        stdlib_rand_status
};

// This is a public-scope accessor method for our table.
RAND_METHOD *RAND_stdlib() { return &stdlib_rand_meth; }

int main()
{
        // If we're in test mode, tell OpenSSL to use our special RNG.  If we
        // don't call this function, OpenSSL uses the SSLeay RNG.
        int test_mode = 1;
        if( test_mode )
        {
                RAND_set_rand_method(RAND_stdlib());
        }

        unsigned int seed = 0x00beef00;
        unsigned int rnum[5];

        RAND_seed(&seed, sizeof(seed));
        RAND_bytes((unsigned char *)&rnum[0], sizeof(rnum));
        printf("%u %u %u %u %u\n", rnum[0], rnum[1], rnum[2], rnum[3], rnum[4]);

        return 0;
}

Каждый раз, когда вы запускаете эту программу, она задает srand() одним и тем же числом и, следовательно, каждый раз выдает вам одну и ту же последовательность случайных чисел.

corruptor:scratch indiv$ g++ rand.cpp -o r -lcrypto -g
corruptor:scratch indiv$ ./r
1547399009 981369121 2368920148 925292993 788088604
corruptor:scratch indiv$ ./r
1547399009 981369121 2368920148 925292993 788088604
corruptor:scratch indiv$ 
person indiv    schedule 22.09.2011
comment
Спасибо! Из документации OpenSSL у меня сложилось впечатление, что переопределить внутренний генератор случайных чисел гораздо сложнее, поэтому я так не хотел этого делать. - person zwol; 22.09.2011
comment
Документация также настоятельно подразумевает, что это следует делать с ENGINE, а не с RAND_METHOD, но я не могу понять, как реализовать свой собственный ENGINE. Вы можете это прокомментировать? - person zwol; 22.09.2011
comment
@Zack: ENGINE — это контейнер, который предоставляет интерфейс для динамической загрузки и выпуска реализаций алгоритмов. Вот и все. Таким образом, если бы вы реализовали механизм ГСЧ как объект ENGINE, вы бы сначала реализовали то, что я показал вам выше, а затем реализовали бы вокруг него оболочку ENGINE, чтобы получить все преимущества интерфейса ENGINE. Интерфейс алгоритма остался без изменений (функции RAND_* и таблица RAND_METHOD). Если вы реализуете ENGINE, вам все равно следует поддерживать пользователей OpenSSL, скомпилированных с помощью -DOPENSSL_NO_ENGINE, по-прежнему предоставляя доступ к таблице. - person indiv; 23.09.2011

Напишите обертку вокруг библиотеки. Затем замените его во время тестирования на свой собственный макет, который возвращает ваши магические значения.

Помните, что в модульном тесте вы не пытаетесь протестировать OpenSSL. Вы пытаетесь проверить свой код.

person John Deters    schedule 15.09.2011
comment
Это было бы так же сложно, как написать собственный движок PRNG для OpenSSL. Заставить PRNG OpenSSL делать то, что я хочу, должно быть намного проще. - person zwol; 16.09.2011
comment
Хорошо, тогда обманите компоновщика тестового проекта. Реализуйте свои собственные RAND_bytes() и RAND_pseudo_bytes() в исходном модуле, и он будет связан до того, как будут добавлены какие-либо библиотеки. - person John Deters; 16.09.2011
comment
Реализовать свой собственный [криптографический PRNG] так же сложно, как написать свой собственный ДВИГАТЕЛЬ. Суть вопроса в том, что я не хочу этого делать, если этого можно избежать. - person zwol; 16.09.2011
comment
Извините, я не ясно выразился. Ваш собственный RAND_bytes(), на который ссылается ваш тестовый проект, не будет настоящим случайным движком. Это была бы простая фиктивная реализация для возврата статических тестовых данных, что-то вроде: RAND_bytes(unsigned char* buf, int num){memcpy(buf, testArray[offset], num); смещение+=число; вернуть 1;}. Вам нужно будет предварительно создать все случайные данные в testArray, и вам также понадобится обработка ошибок. Но вы просили указать способ возврата неслучайных данных вместо случайных, и это один из способов сделать это. - person John Deters; 16.09.2011