Как стать автором
Обновить

Генерация простых чисел

Время на прочтение 23 мин
Количество просмотров 26K
В продолжение темы, начатой в этой серии статей, сегодня мы поговорим о генерации простых чисел. Генерация бывает вероятностной и бывает гарантированной. Так вот, наш случай — с гарантией, которая позволяет использовать получаемые числа в приложениях, связанных с криптографией, что обеспечивает, например, безопасность распоряжения вашими деньгами через банковские приложения. Далее вы увидите, как гарантированно получать простые числа достаточного для криптографии размера, а так же какими могут быть проблемы, если пренебречь гарантиями.

image

Общая схема обеспечения безопасности при обмене данными опирается на сложность разложения большого составного числа на множители. При этом если множителей много, то они будут маленькими, а это сильно упрощает взлом системы безопасности. Поэтому большое составное число получают при помощи умножения двух простых чисел определённого размера. Но здесь очевидна одна проблема — как выбрать достаточно большое простое число?

Сначала определимся с размером. В современной криптографии порядок составного числа определяется формулой $2^{2048}$, то есть собственно порядок равен 2048 в двоичной систем счисления. А делители у этого составного числа бывают порядка 1024, то есть где-то около значений $2^{1024}$. Почему 1024? Потому что уже лет десять назад было разложено на множители число порядка 768, а сегодня уже весьма вероятно разложение составных чисел порядка 1024. Вот и решено для надёжности увеличить порядок сразу в два раза, то есть до 2048. Ну а отталкиваясь от этого порядка легко определить порядок необходимых сомножителей.

Но что будет, если порядок сомножителей окажется много меньше 1024? Тогда за приемлемое время можно разложить составное число, даже если оно имеет порядок 2048. Значит нужно гарантировать, что выбранные нами сомножители действительно имеют порядок близкий к 1024 и при этом являются простыми, то есть не разлагаются на множители. Но вот сам выбор осуществляется по вероятностной схеме, а это как раз и ведёт к потенциальным проблемам.

Проверка вероятности простоты числа осуществляется на основе предположения о том, что число «скорее всего» простое, если его период (о котором рассказывается здесь) является делителем самого числа, минус единица ($N-1$). В виде формулы это выглядит так:

$a^p \pmod N = 1$


$N-1 \pmod p = 0$



Здесь $N$ — исследуемое число, $а$ — значение от $2$ до $N-2$ (оно определяет основание системы счисления, в которой вычисляется период), $p$ — период исследуемого числа. Операция $mod$ здесь означает взятие остатка от деления первого операнда на второй.

Существующие проверки опираются именно на такой подход, поскольку для больших чисел безусловное определение простоты известными тестами занимает очень много времени. Но минус такой проверки очевиден — иногда можно получить составное число, вместо простого. При этом какие делители окажутся в составе такого числа — никто не знает. Точнее это сможет выяснить злоумышленник, применив известные методы факторизации, которые начинают работать за приемлемое время, если множители окажутся порядка менее чем несколько сотен бит.

Как часто ошибается вероятностная проверка простоты? Достаточно редко. Бывает одна ошибка на несколько десятков тысяч простых, а бывает и две, но ничто не гарантирует нас и от трёх. Кроме того, многое зависит от используемого теста. Так, например в стандартной библиотеке языка Java в классе BigInteger реализована проверка, которая допускает промах 2-3 раза на тысячу простых. То есть не стоит думать, что если теоретически ошибок очень мало, то и на практике всё будет в шоколаде.

Чем это опасно? Вроде бы и не так страшно, как могут сказать некоторые, ведь ну что с того, если какой-нибудь сайт, закрывающий просмотр своих страниц протоколом https, получит легко вычисляемый ключ, то какие от этого будут проблемы? Ну узнают хакеры, какие новости вы читали на этом сайте, а что толку? Но на самом деле шифрование производится не только при просмотре веб-страниц. Более неприятная ситуация случится, если хакеры узнают ключ вашего любимого банковского сервиса по дистанционному управлению вашими деньгами. Хотя опять же, вроде бы для того, чтобы наверняка вскрыть обмен с банком (и если банк использует слабую проверку простоты), хакеру придётся ждать примерно 500 лет, пока наконец реализуется вероятность выпуска легко вычисляемого ключа, ведь ключи обычно имеют срок действия 1 год и потому чтобы гарантировать взлом нужно ждать пока не будут проведены 500 выпусков нового ключа. Вроде всё логично, но есть очередное «но» — банков на свете гораздо больше, чем 500 штук. Поэтому прямо сейчас, возможно именно в вашем любимом банке, хакеры могут получить доступ к вашим же деньгам.

Испугались? Наверняка нет, ведь все мы очень смелые, пока жареный петух не клюнет. Но пугаться всё же есть чего. Пусть вероятность успешной атаки хакеров именно на ваш банк и не так высока, но тем не менее, если она реализуется, то ваши деньги наверняка не найдут. Почему? Очень просто — стандартное первое предположение службы безопасности банка — виноват кто-то из сотрудников с соответствующими правами доступа. Не хакер, вероятность вмешательства которого они тоже рассматривают как минимальную, а именно кто-то свой. Поэтому хакер вполне может остаться безнаказанным.

