Общая схема обеспечения безопасности при обмене данными опирается на сложность разложения большого составного числа на множители. При этом если множителей много, то они будут маленькими, а это сильно упрощает взлом системы безопасности. Поэтому большое составное число получают при помощи умножения двух простых чисел определённого размера. Но здесь очевидна одна проблема — как выбрать достаточно большое простое число?
Сначала определимся с размером. В современной криптографии порядок составного числа определяется формулой , то есть собственно порядок равен 2048 в двоичной систем счисления. А делители у этого составного числа бывают порядка 1024, то есть где-то около значений . Почему 1024? Потому что уже лет десять назад было разложено на множители число порядка 768, а сегодня уже весьма вероятно разложение составных чисел порядка 1024. Вот и решено для надёжности увеличить порядок сразу в два раза, то есть до 2048. Ну а отталкиваясь от этого порядка легко определить порядок необходимых сомножителей.
Но что будет, если порядок сомножителей окажется много меньше 1024? Тогда за приемлемое время можно разложить составное число, даже если оно имеет порядок 2048. Значит нужно гарантировать, что выбранные нами сомножители действительно имеют порядок близкий к 1024 и при этом являются простыми, то есть не разлагаются на множители. Но вот сам выбор осуществляется по вероятностной схеме, а это как раз и ведёт к потенциальным проблемам.
Проверка вероятности простоты числа осуществляется на основе предположения о том, что число «скорее всего» простое, если его период (о котором рассказывается здесь) является делителем самого числа, минус единица (). В виде формулы это выглядит так:
Здесь — исследуемое число, — значение от до (оно определяет основание системы счисления, в которой вычисляется период), — период исследуемого числа. Операция здесь означает взятие остатка от деления первого операнда на второй.
Существующие проверки опираются именно на такой подход, поскольку для больших чисел безусловное определение простоты известными тестами занимает очень много времени. Но минус такой проверки очевиден — иногда можно получить составное число, вместо простого. При этом какие делители окажутся в составе такого числа — никто не знает. Точнее это сможет выяснить злоумышленник, применив известные методы факторизации, которые начинают работать за приемлемое время, если множители окажутся порядка менее чем несколько сотен бит.
Как часто ошибается вероятностная проверка простоты? Достаточно редко. Бывает одна ошибка на несколько десятков тысяч простых, а бывает и две, но ничто не гарантирует нас и от трёх. Кроме того, многое зависит от используемого теста. Так, например в стандартной библиотеке языка Java в классе BigInteger реализована проверка, которая допускает промах 2-3 раза на тысячу простых. То есть не стоит думать, что если теоретически ошибок очень мало, то и на практике всё будет в шоколаде.
Чем это опасно? Вроде бы и не так страшно, как могут сказать некоторые, ведь ну что с того, если какой-нибудь сайт, закрывающий просмотр своих страниц протоколом https, получит легко вычисляемый ключ, то какие от этого будут проблемы? Ну узнают хакеры, какие новости вы читали на этом сайте, а что толку? Но на самом деле шифрование производится не только при просмотре веб-страниц. Более неприятная ситуация случится, если хакеры узнают ключ вашего любимого банковского сервиса по дистанционному управлению вашими деньгами. Хотя опять же, вроде бы для того, чтобы наверняка вскрыть обмен с банком (и если банк использует слабую проверку простоты), хакеру придётся ждать примерно 500 лет, пока наконец реализуется вероятность выпуска легко вычисляемого ключа, ведь ключи обычно имеют срок действия 1 год и потому чтобы гарантировать взлом нужно ждать пока не будут проведены 500 выпусков нового ключа. Вроде всё логично, но есть очередное «но» — банков на свете гораздо больше, чем 500 штук. Поэтому прямо сейчас, возможно именно в вашем любимом банке, хакеры могут получить доступ к вашим же деньгам.
Испугались? Наверняка нет, ведь все мы очень смелые, пока жареный петух не клюнет. Но пугаться всё же есть чего. Пусть вероятность успешной атаки хакеров именно на ваш банк и не так высока, но тем не менее, если она реализуется, то ваши деньги наверняка не найдут. Почему? Очень просто — стандартное первое предположение службы безопасности банка — виноват кто-то из сотрудников с соответствующими правами доступа. Не хакер, вероятность вмешательства которого они тоже рассматривают как минимальную, а именно кто-то свой. Поэтому хакер вполне может остаться безнаказанным.
Как устранить эту проблему? Нужно научиться получать гарантированно простые числа. Но как гарантировать их простоту? Для этого существуют четыре метода.
Первый метод — перебор с минимумом ограничений. Обычно это вариант усовершенствованного решета Эратосфена. Метод надёжный и гарантированный, но ограничен очень скромными порядками (менее сотни). Поэтому такой метод неприменим в криптографии.
Второй метод — перебор с более сильными ограничениями. Так, если период числа равен самому числу, минус один, то число гарантированно простое. Формула здесь такая — . Но вот для определения равенства периода разности числа и единицы, используются очень тяжёлые методы, которые работают не для всех классов чисел. Поэтому применение их в криптографии упирается либо в ограниченность количества чисел специфических классов, либо во время выполнения проверки, если мы будем наращивать размер числа. И кроме того, даже числа определённого класса сами по себе ничего не гарантируют, что означает необходимость перебирать много чисел-кандидатов. Вот, например, числа Мерсенна, для которых есть безусловный тест простоты, уже все перебрали и выяснили, что в используемом в криптографии диапазоне их практически нет. Вот весь их список с близкими порядками — 1279, 2203, 2281, 3217. Как вы видите, от 1024 до 2048 есть лишь одно подходящее число. Но даже если взять остальные, то их количество нам очень наглядно намекает — не стоит! Значит опять нам не повезло с методом проверки.
Третий метод — вероятностный. Именно его сегодня используют, в том числе, в вашем любимом банке. Как он работает? Очень просто — проверяется остаток от деления произвольного числа в степени на , если остаток равен единице — вероятно, что число простое. И вот слово «вероятно» здесь наиболее неприятное. И хотя вероятностные методы проверки простоты содержат ещё и дополнительные улучшения, но тем не менее, как было показано выше, стандартная библиотека Java довольно часто ошибается.
И наконец, четвёртый метод. Он работает не так быстро, как вероятностные тесты (хотя и здесь можно кое-что оптимизировать), но зато даёт гарантированно простые числа, да ещё и с легко определяемым периодом. Для чего нужен период простого числа, спросите вы? Например для генерации псевдослучайной или шифрующей последовательности. Точнее, зная период, мы точно знаем, сколько чисел будет в последовательности, что позволяет легко планировать, как долго мы можем использовать генератор последовательности на основе данного числа. Правда это уже относится к симметричному шифрованию, но тоже может пригодиться в ряде случаев. Далее же мы рассмотрим теорию гарантирующей простоту проверки чисел-кандидатов.
Теоретические основы
Чтобы понять, как генерировать гарантированно простые числа, нужно немного погрузиться в математику. Но не пугайтесь, математика здесь на уровне пятого класса.
Для полноты понимания рекомендуется посмотреть здесь подробности о том, что такое период и ряды остатков. Ну а кратко скажем, что ряд остатков образуется при делении «уголком» (известный любому школьнику метод) единицы на выбранное для исследования число. При этом на каждом шаге появляется разность между выше расположенным числом, с дописанным к нему ноликом, и произведением исследуемого числа на значение от 0 до 9 (для десятичной системы счисления) — это и есть остаток. Но шагов при делении «уголком» много, поэтому и остатков тоже много, а вместе они образуют ряд остатков, в котором один и тот же набор чисел периодически повторяется бесконечное число раз. Признаком начала нового цикла является остаток, равный единице. Количество остатков между единицами — это длина периода (или цикла). Вот, собственно, и всё, но также стоит понимать, что такой метод построения ряда остатков можно применить, используя любую систему счисления, и в частности далее чаще всего будет использована система с основанием 2 (а не 10, как принято в школе). При использовании других систем счисления все правила сохраняются, но количество вариантов произведений на каждом шаге будет другим, равным b (от base), то есть основанию системы счисления.
Итак, из ранее показанного мы знаем, что составные числа всегда дают относительно короткие периоды, не включающие ряд «запрещённых» значений. Зная разложение составного числа на множители легко вычислить, сколько значений обязаны отсутствовать в ряде остатков, формирующих период данного числа. Ряд остатков не будет содержать множители и все числа, кратные этим множителям, если сам ряд построен по основанию системы счисления, не кратному ни одному из делителей (то есть основание не делится на делители числа). Например, если множителей всего два, то количество кратных им остатков определяется по формуле , где — количество кратных остатков, и — множители, формирующие наше составное число. Зная количество «запрещённых» остатков, легко вычислить, что ряд остатков длиною более половины значения , будет иметь длину, равную . То есть такой ряд будет на короче, чем ряд, который получился бы, если данное число было бы простым. Это объясняет, почему все числа с периодом, равным полному периоду (то есть ), оказываются простыми — если бы из последовательности остатков был исключён хотя бы один элемент (который «запрещён»), то период стал бы меньше . В результате наличия такой закономерности мы наблюдаем работоспособность всех тех вероятностных тестов, которыми сегодня проверяют простоту числа. Эти тесты вычисляют значения в ряде остатков на позиции или , и если значения равны или , то можно с большой вероятностью утверждать, что число простое. Почему лишь с большой вероятностью, а не гарантировано? Потому что период вероятностные тесты не вычисляют, а между позициями и могут встретиться ещё единицы, что будет означать неравенство периода значению , но если период не равен , то число может быть составным. При этом само по себе отсутствие равенства не критично, важно другое — такое расположение единиц могут дать псевдопростые числа. Среди проверенных таким тестом чисел можно найти псевдопростые, которые являются составными и тем помогают хакерам, ворующим ваши деньги. Ведь каковы делители таких составных чисел? Они вполне могут оказаться достаточно малыми для того, чтобы злоумышленник легко вскрыл зашифрованный обмен данными.
Теперь вспомним, почему вообще могут появляться псевдопростые числа. Такие числа имеют короткие периоды, которые повторяются много раз в пределах полного периода , а потому иногда могут «вмастить» и уложиться целое количество раз в полный период. Так, например, число 25 имеет период длиною 4 для системы счисления с основанием 7. для 25 равно 24, попробуем поделить: 24/4=6. То есть данный период уложился целое число раз в полный период. Этот факт можно проверить по приведённой выше формуле для сокращения периода, но с учётом того факта, что a и b в данном случае одинаковы. Максимально возможный период будет равен 24-4=20, здесь 24 — полное количество остатков, 4 — количество остатков, кратных 5. Но период не всегда бывает максимальным, а все остальные варианты можно получить поделив нацело максимальный период. 20 делится на 2,4,5,10, и как раз при делении 20 на число из приведённого списка мы получаем период длиною 4, который и даёт нам в конце полного периода единицу. А при проверке простоты проверяют как раз значения на позиции , то есть последнее значение в полном периоде. И для 25 оно равно 1, что является признаком простоты числа. Хотя на самом деле однозначный признак простоты, это когда для всех оснований систем счисления, менее , последнее значение в полном периоде равно единице. Но проверять все системы счисления для больших чисел нет никакой возможности, вот и появляются псевдопростые числа, которые иногда используются даже в криптографии, чем повышают уязвимость наших финансов.
Как устранить влияние псевдопростых чисел? В принципе просто — можно проверить периоды для всех систем счисления, но тогда на эту операцию для больших чисел не хватит никакого времени. Поэтому мы пойдём другим путём. И путь тоже простой (в буквальном смысле — в нём используются другие простые числа). Мы видели, что короткие периоды могут уложиться в полный период целое число раз, и это даёт нам псевдопростые числа. Но что нам мешает эти понедельники «взять и отменить»? Да в общем-то, ничего не мешает.
Для существования коротких периодов полный период () должен делиться на много делителей. Так для числа 25 полный период 24 делится на 2, 3, 4, 6, 8, 12. Вот столько есть возможностей для проникновения на охраняемую территорию псевдопростых чисел. Значит нам нужно просто взять в качестве полного периода простое число, ведь оно вообще ни на что не делится и потому автоматически спасает нас от вражеского элемента. Правда есть одно «но» — нам нужны простые числа, а они, как известно, нечётные (кроме одного исключения — 2), значит если простое, то само простым уже быть не может. Поэтому нам придётся допустить возможность для появления неполных периодов. Чем это плохо? Как мы видели выше — полный период гарантирует простоту числа, а вот про неполный — мы пока не доказали. Значит нам нужно доказать данный момент.
Доказательство довольно простое — для составного период, длиною, укладывающейся в два раза, полученный в системе счисления, не кратной делителям N, имеет всего два ряда остатков, и ни один из них не должен содержать чисел, кратных делителям числа («запрещённых» чисел). Если исключается хоть один элемент из одного ряда остатков, то и второй автоматически уменьшается на столько же (ведь один ряд равен другому, домноженному на любое число, отсутствующее в первом). Значит при наличии делителей у числа , мы получим меньшую суммарную длину двух периодов со всеми «разрешёнными» остатками, нежели значение полного периода и тем самым сдвинем единицу с места в конце полного периода, чем обеспечим отсутствие псевдопростоты. Но может ли количество кратных делителям остатков оказаться таким, чтобы дать ровно половину или одну треть полного периода? Ведь тогда мы получили бы, например, две трети «разрешённых» остатков (два ряда) и одну треть «запрещённых», а в сумме — полный период. Но для получения одной трети нам нужно обеспечить делимость полного периода () на 3, что мы можем довольно легко исключить — берём простое число, умножаем его на два и прибавляем к результату единицу — вуаля, перед нами гарантированно исключающее псевдопростоту число. У него количество кратных делителям остатков не может быть равным одной третьей полного периода, потому что на три полный период не делится. Остаётся вариант с половиной остатков, кратных делителям . Этот вариант устраняется чуть сложнее, поэтому ниже будет небольшое отступление.
Вычисление периода, или все числа — дети Мерсенна
Период любого числа можно вычислить. В простейшем случае вычисление производится простым делением уголком до тех пор, пока нам не встретится остаток, равный единице (в системе счисления, не кратной делителям ). Но для больших чисел это нереально долго. Поэтому есть необходимость в выводе формулы для вычисления периода. И такая формула существует, правда не в идеальном виде, когда на входе имеем число, а на выходе — период. Формула такая:
Здесь — исследуемое число, — основание используемой системы счисления (base), — максимальная степень , при которой результат возведения в степень меньше (по другому — индекс старшего разряда в в системе счисления ), — расстояние слева направо в ряду остатков от индекса (равного количеству разрядов в ) до единицы, — некоторый целый коэффициент.
Вывод формулы
По определению формулы понятно, что (1) , то есть первая степень , которая больше , позволяет вычесть и получить некую разницу в виде . Так же из определения следует, что (2) , здесь — целочисленный коэффициент. Если домножить (1) на и заменить на его значение из (2), которое равно , то получим .
Полезные свойства
Как мы видим, используя эту формулу можно получать числа с заранее известным периодом. Правда есть одна сложность — нужно подбирать коэффициент , что для больших чисел опять превращается в нечто недостижимое. Но формула даёт нам другое — мы наглядно видим, как формируются все числа. Да, абсолютно все положительные целые числа можно получить по этой формуле, ведь у всех чисел есть какой-то период. И что интересно — все числа получаются от деления числа Мерсенна на один из его делителей. Вспомним, что числом Мерсенна называется такое число, которое равно . В формуле же в числителе мы видим обобщение для чисел Мерсенна с любым основанием (включая 2, разумеется). И если нас интересуют простые числа, то другие основания, кроме 2, нам вообще не понадобятся.
Зная, что мы делим число Мерсенна, нам было бы полезно уметь вычислять период чисел именно для случая деления чисел Мерсенна (вспомним расширенное понятие периода здесь). Но что очень замечательно — формула для вычисления мерсенновского периода совпадает с формулой для вычисления периода типа . Ну а для вывода формулы деления чисел Мерсенна используется тот же принцип, что и при выводе формулы для деления , за исключением формулы для вычисления значения в ряде остатков с индексом , которая выглядит так — , а для двоичных чисел получаем .
Теперь у нас все карты на руках и мы можем начинать игру по вскрытию псевдопростых засланцев в рядах простых чисел.
Как мы знаем из предыдущих серий, при делимости нацело мерсенновский период числа должен укладываться в количество разрядов числа Мерсенна целое число раз. Поэтому в формуле выше решение в целых числах возможно лишь если знаменатель имеет период либо равный числителю, либо кратно меньший. Но кратно меньший период даст нам, в том числе, и делители самого числа , ведь если делит число Мерсенна, то значит и его делители тоже делят число Мерсенна. И это очень важный момент, ведь из него вытекает, что длины периодов делителей N делят длину периода нацело. То есть если мы возьмём такое, что его период равен периоду числителя, тогда все делители будут одновременно и делителями числа Мерсенна, а потому должны укладываться в период и , и числа Мерсенна целое число раз.
Снова про половину периода
Теперь вспомним, что мы остановились на поиске способа доказать, что мы можем исключить случай, когда половина длины ряда остатков числа кратна его делителям. Доказать мы это хотели для устранения псевдопростых чисел при выборе одного простого числа в качестве основы для конструирования периода следующего простого числа. Так вот теперь мы знаем, что если конструируемое число имеет делители, то их периоды всегда делят период конструируемого числа нацело. Тогда нам остаётся проверить — какие делители могут уложиться в ранее заданные ограничения на конструируемое число. А ограничения такие — его период делится лишь на и на . Значит и делителями могут быть лишь числа с периодами и . Период имеет число , более ни одно число такого периода не имеет. Период не укладывается целое количество раз в , ведь — простое, поэтому делимость на три исключается при таком периоде. Значит остаётся лишь одна возможность — делители имеют период . Но число не может быть меньше или равно своему периоду, поэтому минимальное значение для делителей такое — . Если перемножить два минимальных делителя, то получим — , такое значение должно быть не больше , ведь мы говорим о делителях . Тогда получаем неравенство . Это неравенство может быть отрицательным или равным нулю только при , поэтому для всех сконструированных таким способом чисел псевдопростота исключена, если полученное число больше . Ну а для меньших чисел мы можем проверить простоту даже в уме.
Теперь есть два варианта — новое число простое число, что нам и требовалось, либо это число составное и тогда его период не укладывается целое число раз в полный период, а потому такое число легко проверить на простоту, вычислив последнее значение в полном ряду остатков. То есть сконструированное число можно смело проверять стандартными вероятностными тестами простоты, и что важно, результат теста будет уже не вероятностный, а гарантированный. Вот так в итоге мы освободились от проклятия псевдопростоты, давлеющей над всеми вероятностными тестами.
Но конечно, жизнь была бы мёдом, если бы все проблемы решались так просто. Исключив псевдо-простоту, мы так и не исключили числа, которые ни от кого не прячутся и ни подо что не маскируются. А с ними есть ещё одна проблема — мы пока что умеем проверять произвольный член последовательности остатков лишь при помощи возведения в степень с последующим взятием остатка. Но такой метод очень медленный. Точнее, он достаточно быстр для чисел, используемых в криптографии, но вот для поиска больших простых он не пригоден в домашних условиях, ибо требует много лет вычислений обычного домашнего компьютера, что не даёт нам возможность получить 400k$ (как показано здесь).
Но тем не менее, для вычисления криптографических простых у нас уже почти всё готово. Хотя остаётся ещё наш старый друг — максимализм. Он спрашивает — раз можно использовать период , то что мешает использовать периоды и т.д.? И оказывается, что при должной осторожности — ничего не мешает!
Точно так же, как и в случае с периодом , для периода имеем возможность задать ограничение снизу, умножив простое число на 4 и прибавив 1. Тогда мы гарантируем себя от псевдопростых с периодами менее . Значит остаётся рассмотреть возможность появления псевдопростых с периодом, кратным 4, 3, 2 (1 для составных исключается, как показано выше). Из формулы для вычисления числа по периоду видно, что период делимого числа должен быть равен наименьшему общему кратному его делителей, из чего следует, что возможный период числа должен не только укладываться целое количество раз в (иначе не будет псевдопростоты), но и содержать в себе целое количество периодов делителей. Таким образом резко ограничивается количество возможных делителей псевдопростого числа. Поскольку период любого числа не может быть длиннее , то для возможного делителя , дающего в результате деления 3, период не может быть длиннее , кроме того учтём, что период числа 3 равен 2. должно быть нечётным, поскольку нечётным является . Из перечисленного следует, что чётный период N/3-1 является наименьшим общим кратным с периодом 2 числа 3, значит возможный период потнециально псевдопростого N должен быть равен периоду числа . Всего есть одно значение величины периода , для которого возможна псевдопростота, это . Для остальных значений либо слишком мало, либо его период (а вместе с ним и период ) уйдёт в запрещённую область ниже . Такая же история и с нечётными числами, меньше , но у них периоды не могут быть больше , а потому все они просто исключаются из рассмотрения. Теперь покажем, что не может иметь период , а значит не может делить полный период нацело. Сначала вспомним формулу , которая даёт нам количество кратных делителям остатков для числа, делящегося только на и . В нашем случае , как предполагается, может делиться на и на 3, все остальные делители исключены, поэтому получаем — . Теперь из полного периода нужно вычесть «запрещённые» значения, которых будет , а потом разделить на 4. Получаем возможный период произведения : , что меньше , то есть период длиной невозможен из-за необходимости учитывать «запрещённые» остатки, которая уводит все возможные псевдопростые периоды в запрещённую область (меньше ). Значит такая ситуация гарантирует нам, что сконструированное число не псевдопростое, а потому мы снова можем быть уверены в результатах последующего вероятностного теста простоты.
Аналогично, и с учётом делимости полного периода, можно получить такие же результаты и для других значений. Но если мы хотим так получать криптографические простые числа, то домножая на 2,4,6 мы будем очень долго наращивать размер числа, поэтому есть смысл посмотреть в сторону другого варианта — умножения двух простых чисел. Если мы умножим одно простое на другое, то получим нечётное число, поэтому обязательно нужно домножить ещё и на 2, чтобы получить чётный полный период и нечётное число-кандидат в простые. Полный период в таком случае будет делиться на , где и — простые числа. Теперь нам нужно доказать, что либо указанные периоды не дадут псевдопростоты, либо обнаружить признаки, предупреждающие нас о наличии псевдопростоты. Сразу заметим, что если период будет равен , то число будет простым (как показано выше). Так же выше показано, что число с половинным периодом не может быть псевдопростым, поэтому период длиной можно исключить. Хотя для полноты картины можно проверить период альтернативными способами. Так, если период равен , то учитывая, что и простые, становится понятно, что наименьшее общее кратное периодов делителей может быть равно только , при этом периоды делителей могут быть равны либо у обоих, либо у одного , а у другого . Поскольку период всегда меньше самого числа, то явно будет больше . Значит и псевдопростота при таком периоде невозможна. Поэтому остаётся проверить периоды . 2 меньше минимального возможного периода периода для всех чисел, больше 3, поэтому такой период исключаем. Теперь предположим, что сконструированное число делится на и , тогда при равенстве периода значению , их периоды тоже будут равны , потому что это единственное наименьшее общее кратное для двух чисел, ведь не делится ни на что. Отсюда вытекает, что , где — первый возможный сомножитель с периодом , и — второй. Далее: . Здесь может быть равен только единице, иначе слева не получится целое число. Тогда: , из чего следует, что если , то псевдопростых с периодом быть не может. Остаётся проверить псевдопростоту при , что можно сделать проверкой значения в ряду остатков на позиции , если там 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, то кандидат отклоняется. Далее каждый кандидат проверяется обычным вероятностным тестом, то есть вычисляется значение в ряду остатков на позиции , если это 1 или , то перед нами — гарантированно простое число.
При выполнении генерации следует учитывать, что на каждой итерации получается рост количества сгенерированных простых, то есть коэффициент размножения для 1000 начальных простых много больше единицы. Поэтому для получения криптографических простых необходимо ограничивать генерацию, иначе она займёт очень большое время, да и памяти может не хватить. При этом не стоит сокращать начальный набор простых, потому что генерация должна быть максимально случайной, чтобы, зная её алгоритм, злоумышленник не предсказал получаемые значения. Для этого необходимо реализовать механизм отсечения ветвей дерева генерации, позволяющий каждый раз получать уникальные простые, расположенные достаточно далеко от ранее сгенерированных. И конечно, отсечение лишнего существенно ускоряет процесс.
Время выполнения теста зависит от количества сгенерированных кандидатов. Каждый из кандидатов проходит вероятностный тест, что в наибольшей степени замедляет генерацию. В тестовых запусках для получения нескольких сотен простых в диапазоне — было потрачено от 5 до 20 минут. Такая разница времени объясняется различными путями, которые проходит алгоритм в дереве генерации. Так изначально генерация ограничивается примерно 10 числами, но с приближением криптографического размера генерация затухает из-за существенного сокращения коэффициента размножения с ростом величины числа. Поэтому необходимо повышать количество промежуточных простых, но на сколько конкретно — сказать сложно, а потому для ограничения используется простое повышение допустимого количества в два раза с ростом размера числа и превышением грубо задаваемого порога. В результате в рамках ограничений количество новых простых может плавать и тем самым вносить существенное различие в общее время генерации.
Далее приводится текст процедуры на Java, выполняющей генерацию простых чисел. Она, естественно, не удовлетворяет криптографическим требованиям, которые выходят далеко за рамки кода программы и в основном касаются организационных моментов. В чисто программной части в процедуре не реализован механизм отсечения ветвей дерева генерации, за исключением простейшего ограничения. Но тем не менее в качестве примера реализации алгоритма генерации программа может чем-то помочь.
Входными параметрами является начальный список простых и PrintWriter для сохранения результата в файл. После завершения алгоритма в файле будут все произведения сгенерированных простых с теми исходными числами, которые имеют модуль трёх равным единице. Результат можно проверить при помощи онлайн-сервисов, реализующих факторизацию чисел, но следует понимать, что они могут использовать вероятностную проверку на простоту перед выполнением факторизации, а потому для проверки предложенного подхода становятся бесполезными (ведь все числа и так проверены вероятностным тестом). Но ряд из них сразу берутся разлагать предложенное число на множители, такие сайты могут вселить некоторую дополнительную уверенность в правильности предложенного метода.
public static void generatePrimes(List<Integer> primes, PrintWriter pw)
{
List<BigInteger> mod3_1 = new ArrayList<BigInteger>();
List<BigInteger> l = new ArrayList<BigInteger>();
BigInteger three=BigInteger.valueOf(3), two=BigInteger.valueOf(2);
for (int k=0;k<primes.size()-1;k++)
{
BigInteger seed=BigInteger.valueOf(primes.get(k));
BigInteger s2=seed.shiftLeft(1);
BigInteger r0=seed.remainder(three);
if (r0.intValue()==1) mod3_1.add(seed);
for (int i=k+1;i<primes.size();i++)
{
BigInteger p=BigInteger.valueOf(primes.get(i));
BigInteger r=p.remainder(three);
if (r.intValue()==r0.intValue()) continue; // divisible by 3
else addIfPrime(p,seed,s2,two,l);
}
}
List<BigInteger> ps=l;
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);
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;
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)
{
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=two.modPow(fp.shiftRight(1),n);
if (r.compareTo(BigInteger.ONE)!=0) return;
l.add(n);
}
Ну а теперь, когда вы (ну почти) всё знаете о генерации простых чисел, возможно ваши интересы всё же выйдут за рамки одной только криптографии и вам станет интересно позаниматься теорией чисел, благо она, как было указано выше, доступна для учеников пятого класса, но при этом ещё и позволяет самостоятельно находить истинные бриллианты, не ожидая помощи от серьёзных математиков.
Комментарии (20)
ilammy
05.10.2019 08:45Современная криптография больше топит за эллиптические кривые, где большие простые числа не очень нужны, и порядок размера параметров поменьше (256 — 512 битов).
Для систем на основе факторизации NIST, например, считает 1024-битные числа уже слишком маленькими, а 2048-битные — приемлемыми максимум до 2030 года. Я где-то видел, что NSA требует минимум 3072-битные параметры уже сейчас.
math_user Автор
05.10.2019 12:04Современная криптография больше топит за эллиптические кривые
Решения там всё равно целочисленные, а это — теория чисел в полный рост. Простые же задают структуру в теории чисел.
toyban
05.10.2019 15:23Вы все же несколько лукавите, утверждая, будто вероятностные подходы такие плохие. У вероятностных тестов есть доказанная верхняя граница вероятности ложноположительного результата, которая зависит от количества проведенных тестов. Так, допустим, проведите тест 1000 раз, и вероятность ошибки будет настолько мала, что мы ошибемся от силы лишь один раз, даже если б проверяли миллиарды случайных чисел ежесекундно, начиная с момента Большого Взрыва.
Плюс вероятностные тесты очень простые и крайне шустрые. Не нужно ждать 20 минут, чтоб произвести какую-то сотню простых чисел длиной 1000 бит.
math_user Автор
05.10.2019 15:39Вы все же несколько лукавите, утверждая, будто вероятностные подходы такие плохие
Вероятностные подходы работоспособны, но их работоспособность зависит от ряда факторов, таких как грамотность использующего их человека, организационные моменты, очень важные в криптографии, а так же всё тот же очевидный факт — вероятность может оказаться не в пользу использующего такой подход.
Поэтому, если есть возможность в корне устранить саму причину возникновения проблемы (то есть избавиться от вероятности), то есть очевидный смысл воспользоваться предлагаемым подходом.
У вероятностных тестов есть доказанная верхняя граница вероятности ложноположительного результата, которая зависит от количества проведенных тестов. Так, допустим, проведите тест 1000 раз, и вероятность ошибки будет настолько мала, что мы ошибемся от силы лишь один раз, даже если б проверяли миллиарды случайных чисел ежесекундно, начиная с момента Большого Взрыва.
Не совсем так. Для больших чисел (порядка 1024) количество обязательных для гарантированной проверки оснований систем счисления, равно количеству простых, меньших, чем само число. Это количество столь велико, что не то что ежесекундно, но вообще за множество полных циклов от большого взрыва до очередного коллапса вселенной, вы не сможете перебрать все варианты.
Плюс вероятностные тесты очень простые и крайне шустрые. Не нужно ждать 20 минут, чтоб произвести какую-то сотню простых чисел длиной 1000 бит.
Как вы видели, я предложил работающий код, а значит немного представляю, сколько времени может потребоваться на различные задачи. Скажу конкретно — генерация 1024-битных чисел с вероятностной проверкой мною производилась, но без точного учёта времени (были другие цели), хотя знаю точно (ибо долго ждал) — несколько сотен чисел необходимого размера с использованием той же библиотеки, что и в коде для генерации перемножением, потребуют от вас ожидания как раз минут пять, а то и 10 (повторюсь — время оценивал субъективно, но оценка такая — долго).
Кроме того, время, пусть даже в 4 раза большее, но измеряемое минутами, и при этом гарантирующее отсутствие псевдопростых, есть просто ничтожная затрата по сравнению с потенциально устраняемым ущербом. Ну и вспомним, что ключи генерируют раз в год (иногда в два), значит потерять пусть полчаса раз в год ради устранения возможного ущерба в размере многих миллионов — ну разве это затраты?toyban
05.10.2019 19:25+1Ну, так я и не говорил о гарантированном доказательстве на простоту. Я говорил, что если мы смотрим на тест, который с вероятностью, допустим, не больше 2^(-1000) ошибается, то вполне здраво предположить, что он никогда не ошибется, кроме, может, единожды за все время существования Вселенной. Черт, да у него вероятность ошибки в РАЗЫ! меньше, чем если б кто-то, случайно генерируя биткоин адрес, создал бы себе номер чужого кошелька и забрал бы чужие накопления. И вот я ни разу не слышал, чтоб произошла такая коллизия номеров кошельков.
К тому же предоставленный Вами алгоритм обладает одним неприятным свойством: созданные случайные простые числа не совсем "случайны". Их можно легко воссоздать, если кто-то украдет список начальных простых чисел. Да, вероятностные тесты тоже потенциально страдают от этого недостатка из-за random seed, но мы всегда можем применить какой-нибудь источник случайного шума (оставим пока в стороне вопрос существования случайности в нашем мире).
math_user Автор
05.10.2019 22:50если мы смотрим на тест, который с вероятностью, допустим, не больше 2^(-1000) ошибается
А почему вы решили, что алгоритм ошибается с вероятностью 2^(-1000)? Но даже если принять вашу (некорректную) оценку, то любое доступное количество прогонов Миллера-Рабина никак не поможет против чисел Кармайкла, плюс нет гарантий от пропуска и других видов чисел. Поэтому нужно точнее оценивать вероятность ошибки, и при этом она будет на сотни порядков больше.
К тому же предоставленный Вами алгоритм обладает одним неприятным свойством: созданные случайные простые числа не совсем «случайны». Их можно легко воссоздать, если кто-то украдет список начальных простых чисел.
Ну это не проблема. Точнее эта проблема ни чуть не страшнее, чем проблема генерации простым перебором с проверкой, потому что выбор начала последовательности, которую мы проверяем, требует случайности того же порядка, что и генерируемое число, но если в наличии есть уже случайное число такого порядка, то ни что нам не мешает применить эту случайность к отсечению ветвей в дереве генерации, начиная от выбора начального набора простых, и далее по всем этапам. Если вы запустите предложенный код без ограничений, то увидите, что первое умножение сгенерированных чисел на единичные по модулю 3 простые из начального набора даёт нам десятки тысяч простых, которые, естественно, все так же могут участвовать в последующих этапах. То есть коэффициент размножения будет около 10, а количество этапов для 30-битных начальных значений — более 30. В целом одна тысяча простых грубо даст ~10^30 вариантов. Но простых в нашем распоряжении не тысяча, а очень большое количество. Так, простые числа до миллиарда уже дают нам 50 миллионов, а теперь прикиньте количество сочетаний из 50 миллионов по 1000, и умножьте потом на 10^30. И это всего лишь числа до миллиарда, но ни что не мешает нам взять и до триллиона (гененрировать их тем же решетом, например). Так что с этой стороны я не вижу проблем.toyban
06.10.2019 00:02Вероятность ложноположительного результата теста Миллера-Рабина не больше 4^(-k), где k — количество тестов. Возьмите k = 500, и получите вероятность ошибки 2^(-1000).
math_user Автор
06.10.2019 14:13Вероятность ошибки оценивается по единичному испытанию, а далее — по формулам теории вероятностей. Об этом в википедии написано достаточно подробно. Вы же приняли прямо пропорциональную зависимость, то есть игнорировали формулы из теории вероятностей. Попробуйте всё же выполнить расчёт правильно.
toyban
06.10.2019 18:40+1Простите, Вы хотите меня испугать теорией вероятности? Боюсь, у Вас это не выйдет: я довольно неплохо ею владею.
Дальше, это какую же формулу я игнорирую из ТВ? Тут, к счастью, расчет элементарный.
Назовем число свидетелем (того, что составное), если тест Ферма не выполняется для данных и , то есть данное свидетельствует, что — составное. Вероятность того, что случайно взятое будет свидетелем, когда n — составное, не меньше 3/4:
Теперь мы возьмем k разных случайных чисел и проведем тест Ферма для них (в чем и заключается алгоритм Миллера-Рабина). Так как все были взяты случайно и независимо друг от друга, то вероятность ложноположительного результата ВСЕГО теста это произведение вероятностей ложноположительного результатa на каждом , т.е.
Ради Вас все это писал. И где же тут ошибка? Какую ж такую ускользающую формулу ТВ я игнорирую?
math_user Автор
06.10.2019 20:24Пугать я вас, разумеется, не собирался (хотя услышать более обоснованное мнение хотел, признаюсь, ведь многие склонны бросить слово и потом «замять»).
Но мне казалось, что раз мы совершаем повторное испытание при условии, что предыдущее дало один конкретный вариант из двух, то и подходить к оценке вероятности повторения ранее случившегося события (на том же испытуемом объекте) нужно с позиций условной вероятности.
Рад, что вы подробно описали свой ход мыслей. Кроме того, почитал ещё раз википедию, там тоже поддерживают ваш подход. Ну и признаюсь — вероятности изучал давно, а потому не уверен и в своей правоте.
Но тем не менее, если всё же говорить о времени выполнения теста, то очевидно, что предлагаемые вами 500 возведений в степень по модулю представляют из себя существенно большую нагрузку на процессор, нежели одно возведение в степень по модулю (для больших чисел) в случае предложенного выше алгоритма. А потому предложенный алгоритм имеет очевидное преимущество перед альтернативой в виде 500 (да пусть даже 10) возведений в степень по модулю.
Вообще, вместе с Java идут и исходники, надо подробнее будет посмотреть, как они реализовали этот тест. Раньше мельком заглядывал и видел какие-то ограничения по количеству возведений в степень.
math_user Автор
07.10.2019 13:41Посмотрел код Java-библиотеки. Там стоит ряд ограничений, в зависимости от размера проверяемого числа. Для размера 1024 и более стоит насильственное урезание количества повторов до 2. То есть либо 1 проверка, либо две.
Почему это сделано? Очевидно, что ради скорости. При этом, если вспомним, что вероятность выбрать систему счисления, ложно свидетельствующую о простоте числа, составляет 1/4. Если принимаем повторные попытки за независимые испытания, то в серии из 2-х испытаний получаем вероятность ложного ответа, равную 1/16. И это против гарантированного результата, хоть и достигаемого за несколько большее время (видимо от 4-5 раз больше). На мой взгляд выбор в данной ситуации абсолютно очевиден и однозначен. Ну а установка такого явного ограничения на количество повторов внутри библиотеки, да ещё и без указания на это в документации, это просто кричащее предупреждение — срочно меняйте подход при генерации простых для криптографии!
vlanko
05.10.2019 21:41Проверил — Джава тормозит.
Для вероятности ошибки порядка е-6 скорость генерации порядка 310 чисел в минуту на 3,9 ГГц для 1023 бит.math_user Автор
05.10.2019 22:51Это ещё быстро. Возможно, стоит повысить коэффициент на входе метода проверки, потому что от него зависит количество прогонов, ну а от количества — время.
NeverWalkAloner
05.10.2019 18:06Забавно, как раз недавно писал статью на эту тему. Чтобы исключить вероятность ошибки, сегодня используют комбинацию вероятностных тестов на простоту. Такой подход называется тест Baillie–PSW и пока не найдено ни одного составного числа проходящего этот тест.
math_user Автор
05.10.2019 19:06пока не найдено ни одного составного числа проходящего этот тест
Тест систематически прогонялся на числах до 2^64, а что там дальше — никто не знает.
Кроме того, в той же библиотеке Java, которая использовалась при написании генератора, реализованы лишь два теста — Миллера-Рабина и Люка-Лемера. То есть даже если предположить, что Baillie–PSW идеален, то как мы видим на примере Java — библиотеки далеко не идеальны. А выбирают их такие же неидеальные пользователи. В сумме вероятность проблемы получается отнюдь не нулевая. Ну и сами можете проверить — в Java переберите с миллион нечётных чисел с тестированием их стандартной библиотекой с небольшим коэффициентом certainity — получите сотни промахов.
Поэтому проблема есть, а раз она существует, то и решать её явно стоит, тем более, что решение по стоимости — копеечное.
vlanko
Хе, в джаве для >=1024 низкая точность.
Вероятность ошибки 62,5 из 1000. Но ничего не мешает прогнать тест нужное количество раз.
math_user Автор
Да, все проблемы решаемы. Но всё же есть путь для более радикального решения.