Рейтинг:0

Можно ли взломать линейный конгруэнтный генератор, если я знаю только модуль вывода?

флаг sz

Редактировать, предложенный fgrieu:

У меня есть сто целых чисел в $\{0,1,2,3,4,5\}$ которые, как я подозреваю, являются последовательными значениями $\lэтаж x_n/2^{16}\rэтаж\bmod6$ вычисляется как $x_{n+1}:=a\cdot x_n+b\bmod m$, с $м=2^{31}$, и $(а,б)\в{(214013,2531011),(22695477,1)}$. Как мне проверить эту гипотезу, найти $(а,б)$ используется, и предсказать, что следует в последовательности?


Вопрос про "Грамотная реализация на скомпилированном языке заняла бы секунду на современном настольном ПК".

Я написал некоторый код, но ожидается, что он будет работать 20 часов.

Я пытаюсь найти случайное семя. Сначала я ввожу свои данные в массив. Поскольку я не знаю, что мои первые данные - это какое-то число, сгенерированное сервером, мне нужно это выяснить. Я знаю только, что сервер отключается каждый четверг в 14:00 и перезагружается примерно в 14:45-15:45 в тот же день. Когда сервер включен, ir генерирует 3 числа каждые 45 секунд. Данные, которые у меня есть, собраны в пятницу в 1:50, поэтому мои первые данные, возможно, 2400-2640-й номер LCG.

Сначала я запускаю ранд 2399 раз, чтобы отбросить первые 2399 чисел. Затем я выполняю цикл 241 раз, чтобы найти мои первые данные о том, какое число сгенерировано сервером. (неопределенность времени перезапуска сервера 14:45-15:45, 240 номеров в час)

Мой метод: Если бит 16 2400-го x равен биту 0 $y_1$, затем я проверяю бит 16 и бит 0 2401-го x $y_2$, и так далее. Если есть неравенство, разорвите цикл, затем запустите другой цикл, сравните 2401-й x и бит 0 из $y_1$.

Как лучше это сделать? Я только начал изучать С++ две недели назад, пожалуйста, простите мое невежество.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <иопоток>
#include <inttypes.h>

использование пространства имен std;

константа int RESULT[3][7] = {
    {0,1,0,1,1,1,1},
    {1,0,1,0,0,0,0},
    {0,1,1,0,1,0,0}
};

статический беззнаковый длинный x;

int test_rand (недействительно)
{
    х = 214013 * х + 2531011; // или это 22695477*x+1
    return (int)((x >> 16) & 0x7FFF);
};

int onlyx16 (недействительно)
{
    х = 214013 * х + 2531011; // или это 22695477*x+1
    возврат (x >> 16) & 1;
};

void chk_seed (беззнаковое длинное семя)
{
    интервал d1[241]{};
    интервал d2[241]{};
    интервал d3[241]{};

    х = семя;

    для (целое я = 0; я < 2399; я ++) {
        test_rand();
    }

    для (целое я = 0; я < 241; я ++)
    {
        d1[i] = onlyx16();
        d2[i] = onlyx16();
        d3[i] = onlyx16();
    };

    правильно = 0;

    для (целое k = 0; k < 236; k++)
    {
        правильный = 0;
        для (целое я = 0; я < 7; я ++)
        {
            если ((d1[i + k]) == RESULT[0][i])
            {
                правильно += 1;
            }
            еще {
                правильный = 0;
                сломать;
            };
            если ((d2[i + k]) == РЕЗУЛЬТАТ[1][i])
            {
                правильно += 1;
            }
            еще {
                правильный = 0;
                сломать;
            };
            если ((d3[i + k]) == РЕЗУЛЬТАТ[2][i])
            {
                правильно += 1;
            }
            еще {
                правильный = 0;
                сломать;
            };
        };
        если (правильно == 21)
        {
            printf("seed 0x%d в порядке\n", seed);
            printf("Прогноз результатов:\n");
            for (int round = 0; round < 5; round++)
            {
                printf("раунд%d", раунд + 1);
                интервал new_d[3]{};
                для (целое я = 0; я < 3; я ++)
                {
                    new_d[i] = test_rand()% 6;
                    printf("%d", new_d[i]);
                };
                printf("\n");
            }
        };
    }
};