Как устранить эту проблему? Нужно научиться получать гарантированно простые числа. Но как гарантировать их простоту? Для этого существуют четыре метода.

Первый метод — перебор с минимумом ограничений. Обычно это вариант усовершенствованного решета Эратосфена. Метод надёжный и гарантированный, но ограничен очень скромными порядками (менее сотни). Поэтому такой метод неприменим в криптографии.

Второй метод — перебор с более сильными ограничениями. Так, если период числа равен самому числу, минус один, то число гарантированно простое. Формула здесь такая — $N-1=p$. Но вот для определения равенства периода разности числа и единицы, используются очень тяжёлые методы, которые работают не для всех классов чисел. Поэтому применение их в криптографии упирается либо в ограниченность количества чисел специфических классов, либо во время выполнения проверки, если мы будем наращивать размер числа. И кроме того, даже числа определённого класса сами по себе ничего не гарантируют, что означает необходимость перебирать много чисел-кандидатов. Вот, например, числа Мерсенна, для которых есть безусловный тест простоты, уже все перебрали и выяснили, что в используемом в криптографии диапазоне их практически нет. Вот весь их список с близкими порядками — 1279, 2203, 2281, 3217. Как вы видите, от 1024 до 2048 есть лишь одно подходящее число. Но даже если взять остальные, то их количество нам очень наглядно намекает — не стоит! Значит опять нам не повезло с методом проверки.

Третий метод — вероятностный. Именно его сегодня используют, в том числе, в вашем любимом банке. Как он работает? Очень просто — проверяется остаток от деления произвольного числа в степени $N-1$ на $N$, если остаток равен единице — вероятно, что число простое. И вот слово «вероятно» здесь наиболее неприятное. И хотя вероятностные методы проверки простоты содержат ещё и дополнительные улучшения, но тем не менее, как было показано выше, стандартная библиотека Java довольно часто ошибается.

И наконец, четвёртый метод. Он работает не так быстро, как вероятностные тесты (хотя и здесь можно кое-что оптимизировать), но зато даёт гарантированно простые числа, да ещё и с легко определяемым периодом. Для чего нужен период простого числа, спросите вы? Например для генерации псевдослучайной или шифрующей последовательности. Точнее, зная период, мы точно знаем, сколько чисел будет в последовательности, что позволяет легко планировать, как долго мы можем использовать генератор последовательности на основе данного числа. Правда это уже относится к симметричному шифрованию, но тоже может пригодиться в ряде случаев. Далее же мы рассмотрим теорию гарантирующей простоту проверки чисел-кандидатов.

Теоретические основы


Чтобы понять, как генерировать гарантированно простые числа, нужно немного погрузиться в математику. Но не пугайтесь, математика здесь на уровне пятого класса.

Для полноты понимания рекомендуется посмотреть здесь подробности о том, что такое период и ряды остатков. Ну а кратко скажем, что ряд остатков образуется при делении «уголком» (известный любому школьнику метод) единицы на выбранное для исследования число. При этом на каждом шаге появляется разность между выше расположенным числом, с дописанным к нему ноликом, и произведением исследуемого числа на значение от 0 до 9 (для десятичной системы счисления) — это и есть остаток. Но шагов при делении «уголком» много, поэтому и остатков тоже много, а вместе они образуют ряд остатков, в котором один и тот же набор чисел периодически повторяется бесконечное число раз. Признаком начала нового цикла является остаток, равный единице. Количество остатков между единицами — это длина периода (или цикла). Вот, собственно, и всё, но также стоит понимать, что такой метод построения ряда остатков можно применить, используя любую систему счисления, и в частности далее чаще всего будет использована система с основанием 2 (а не 10, как принято в школе). При использовании других систем счисления все правила сохраняются, но количество вариантов произведений на каждом шаге будет другим, равным b (от base), то есть основанию системы счисления.

Итак, из ранее показанного мы знаем, что составные числа всегда дают относительно короткие периоды, не включающие ряд «запрещённых» значений. Зная разложение составного числа на множители легко вычислить, сколько значений обязаны отсутствовать в ряде остатков, формирующих период данного числа. Ряд остатков не будет содержать множители и все числа, кратные этим множителям, если сам ряд построен по основанию системы счисления, не кратному ни одному из делителей (то есть основание не делится на делители числа). Например, если множителей всего два, то количество кратных им остатков определяется по формуле $m=a+b-2$, где $m$ — количество кратных остатков, $a$ и $b$ — множители, формирующие наше составное число. Зная количество «запрещённых» остатков, легко вычислить, что ряд остатков длиною более половины значения $N-1$, будет иметь длину, равную $L=N-1-(a+b-2)=N-a-b+1$. То есть такой ряд будет на $a+b-2$ короче, чем ряд, который получился бы, если данное число было бы простым. Это объясняет, почему все числа с периодом, равным полному периоду (то есть $N-1$), оказываются простыми — если бы из последовательности остатков был исключён хотя бы один элемент (который «запрещён»), то период стал бы меньше $N-1$. В результате наличия такой закономерности мы наблюдаем работоспособность всех тех вероятностных тестов, которыми сегодня проверяют простоту числа. Эти тесты вычисляют значения в ряде остатков на позиции $N-1$ или $(N-1)/2$, и если значения равны $1$ или $N-1$, то можно с большой вероятностью утверждать, что число простое. Почему лишь с большой вероятностью, а не гарантировано? Потому что период вероятностные тесты не вычисляют, а между позициями $N-1$ и $(N-1)/2$ могут встретиться ещё единицы, что будет означать неравенство периода значению $N-1$, но если период не равен $N-1$, то число может быть составным. При этом само по себе отсутствие равенства не критично, важно другое — такое расположение единиц могут дать псевдопростые числа. Среди проверенных таким тестом чисел можно найти псевдопростые, которые являются составными и тем помогают хакерам, ворующим ваши деньги. Ведь каковы делители таких составных чисел? Они вполне могут оказаться достаточно малыми для того, чтобы злоумышленник легко вскрыл зашифрованный обмен данными.

Теперь вспомним, почему вообще могут появляться псевдопростые числа. Такие числа имеют короткие периоды, которые повторяются много раз в пределах полного периода $N-1$, а потому иногда могут «вмастить» и уложиться целое количество раз в полный период. Так, например, число 25 имеет период длиною 4 для системы счисления с основанием 7. $N-1$ для 25 равно 24, попробуем поделить: 24/4=6. То есть данный период уложился целое число раз в полный период. Этот факт можно проверить по приведённой выше формуле для сокращения периода, но с учётом того факта, что a и b в данном случае одинаковы. Максимально возможный период будет равен 24-4=20, здесь 24 — полное количество остатков, 4 — количество остатков, кратных 5. Но период не всегда бывает максимальным, а все остальные варианты можно получить поделив нацело максимальный период. 20 делится на 2,4,5,10, и как раз при делении 20 на число из приведённого списка мы получаем период длиною 4, который и даёт нам в конце полного периода $N-1$ единицу. А при проверке простоты проверяют как раз значения на позиции $N-1$, то есть последнее значение в полном периоде. И для 25 оно равно 1, что является признаком простоты числа. Хотя на самом деле однозначный признак простоты, это когда для всех оснований систем счисления, менее $N$, последнее значение в полном периоде равно единице. Но проверять все системы счисления для больших чисел нет никакой возможности, вот и появляются псевдопростые числа, которые иногда используются даже в криптографии, чем повышают уязвимость наших финансов.

Как устранить влияние псевдопростых чисел? В принципе просто — можно проверить периоды для всех систем счисления, но тогда на эту операцию для больших чисел не хватит никакого времени. Поэтому мы пойдём другим путём. И путь тоже простой (в буквальном смысле — в нём используются другие простые числа). Мы видели, что короткие периоды могут уложиться в полный период целое число раз, и это даёт нам псевдопростые числа. Но что нам мешает эти понедельники «взять и отменить»? Да в общем-то, ничего не мешает.

Для существования коротких периодов полный период ($N-1$) должен делиться на много делителей. Так для числа 25 полный период 24 делится на 2, 3, 4, 6, 8, 12. Вот столько есть возможностей для проникновения на охраняемую территорию псевдопростых чисел. Значит нам нужно просто взять в качестве полного периода простое число, ведь оно вообще ни на что не делится и потому автоматически спасает нас от вражеского элемента. Правда есть одно «но» — нам нужны простые числа, а они, как известно, нечётные (кроме одного исключения — 2), значит если $N-1$ простое, то само $N$ простым уже быть не может. Поэтому нам придётся допустить возможность для появления неполных периодов. Чем это плохо? Как мы видели выше — полный период гарантирует простоту числа, а вот про неполный — мы пока не доказали. Значит нам нужно доказать данный момент.

Доказательство довольно простое — для составного $N$ период, длиною, укладывающейся в $N-1$ два раза, полученный в системе счисления, не кратной делителям N, имеет всего два ряда остатков, и ни один из них не должен содержать чисел, кратных делителям числа $N$ («запрещённых» чисел). Если исключается хоть один элемент из одного ряда остатков, то и второй автоматически уменьшается на столько же (ведь один ряд равен другому, домноженному на любое число, отсутствующее в первом). Значит при наличии делителей у числа $N$, мы получим меньшую суммарную длину двух периодов со всеми «разрешёнными» остатками, нежели значение полного периода и тем самым сдвинем единицу с места в конце полного периода, чем обеспечим отсутствие псевдопростоты. Но может ли количество кратных делителям остатков оказаться таким, чтобы дать ровно половину или одну треть полного периода? Ведь тогда мы получили бы, например, две трети «разрешённых» остатков (два ряда) и одну треть «запрещённых», а в сумме — полный период. Но для получения одной трети нам нужно обеспечить делимость полного периода ($N-1$) на 3, что мы можем довольно легко исключить — берём простое число, умножаем его на два и прибавляем к результату единицу — вуаля, перед нами гарантированно исключающее псевдопростоту число. У него количество кратных делителям остатков не может быть равным одной третьей полного периода, потому что на три полный период не делится. Остаётся вариант с половиной остатков, кратных делителям $N$. Этот вариант устраняется чуть сложнее, поэтому ниже будет небольшое отступление.