основной ()
{
    for (беззнаковое длинное семя = 0; семя < 0x100000000; семя++)
        chk_seed (семя);
};

$x_{n+1} = (a \cdot x_{n} + b) \mod m$

В обычной ситуации, $x_{n+1}$ и $x_n$ известны. Но теперь я знаю только $x_n\mod 6$ и $x_{n+1}\mod 6$.

Я искал много веб-сайтов в Google, но нашел только один вопрос, в котором говорилось об этой проблеме.

Прогнозирование значений от линейного конгруэнтного генератора

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

Я хочу найти a, b, m, а затем использовать исходный код C++, который я нашел здесь, чтобы перебрать начальное число.

Я нашел здесь ответ, в котором говорилось о том, как найти m, но я не знаю $x_{n+1}$ и $x_n$.

https://security.stackexchange.com/questions/4268/cracking-a-linear-congruential-generator

Я новичок в этой теме, но я отчаянно хотел взломать этот ГПСЧ, этот ГПСЧ заставил меня сильно помучиться, я решил изучать программирование из-за этого ГПСЧ. Спасибо за помощь!

fgrieu avatar
флаг ng
Сложность зависит от того, заданы ли $a$, $b$, $m$; о том, является ли $m$ степенью двойки и какой; и от того, сколько их $x_n\bmod 6$. Если проблема задана для Java LCG по умолчанию: вы _не_ получаете $n\bmod6$ на выходе! Ни MSVC, ни `rand` Borland не возвращают $x_n$.Сообщается, что они возвращают биты 30..16 (это 15 бит) $x_n$, см. [это] (https://stackoverflow.com/q/6793065/903600) и [это] (https://stackoverflow.com/ q/14672358/903600). Таким образом, я сомневаюсь в «know $x_n\bmod6$».
fairytale avatar
флаг sz
@fgrieu Я знаю сто $x_n\bmod 6$. Я проверил файлы .exe в папке, один компилятор - MSVC, другой компилятор - Borland C++, поэтому я попробовал MSVC и Borland rand, однако они не могут дать мне правильный результат в будущем. Я получаю только 0-5 на выходе, поэтому я думаю, что это вызвано «% 6» https://stackoverflow.com/questions/1202687/how-do-i-get-a-specific-range-of-numbers- from-rand Я думаю, что это обычная практика получения определенного диапазона случайных чисел. [отредактировано модом, чтобы сжать несколько комментариев]
fgrieu avatar
флаг ng
Но у вас нет указаний на то, что это $x_n$, который взят $\bmod6$, как написано в вопросе. Если бы это было так, и если бы $m$ было четным, а $a$ нечетным, как обычно, то числа, которые вы получаете, были бы альтернативно нечетными и четными. Вопрос, который вы хотите задать, может быть таким: «У меня есть сто целых чисел в $\{0,1,2,3,4,5\}$, которые, как я подозреваю, являются последовательными значениями $\lfloor x_n/2^{16 }\rfloor\bmod6$ вычисляется как $x_{n+1}:=a\cdot x_n+b\bmod m$, где $m=2^{31}$ и $(a,b)\in\{ (214013,2531011),(22695477,1)\}$. Как мне проверить эту гипотезу, найти использованные $(a,b)$ и предсказать, что будет дальше в последовательности?»
fairytale avatar
флаг sz
@fgrieu Хорошо, давайте попробуем этот подход. Спасибо за ваше предложение. Я отредактирую вопрос позже и попробую ваш метод, сейчас я не могу пользоваться компьютером. [отредактировано модом, чтобы сжать несколько комментариев]
Fractalice avatar
флаг in
Статья «Реконструкция усеченных целочисленных переменных, удовлетворяющих линейным сравнениям» может быть очень актуальной.
Рейтинг:3
флаг ng

Согласно комментариям, пересмотренным оригинальный вопрос: ОП предполагает, что 100 цифр $y_n$ в $\{0,1,2,3,4,5\}$ в их распоряжении, получаются с использованием выражения C(++), эквивалентного ранд ()% 6 с ранд() используя (некриптографический) PRNG на основе линейного конгруэнтного генератора с кодом, эквивалентным

статический беззнаковый длинный x;
интервал рандом (недействительным) {
  х = 214013*х+2531011; // или это 22695477*x+1
возврат (целое число)((x>>16)&0x7FFF);
}

Заметь $х$ по крайней мере 32-бит, но только младшие 31 бит имеют значение из-за (х>>16)&0x7FFF и C исполнение беззнаковый длинный умножение с усечением старших разрядов, которые не помещаются в переменную.

Если абстрагироваться от деталей программирования, можно предположить, что $х$ развивается за $x_{n+1}:=a\cdot x_n+b\bmod m$ с $м=2^{31}$ и $(а,б)$ для некоторых фиксированных констант, которые считаются либо $(214013,2531011)$ или же $(22695477,1)$. И ранд() выводит биты 30–16 $х$ Таким образом $y_n:=\lfloor x_n/2^{16}\rfloor\bmod 6$ даны для $n=0$ к $99$ (с затравкой неизвестное целое число $x_{-1}$ в каком-то нематериальном диапазоне, поскольку только $x_{-1}\bmod m$ будет иметь значение; и нам лучше попытаться найти $x_0$ тем не мение).

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

Да, это возможно, с отличной уверенностью. Эффективное состояние рассматриваемых ГПСЧ имеет 31-бит, рассмотрено только два ГПСЧ, они пригодны для целей моделирования, поэтому мы должны быть в состоянии найти их состояние, которое используется с чуть более чем $31+1=32$ бит информации; и мы получаем $100\cdot\log_2(6)\приблизительно258,5$ бит, что даст множество подтверждений.

Самое простое - попробовать оба предполагаемых $(а,б)$, и изучить возможные значения $x_0$. Есть только $2^{31}$, зная $y_0$ позволяет систематически снижать его в $6$. Каждый следующий $y_i$ далее устраняет $5$ кандидаты из $6$. Если ни один кандидат не выживет $y_i$, гипотеза опровергнута. Если совпадение найдено, мы знаем, какое $(а,б)$ мы использовали, и можем вычислить дополнительные $y_i$. Грамотная реализация на скомпилированном языке заняла бы секунду на современном настольном ПК.

Но, может быть, вы хотите сломать это в режиме реального времени с современным процессором стоимостью 0,5 доллара, работающим от батареи стоимостью 0,2 доллара, или застряли на программируемый калькулятор 1970-х годов, или ЭНИАК. Заметьте, что $6$ четно, а делитель $2^{16}$. Следует $y_n\bmod 2$ немного $16$ из $x_n$. Также заметьте, что поскольку $м$ это степень двойки, изменение бита в $x_n$ не распространяется на младшие биты $x_{n+1}$. Если следует, что бит 16 из $x_n$ зависит только от младших 17 бит $x_0$. Мы знаем бит 16 из $x_0$, поэтому нужно проверить не более $2^{16}$ кандидаты на биты 15–0 $x_0$. Затем мы можем найти остальные 14 бит, как указано выше. Что разделяй и властвуй подход позволил бы решать гораздо более крупные параметры, например. переменная Икс типа uin64_t и вернуть х>>33, на современном процессоре.

Интересно, что мы могли бы сделать, если бы $а$, $б$ и/или $м$ были неизвестны.


Примечания к вышеизложенному:

  • Он использует доминирующее соглашение в информатике (и криптографии с несколькими исключениями, такими как DES): биты отсчитываются от 0 (младший бит), так что если неотрицательное целое число $v$ представлен в двоичном виде как $к$ биты $b_j$, тогда $v=\сумма b_j$ с $0\le j<k$. В соглашении с обратным порядком байтов бит 0 записывается справа: $6\times7=42$ в десятичном виде $110\times111=101010$ в двоичном формате.
  • Компьютерная переменная Икс по крайней мере 32-битный, но только 31 бит младшего разряда (от 0 до 30) имеет значение и используется в $x_n$, таким образом $0\le x_n<m=2^{31}$. Результат ранд() как минимум 16-бит, но важны только младшие 15 бит (от 0 до 14), а все остальные равны нулю, таким образом $0\le$ранд()$\ле$RAND_MAX$=2^{15}-1$. Если $0\le i<15$ затем немного $j$ выпуска ранд() соответствует биту $j+16$ из Икс. Далее следует бит 0 $y_n$ соответствует биту 16 из $x_n$.

(не по теме) комментарии к текущий код:

  • Он пытается использовать ускорение, которое стало возможным благодаря 6 быть даже. я утверждаю, что это нет требуется для достижения времени выполнения в секундах, если
    • мы исследовать возможные значения $x_0$, а не семя много шагов назад.
    • мы используем это $x_0$ является 31-битным, поэтому мы можем ограничить внешний поиск до [0, 0x7ffffffff] это $2^{31}$ кандидат $x_0$.
    • мы используем то, что знаем $y_0$, таким образом, что $x_0=2^{16}\cdot(6\cdot и+ y_0)+v$ за $0\le u<\lceil2^{15}/6\rceil$ и $0\le v<2^{16}$, который ограничивается примерно $2^{31}/6$ кандидаты на $x_0$.
    • оптимизируем тест для проверки кандидата $x_0$ против $y_1$ во внутреннем цикле на $v$.
  • Суть по желанию ускорение 6 четно, это сначала найти биты 16…0 $x_0$ соответствие значениям $y_0$ собрал, потом биты 30…17. Код этого не делает! С таким ускорением время выполнения сократилось бы до миллисекунды.
  • Нам нужны полные значения $y_n$ собрались (как в неоптимизированном поиске, так и во второй части оптимизированного поиска), но они, кажется, не доступны на входе, что, я думаю, $y_n\bmod2$. Далее, я не понимаю, почему это в двумерном массиве РЕЗУЛЬТАТ[3][7].
  • Когда $x_0$ найдено, если было необходимо найти начальное число (или, скорее, его биты 30–0) за известное количество шагов до этого, это можно сделать эффективно, пройдя назад по LCG, используя $x_{n-1}:=a^{-1}\,(x_n-b)\bmod m$ куда $а^{-1}$ это модульный обратный из $а$ по модулю $м$.

Вот рабочий код без дополнительное ускорение (таким образом, способное работать с нечетным конечным модулем уменьшения $г$ где вопрос 6). Попробуйте онлайн!

#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>

#define A 214013 // для VC; 22695477 для БК
#define B 2531011 // для ВК; 1 для БК
#define R 6 // модуль, в [2, 32768]
// static_assert(A > 0 && A % 8 == 5, "Модуль 8 должен быть 5");
// static_assert(B > 0 && B % 2 == 1, "B mod 2 должен быть равен 1");
// static_assert(R >= 2 && R <= 32768, "R должен быть в [2, 32768]");

// решение по модулю, уменьшенное, когда R является степенью двойки
#define M ((uint32_t)(((R-1)&R)!=0 ? 0x8000 : R)<<16)

// Последовательность yn для VC (a=214013, b=2531011), n=6, seed=1639326023
// Если R является степенью двойки, достаточно ceil(16/log2(R))+1 записей
// В противном случае это записи ceil(31/log2(R)), таким образом, 12 для R=6.
константа int16_t у [] = {
  0,5,3,0,4,3,1,0,4,5,4,4,4,5,5,3,0,2,0,5,4,5,0, // 0, 2,5,1,3,5,5,5,
};

// модульная обратная INVA для A по модулю M
#define INVA (IN_A(IN_A(IN_A(IN_A((uint32_t)A))))&(M-1))
#define IN_A(v) (v*(2-v*A))

интервал основной (пустой) {
  // решить, начиная с x0, чтобы он соответствовал y0
  const uint32_t y0 = y[0], y1 = y[1];
  int32_t x0 = (int32_t)(((M >> 16) - y0) / R * R + y0) << 16 | 0xffff;
  uint32_t найдено = 0;
  printf("генератор: ((int)((x = %"PRIu32" * x + %"PRIu32 ") >> 16) & 0x7fff) %% %u\n",
    (uint32_t)A, (uint32_t)B, (без знака)R);
  в то время как (x0 >= 0) {
    uint32_t x1 = A * (uint32_t)x0 + B;
    делать {
      // утверждать( (x0 >> 16) % R == y0);
      // утверждение( x1 == A * (uint32_t)x0 + B);
      если (((x1 >> 16) & 0x7fff) % R == y1) {
        uint32_t х = х1;
        инт н;
        для (n = 2; n < sizeof(y) / sizeof(*y); ++n)
          если ((((x = A * x + B) >> 16) & 0x7fff) % R != y[n])
            перейти к следующему шагу;
        // нашел решение
        х = (ИНВА * ((uint32_t)x0 - B)) & (M - 1);
        printf("x0 может быть %" PRId32 ", то есть seed=%" PRIu32
          "по модулю 0x%" PRIx32 ", что дает:\n", x0, x, M);
        // подтверждаем точку зрения, показывая вывод
        для (n = 0; n < sizeof (y) / sizeof (* y) + 8; ++ n)
          printf(" %d", ((int)((x = A * x + B) >> 16) & 0x7fff) % R);
        printf("\n");
        ++найдено;
      }
    nxt: x1 -= (uint32_t)A;
    } while (((x0--) & 0xffff) != 0);
    х0 -= (int32_t)(R - 1) << 16;
  }
  printf("Найдено %" PRIu32 " решение%s\n", найдено, найдено > 1 ? "s" : "");
  вернуть 0;
}

// получаем:
// генератор: ((int)((x = 214013 * x + 2531011) >> 16) & 0x7fff) % 6
// x0 может быть 531633902, то есть seed=1639326023 по модулю 0x80000000, что дает:
// 2 3 4 1 5 1 1 5 4 0 3 2 2 5 5 3 5 5 3 4 0 1 1 4 1 3 3 2 5 4 4
// найдено 1 решение
fairytale avatar
флаг sz
Если $x_n$ равно 32768(1000 0000 0000 0000), бит 16 равен 0, если $y_n\bmod 2$ также равен 0, то это $y_n$ соответствует $x_n$. Затем я проверяю следующий $x_n$. Я прав? (Я предполагаю, что биты считаются слева, если вы не говорите «низкий»)
fgrieu avatar
флаг ng
@fairytale: а, нет. См. новый раздел «_Примечания к вышеизложенному_», особенно два последних предложения.
fairytale avatar
флаг sz
Моя последовательность 4,5,0,1,4,3,4,5,1,1,0,2,1,2,3,1,0,2,5,2,0. К сожалению, не найдено... Пробовал также 1103515245 и 12345 (glibc rand), но так и не нашел. Вздох. Программа очень старая, написана до 2006 года, также она не имеет серьезного отношения, поэтому я считаю, что это не криптографически-защищенный PRNG.Теперь я предполагаю, что автор мог использовать свои собственные A, B, M или использовать Mersenne Twister.
fgrieu avatar
флаг ng
@fairytale: на переформулированный вопрос дан отрицательный ответ. У вас есть исполняемый файл («_Я проверил .exe-файлы_»), поэтому все еще есть курс действий обратного проектирования; декомпиляция и/или наблюдение за работой программы в инструментальной среде. Это еще более не по теме крипто-SE, чем программирование. Но, возможно, на [reverseengineering-SE] (https://reverseengineering.stackexchange.com/)?

Ответить или комментировать

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