Вычисление периода, или все числа — дети Мерсенна


Период любого числа можно вычислить. В простейшем случае вычисление производится простым делением уголком $1/N$ до тех пор, пока нам не встретится остаток, равный единице (в системе счисления, не кратной делителям $N$). Но для больших чисел это нереально долго. Поэтому есть необходимость в выводе формулы для вычисления периода. И такая формула существует, правда не в идеальном виде, когда на входе имеем число, а на выходе — период. Формула такая:

$N=\frac{b^{m+n+1}-1} {b^n+k}$



Здесь $N$ — исследуемое число, $b$ — основание используемой системы счисления (base), $m$ — максимальная степень $b$, при которой результат возведения в степень меньше $N$ (по другому — индекс старшего разряда в $N$ в системе счисления $b$), $n$ — расстояние слева направо в ряду остатков от индекса $m+1$ (равного количеству разрядов в $N$) до единицы, $k$ — некоторый целый коэффициент.

Вывод формулы


По определению формулы понятно, что (1) $b^{m+1}-N=x$, то есть первая степень $b$, которая больше $N$, позволяет вычесть $N$ и получить некую разницу в виде $x$. Так же из определения следует, что (2) $x*b^n-k*N=1$, здесь $k$ — целочисленный коэффициент. Если домножить (1) на $b^n$ и заменить $x*b^n$ на его значение из (2), которое равно $kN+1$, то получим $b^{m+n+1}=N*b^n+kN+1=N*(b^n+k)+1 => N=\frac{b^{m+n+1}-1}{b^n+k}$.

Полезные свойства


Как мы видим, используя эту формулу можно получать числа с заранее известным периодом. Правда есть одна сложность — нужно подбирать коэффициент $k$, что для больших чисел опять превращается в нечто недостижимое. Но формула даёт нам другое — мы наглядно видим, как формируются все числа. Да, абсолютно все положительные целые числа можно получить по этой формуле, ведь у всех чисел есть какой-то период. И что интересно — все числа получаются от деления числа Мерсенна на один из его делителей. Вспомним, что числом Мерсенна называется такое число, которое равно $2^n-1$. В формуле же в числителе мы видим обобщение для чисел Мерсенна с любым основанием (включая 2, разумеется). И если нас интересуют простые числа, то другие основания, кроме 2, нам вообще не понадобятся.

Зная, что мы делим число Мерсенна, нам было бы полезно уметь вычислять период чисел именно для случая деления чисел Мерсенна (вспомним расширенное понятие периода здесь). Но что очень замечательно — формула для вычисления мерсенновского периода совпадает с формулой для вычисления периода типа $1/N$. Ну а для вывода формулы деления чисел Мерсенна используется тот же принцип, что и при выводе формулы для деления $1/N$, за исключением формулы для вычисления значения в ряде остатков с индексом $n$, которая выглядит так — $\frac{b^{n+1}-1}{b-1}$, а для двоичных чисел получаем $2^{n+1}1$.

Теперь у нас все карты на руках и мы можем начинать игру по вскрытию псевдопростых засланцев в рядах простых чисел.

Как мы знаем из предыдущих серий, при делимости нацело мерсенновский период числа должен укладываться в количество разрядов числа Мерсенна целое число раз. Поэтому в формуле выше решение в целых числах возможно лишь если знаменатель имеет период либо равный числителю, либо кратно меньший. Но кратно меньший период даст нам, в том числе, и делители самого числа $N$, ведь если $N$ делит число Мерсенна, то значит и его делители тоже делят число Мерсенна. И это очень важный момент, ведь из него вытекает, что длины периодов делителей N делят длину периода $N$ нацело. То есть если мы возьмём $N$ такое, что его период равен периоду числителя, тогда все делители $N$ будут одновременно и делителями числа Мерсенна, а потому должны укладываться в период и $N$, и числа Мерсенна целое число раз.

Снова про половину периода


Теперь вспомним, что мы остановились на поиске способа доказать, что мы можем исключить случай, когда половина длины ряда остатков числа кратна его делителям. Доказать мы это хотели для устранения псевдопростых чисел при выборе одного простого числа в качестве основы для конструирования периода следующего простого числа. Так вот теперь мы знаем, что если конструируемое число имеет делители, то их периоды всегда делят период конструируемого числа нацело. Тогда нам остаётся проверить — какие делители могут уложиться в ранее заданные ограничения на конструируемое число. А ограничения такие — его период делится лишь на $2$ и на $(N-1)/2$. Значит и делителями могут быть лишь числа с периодами $2$ и $(N-1)/2$. Период $2$ имеет число $2$, более ни одно число такого периода не имеет. Период $2$ не укладывается целое количество раз в $(N-1)/2$, ведь $(N-1)/2$ — простое, поэтому делимость на три исключается при таком периоде. Значит остаётся лишь одна возможность — делители имеют период $(N-1)/2$. Но число не может быть меньше или равно своему периоду, поэтому минимальное значение для делителей такое — $(N-1)/2+1$. Если перемножить два минимальных делителя, то получим — $(N-1)^2/4+(N-1)+1=(N^2-2N+1+4N)/4=(N^2+2N+1)/4$, такое значение должно быть не больше $N$, ведь мы говорим о делителях $N$. Тогда получаем неравенство $(N^2+2N+1)/4\leq N => N^2-2N+1\leq 0$. Это неравенство может быть отрицательным или равным нулю только при $N=1$, поэтому для всех сконструированных таким способом чисел псевдопростота исключена, если полученное число больше $1$. Ну а для меньших чисел мы можем проверить простоту даже в уме.

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

Но конечно, жизнь была бы мёдом, если бы все проблемы решались так просто. Исключив псевдо-простоту, мы так и не исключили числа, которые ни от кого не прячутся и ни подо что не маскируются. А с ними есть ещё одна проблема — мы пока что умеем проверять произвольный член последовательности остатков лишь при помощи возведения в степень с последующим взятием остатка. Но такой метод очень медленный. Точнее, он достаточно быстр для чисел, используемых в криптографии, но вот для поиска больших простых он не пригоден в домашних условиях, ибо требует много лет вычислений обычного домашнего компьютера, что не даёт нам возможность получить 400k$ (как показано здесь).

Но тем не менее, для вычисления криптографических простых у нас уже почти всё готово. Хотя остаётся ещё наш старый друг — максимализм. Он спрашивает — раз можно использовать период $(N-1)/2$, то что мешает использовать периоды $(N-1)/4, (N-1)/6, (N-1)/8$ и т.д.? И оказывается, что при должной осторожности — ничего не мешает!

Точно так же, как и в случае с периодом $(N-1)/2$, для периода $(N-1)/4$ имеем возможность задать ограничение снизу, умножив простое число на 4 и прибавив 1. Тогда мы гарантируем себя от псевдопростых с периодами менее $(N-1)/4$. Значит остаётся рассмотреть возможность появления псевдопростых с периодом, кратным 4, 3, 2 (1 для составных исключается, как показано выше). Из формулы для вычисления числа по периоду видно, что период делимого числа должен быть равен наименьшему общему кратному его делителей, из чего следует, что возможный период числа $N$ должен не только укладываться целое количество раз в $N-1$ (иначе не будет псевдопростоты), но и содержать в себе целое количество периодов делителей. Таким образом резко ограничивается количество возможных делителей псевдопростого числа. Поскольку период любого числа не может быть длиннее $N-1$, то для возможного делителя $N$, дающего в результате деления 3, период не может быть длиннее $N/3-1$, кроме того учтём, что период числа 3 равен 2. $N/3$ должно быть нечётным, поскольку нечётным является $N$. Из перечисленного следует, что чётный период N/3-1 является наименьшим общим кратным с периодом 2 числа 3, значит возможный период потнециально псевдопростого N должен быть равен периоду числа $N/3$. Всего есть одно значение величины периода $N$, для которого возможна псевдопростота, это $(N-1)/4$. Для остальных значений либо $N/3$ слишком мало, либо его период (а вместе с ним и период $N$) уйдёт в запрещённую область ниже $(N-1)/4$. Такая же история и с нечётными числами, меньше $N/3$, но у них периоды не могут быть больше $(N-1)/4$, а потому все они просто исключаются из рассмотрения. Теперь покажем, что $N/3$ не может иметь период $(N-1)/4$, а значит не может делить полный период нацело. Сначала вспомним формулу $m=a+b-2$, которая даёт нам количество кратных делителям остатков для числа, делящегося только на $a$ и $b$. В нашем случае $N$, как предполагается, может делиться на $N/3$ и на 3, все остальные делители исключены, поэтому получаем — $m=N/3+3-2=N/3+1$. Теперь из полного периода нужно вычесть «запрещённые» значения, которых будет $N/3+1$, а потом разделить на 4. Получаем возможный период произведения $3*N/3$: $p=(N-1-N/3-1)/4=N/6-1/2$, что меньше $(N-1)/4$, то есть период длиной $(N-1)/4$ невозможен из-за необходимости учитывать «запрещённые» остатки, которая уводит все возможные псевдопростые периоды в запрещённую область (меньше $(N-1)/4$). Значит такая ситуация гарантирует нам, что сконструированное число не псевдопростое, а потому мы снова можем быть уверены в результатах последующего вероятностного теста простоты.

Аналогично, и с учётом делимости полного периода, можно получить такие же результаты и для других значений. Но если мы хотим так получать криптографические простые числа, то домножая на 2,4,6 мы будем очень долго наращивать размер числа, поэтому есть смысл посмотреть в сторону другого варианта — умножения двух простых чисел. Если мы умножим одно простое на другое, то получим нечётное число, поэтому обязательно нужно домножить ещё и на 2, чтобы получить чётный полный период и нечётное число-кандидат в простые. Полный период в таком случае будет делиться на $2, a, b, 2a, 2b, ab$, где $a$ и $b$ — простые числа. Теперь нам нужно доказать, что либо указанные периоды не дадут псевдопростоты, либо обнаружить признаки, предупреждающие нас о наличии псевдопростоты. Сразу заметим, что если период будет равен $2*a*b$, то число будет простым (как показано выше). Так же выше показано, что число с половинным периодом не может быть псевдопростым, поэтому период длиной $ab$ можно исключить. Хотя для полноты картины можно проверить период $ab$ альтернативными способами. Так, если период равен $ab$, то учитывая, что $a$ и $b$ простые, становится понятно, что наименьшее общее кратное периодов делителей $N$ может быть равно только $ab$, при этом периоды делителей могут быть равны либо $ab$ у обоих, либо у одного $a$, а у другого $b$. Поскольку период всегда меньше самого числа, то $(ab+x)*(ab+y)$ явно будет больше $2ab+1$. Значит и псевдопростота при таком периоде невозможна. Поэтому остаётся проверить периоды $2, a, b, 2a, 2b$. 2 меньше минимального возможного периода периода для всех чисел, больше 3, поэтому такой период исключаем. Теперь предположим, что сконструированное число делится на $m$ и $n$, тогда при равенстве периода $N$ значению $a$, их периоды тоже будут равны $a$, потому что это единственное наименьшее общее кратное для двух чисел, ведь $a$ не делится ни на что. Отсюда вытекает, что $(a+x)*(a+y)=N=ab*2+1$, где $a+x=m$ — первый возможный сомножитель с периодом $a$, и $a+y=n$ — второй. Далее: $a^2+ax+ay+xy=2ab+1$ $=>$ $a^2+ax+ay+(ka+r)=2ab+1$ $=>$ $a+x+y+k=(2ab+1-r)/a$. Здесь $r$ может быть равен только единице, иначе слева не получится целое число. Тогда: $x+y+k=2b-a$, из чего следует, что если $а \geq 2b$, то псевдопростых с периодом $a$ быть не может. Остаётся проверить псевдопростоту при $a<2b$, что можно сделать проверкой значения в ряду остатков на позиции $a$, если там 1, то такое число может быть псевдопростым и его стоит исключить из дальнейших операций. Рассуждения для $b$ полностью аналогичны, что означает необходимость проверки равенства единице и его остатка при условии $b<2a$.

Для периода $2a$ имеем: $4a^2+2ax+2ay+xy=2ab+1$ $=>$ $4a^2+2ax+2ay+(ka+r)=2ab+1$ $=>$ $4a+2x+2y+k=(2ab+1-r)/a$ $=>$ $2x+2y+k=2b-4a$. Получаем, что при $4a \geq 2b$ (или эквивалентно — $2a \geq b$) опять не может быть простоты, но если $2a<b$, то нужно проверять остаток на позиции $2a$. Аналогично и для $b$ — проверяем если $2b<a$. В сумме получаем и для $a$ и для $b$ необходимость выполнить проверку лишь для позиций $2a$ и $2b$, потому что периоды $a$ и $b$ дадут повтор как раз в положении $2a$ и $2b$. Проверку выполняем лишь при указанных выше условиях, что почти в два раза ускорит процесс при проверке больших значений $a$ или $b$, ведь для них обязательно выполняется $a \geq 2b$ при предложенной ниже схеме генерации простых, а меньшие значения будут проверяться очень быстро из-за их небольшой величины.

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

Кроме того, открывается путь для проверки простоты произвольного числа, при условии нахождения делителей его полного периода ($N-1$). Так, для простых чисел < 126 000 000 количество принадлежащих к классу «перемноженных простых» равняется 676625, при общем количестве простых 7163812, что даёт нам немного меньше, чем 9.5%. Для простых чисел до миллиарда имеем 3.49% относящихся к классу 2p+1, 1.8116% из класса 4p+1, 2.4709% из класса 6p+1, а всего — 7.7746%, где p — простое число. Правда следует учесть, что разложение полного периода больших чисел сильно затруднено. В таком случае можно предложить рекурсивную проверку, которая немного повысит размер доступных для проверки чисел, но при этом сильно сократит долю проходящих такую проверку чисел, ведь если коэффициент, определяющий принадлежность к классам рекурсивно простых чисел, принять равным 0.2, то уже на второй проверке будем иметь всего 0.04, или 4% от общего количества простых чисел. Но тем не менее, в ряде случаев такой подход может принести пользу.

Практические результаты


Собственно генератор, разумеется, был написан и протестирован, благо сложность там минимальная. По ходу проверки выяснилось, что вполне работоспособным будет следующий алгоритм:

Получаем на вход, например, 1000 начальных простых чисел, которые можно сгенерировать при помощи решета Эратосфена или просто скачать отсюда. Затем перемножаем каждое значение с каждым оставшимся и не забываем домножить на два, а потом прибавить единицу. Получившийся кандидат часто делится на 3, поскольку у простых чисел есть специфическая особенность, аналогичная наличию заряда у частиц в физике. Простые с одинаковым «зарядом» отталкиваются, а с разными — притягиваются. В данном случае «заряд» это остаток от деления на 3. То есть если остаток от деления на 3 у обоих простых одинаковый — новое простое не получится, потому что оно будет делиться на 3. А если остаток отличается — вот тогда мы можем получить подходящего кандидата в простые. При этом все полученные перемножением простые «синхронизируются», то есть получают одинаковый остаток, равный 2. Поэтому снова перемножать их самих на себя бесполезно. Значит для получения новых простых нам нужно отфильтровать ту часть начальной 1000, у которой модуль тройки (остаток от деления на 3) равен 1. Таким образом после первого перемножения всех со всеми, получаем подросшие в размере числа, которые уже бессмысленно перемножать друг с другом, поэтому для дальнейшего роста размера (до используемого в криптографии) мы должны снова и снова перемножать полученные простые на ранее отобранные «противоположно заряженные» числа. После перемножений и добавления единицы выполняем проверки по критерию возможности псевдопростоты и если критерий выполняется, то проверяем остаток на позиции 2a для каждого кандидата. Если там 1, то кандидат отклоняется. Далее каждый кандидат проверяется обычным вероятностным тестом, то есть вычисляется значение в ряду остатков на позиции $(N-1)/2$, если это 1 или $N-1$, то перед нами — гарантированно простое число.

При выполнении генерации следует учитывать, что на каждой итерации получается рост количества сгенерированных простых, то есть коэффициент размножения для 1000 начальных простых много больше единицы. Поэтому для получения криптографических простых необходимо ограничивать генерацию, иначе она займёт очень большое время, да и памяти может не хватить. При этом не стоит сокращать начальный набор простых, потому что генерация должна быть максимально случайной, чтобы, зная её алгоритм, злоумышленник не предсказал получаемые значения. Для этого необходимо реализовать механизм отсечения ветвей дерева генерации, позволяющий каждый раз получать уникальные простые, расположенные достаточно далеко от ранее сгенерированных. И конечно, отсечение лишнего существенно ускоряет процесс.

Время выполнения теста зависит от количества сгенерированных кандидатов. Каждый из кандидатов проходит вероятностный тест, что в наибольшей степени замедляет генерацию. В тестовых запусках для получения нескольких сотен простых в диапазоне $2^{900}$$2^{1200}$ было потрачено от 5 до 20 минут. Такая разница времени объясняется различными путями, которые проходит алгоритм в дереве генерации. Так изначально генерация ограничивается примерно 10 числами, но с приближением криптографического размера генерация затухает из-за существенного сокращения коэффициента размножения с ростом величины числа. Поэтому необходимо повышать количество промежуточных простых, но на сколько конкретно — сказать сложно, а потому для ограничения используется простое повышение допустимого количества в два раза с ростом размера числа и превышением грубо задаваемого порога. В результате в рамках ограничений количество новых простых может плавать и тем самым вносить существенное различие в общее время генерации.

Далее приводится текст процедуры на Java, выполняющей генерацию простых чисел. Она, естественно, не удовлетворяет криптографическим требованиям, которые выходят далеко за рамки кода программы и в основном касаются организационных моментов. В чисто программной части в процедуре не реализован механизм отсечения ветвей дерева генерации, за исключением простейшего ограничения. Но тем не менее в качестве примера реализации алгоритма генерации программа может чем-то помочь.

Входными параметрами является начальный список простых и PrintWriter для сохранения результата в файл. После завершения алгоритма в файле будут все произведения сгенерированных простых с теми исходными числами, которые имеют модуль трёх равным единице. Результат можно проверить при помощи онлайн-сервисов, реализующих факторизацию чисел, но следует понимать, что они могут использовать вероятностную проверку на простоту перед выполнением факторизации, а потому для проверки предложенного подхода становятся бесполезными (ведь все числа и так проверены вероятностным тестом). Но ряд из них сразу берутся разлагать предложенное число на множители, такие сайты могут вселить некоторую дополнительную уверенность в правильности предложенного метода.

/** Генерация простых чисел путём многократного перемножения входного набора заведомо простых.
 * На каждой итерации одно входное простое перемножается с другим, затем результат умножается на 2,
 * после чего к результату прибавляется единица:
 * probablyPrime=prime1*prime2*2+1
 * Каждое потенциально простое далее проверяется на псевдопростоту в функции addIfPrime.
 * Если псевдопростота исключена, то потенциально простое проверяется на простоту при помощи аналога
 * теста Ферма, являющегося детерминированным для любых чисел, не являющихся псевдопростыми по основанию 2. */
public static void generatePrimes(List<Integer> primes, PrintWriter pw)
{
	// Список простых чисел с остатком при деление по модулю 3 = 1.
	List<BigInteger> mod3_1 = new ArrayList<BigInteger>();
	// Список чисел признанных простыми функцией addIfPrime()
	List<BigInteger> l = new ArrayList<BigInteger>();
	// Экземпляры больших чисел со значением 3 и 2.
	BigInteger three = BigInteger.valueOf(3), two = BigInteger.valueOf(2);
	// Цикл обработки простых чисел, данных в виде входящего параметра в процедуру.
	// В цикле для каждого входного простого вычисляется его произведение со всеми остальными простыми.
	// Если результат перемножения не равен единице по модулю 3, то такие числа игнорируются из-за
	// порождения при последующих пермножениях чисел, кратных трём.
	for (int k = 0; k < primes.size() - 1; k++)
	{
		// Рассматриваемое простое число (а)
		BigInteger seed = BigInteger.valueOf(primes.get(k));
		// Удвоенное простое число (2а)
		BigInteger s2 = seed.shiftLeft(1);
		// Остаток при делении `a` на 3
		BigInteger r0 = seed.remainder(three);
		// Проверка на остаток = 1
		if (r0.intValue() == 1) mod3_1.add(seed);
		// Цикл по тем простым числам, с которыми данное число пока что не перемножалось
		for (int i = k + 1; i < primes.size(); i++)
		{
			BigInteger p = BigInteger.valueOf(primes.get(i)); // Перевод типа int в тип BigInteger
			BigInteger r = p.remainder(three); // Остаток от деления p на 3
			// Если остатки от деления очередного простого числа на три и ранее выбранного так же простого
			// равны друг другу, то такую пару игнорируем из-за обязательной делимости результата на 3
			if (r.intValue() == r0.intValue()) continue; // divisible by 3
			else addIfPrime(p, seed, s2, two, l); // Если делимости на 3 нет, то проверяем на простоту
		}
	}
	// Фиксируем ссылку на полученные выше результаты перемножений и проверок простоты в перменной ps
	// (ps - сокращение от primes)
	List<BigInteger> ps = l;
	// В этом цикле каждое ранее найденное простое перемножается с ранее отобранными простыми,
	// дающими по модулю 3 единицу. Результат перемножения проверяется на простоту функцией addIfPrime.
	// 
	do
	{
		System.out.println("found " + l.size() + ", bits=" + l.get(0).bitLength());
		l = new ArrayList<BigInteger>();
		for (int k = 0; k < ps.size(); k++)
		{
			BigInteger seed = ps.get(k);
			BigInteger s2 = seed.shiftLeft(1);
			// Проходим по списку равных единице по модулю тройки чисел и перемножаем их на
			// ранее полученные простые результаты аналогичных перемножений
			for (int i = 0; i < mod3_1.size(); i++)
				addIfPrime(mod3_1.get(i), seed, s2, two, l);
			// Здесь проверяем разрядность полученных простых чисел. Если разрядность превышает порог в
			// 700, 800, 900, то меняем максимально допустимое значение размера списка получаемых простых
			// с целью ограничения мощности генерации. Если генерацию не ограничивать, то количество промежуточных
			// простых, меньших чем требуемые нам числа криптографических порядков, будет очень большим.
			int n = 100000; // change following to adjust for required bit count
			if (l.size() > 0)
				if (l.get(0).bitLength() < 700) n = 10;
				else if (l.get(0).bitLength() < 800) n = 20;
				else if (l.get(0).bitLength() < 900) n = 40;
			if (l.size() > n) break; // Если количество полученных простых больше максимально допустимого, то выходим из данного цикла
		}
		ps = l; // Фиксируем ссылку на полученные выше результаты перемножений и проверок простоты в перменной ps
		// Записываем все полученные простые в поток, полученный на входе процедуры
		for (int i = 0; i < l.size(); i++)
			pw.println(l.get(i));
		pw.flush(); // дописываем буфер потока, что бы зафиксировать все полученные результаты
	}
	while (l.size() > 0);
	System.out.println("Done");
}
/** Вычисляем новое простое, перемножая два известных простых, затем удваивая произведение и прибавляя единицу.
 * Полученое потенциально простое число проверяется на потенциальную псевдопростоту согласно критериям из статьи.
 * Если число не является псевдопростым, то далее проводится стандартный вероятностный тест простоты,
 * который в данном случае является детерминированным в следствии устранения возможности появления
 * псевдопростых чисел предыдущей фильтрацией. */
private static void addIfPrime(BigInteger a, BigInteger b, BigInteger b2, BigInteger two, List<BigInteger> l)
{
	// a2=2*a; fp=a*b*2; n=a*b*2+1;
	BigInteger a2=a.shiftLeft(1), fp=b.multiply(a2), n=fp.add(BigInteger.ONE);
	BigInteger r=null;
	if (a2.compareTo(b)<0) r=two.modPow(a2,n); // 2a<b
	else if (a.compareTo(b2)<0) r=two.modPow(a,n); // a<2b
	if (r!=null && r.compareTo(BigInteger.ONE)==0) return;
	r=null;
	if (b2.compareTo(a)<0) r=two.modPow(b2,n); // 2b<a
	else if (b.compareTo(a2)<0) r=two.modPow(b,n); // b<2a
	if (r!=null && r.compareTo(BigInteger.ONE)==0) return;
	// Детерминированная проверка простоты (для случая, исключающего наличие псевдопростых числе)
	// при помощи вычисления остатка по формуле:
	// r=2^(fp/2) mod n
	r=two.modPow(fp.shiftRight(1),n);
	if (r.compareTo(BigInteger.ONE)!=0) return;
	l.add(n);
}

Ну а теперь, когда вы (ну почти) всё знаете о генерации простых чисел, возможно ваши интересы всё же выйдут за рамки одной только криптографии и вам станет интересно позаниматься теорией чисел, благо она, как было указано выше, доступна для учеников пятого класса, но при этом ещё и позволяет самостоятельно находить истинные бриллианты, не ожидая помощи от серьёзных математиков.
Теги:
Хабы:
+13
Комментарии 22
Комментарии Комментарии 22

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн