<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Prime Numbers on kenji.blog</title><link>http://kenji.blog/ru/tags/prime-numbers/</link><description>Recent content in Prime Numbers on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ru</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 16:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ru/tags/prime-numbers/index.xml" rel="self" type="application/rss+xml"/><item><title>Гипотеза Римана и распределение простых чисел: глубокая связь с современной криптографией</title><link>http://kenji.blog/ru/p/riemann-hypothesis-prime-distribution-cryptography/</link><pubDate>Fri, 11 Sep 2026 16:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/riemann-hypothesis-prime-distribution-cryptography/</guid><description>&lt;img src="http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/img/eyecatch.jpg" alt="Featured image of post Гипотеза Римана и распределение простых чисел: глубокая связь с современной криптографией" />&lt;h1 id="1-введение-тайна-простых-чисел-во-вселенной-и-гипотеза-римана">1. Введение: Тайна простых чисел во Вселенной и гипотеза Римана
&lt;/h1>&lt;p>«Простые числа» (Prime Numbers) — это натуральные числа, которые делятся только на 1 и на сами себя. В мире математики их часто называют «атомами». Эта последовательность, 2, 3, 5, 7, 11, 13&amp;hellip;, на первый взгляд кажется хаотичной и случайной. С тех пор как древнегреческий математик Евклид доказал, что «простых чисел бесконечно много», бесчисленное множество математиков пытались разгадать закономерности, скрывающиеся в этом ряду.&lt;/p>
&lt;p>Ближе всего к разгадке тайны простых чисел подошла &lt;strong>«Гипотеза Римана» (Riemann Hypothesis)&lt;/strong>, предложенная немецким математиком Бернхардом Риманом в 1859 году. Гипотеза Римана является одной из самых важных и нерешенных проблем в современной математике. Она также включена в список Задач тысячелетия Института Клэя, за решение которой назначена награда в 1 миллион долларов.&lt;/p>
&lt;p>На первый взгляд может показаться, что сложная проблема чистой математики, касающаяся распределения простых чисел, не имеет ничего общего с нашей повседневной жизнью. Однако безопасность инфраструктуры современного общества, в особенности интернета, опирается на &lt;strong>современные криптографические технологии, такие как шифрование RSA и криптография на эллиптических кривых (ECC)&lt;/strong>, которые глубоко зависят от свойств огромных простых чисел.&lt;/p>
&lt;p>В этой статье мы совершим математическое путешествие: от распределения простых чисел к теореме о распределении простых чисел, дзета-функции Римана и, наконец, к сути гипотезы Римана. Мы чрезвычайно подробно и глубоко рассмотрим, как всё это связано с современными криптографическими технологиями, и что произойдет с миром, если гипотеза Римана будет доказана.&lt;/p>
&lt;hr>
&lt;h1 id="2-теорема-о-распределении-простых-чисел-открытие-гаусса">2. Теорема о распределении простых чисел: Открытие Гаусса
&lt;/h1>&lt;p>Чтобы понять, как распределены простые числа, математики придумали &lt;strong>функцию распределения простых чисел (Prime-counting function)&lt;/strong> $\pi(x)$, которая показывает «сколько простых чисел существует до заданного числа $x$».&lt;/p>
&lt;p>Например:&lt;/p>
&lt;ul>
&lt;li>$\pi(10) = 4$ (2, 3, 5, 7)&lt;/li>
&lt;li>$\pi(100) = 25$&lt;/li>
&lt;li>$\pi(1000) = 168$&lt;/li>
&lt;/ul>
&lt;p>15-летний гениальный математик Карл Фридрих Гаусс, вычислив огромные таблицы простых чисел, обнаружил, что частота появления простых чисел убывает обратно пропорционально натуральному логарифму $\ln x$. Иными словами, он предположил, что вероятность найти простое число в окрестности числа $x$ составляет примерно $\frac{1}{\ln x}$.&lt;/p>
&lt;p>Выражение этой идеи с помощью интеграла называется &lt;strong>интегральным логарифмом (Logarithmic integral)&lt;/strong> $\text{Li}(x)$:&lt;/p>
$$ \text{Li}(x) = \int_{2}^{x} \frac{dt}{\ln t} $$
&lt;p>Предположение Гаусса было независимо доказано в 1896 году Жаком Адамаром и Шарлем Жан де ла Валле-Пуссеном, и утвердилось как &lt;strong>Теорема о распределении простых чисел (Prime Number Theorem, PNT)&lt;/strong>.&lt;/p>
$$ \lim_{x \to \infty} \frac{\pi(x)}{\text{Li}(x)} = 1 $$
&lt;p>Или же это можно приблизительно выразить следующим образом:&lt;/p>
$$ \pi(x) \sim \frac{x}{\ln x} $$
&lt;p>Эта теорема показала, что на макроскопическом уровне простые числа распределены очень плавно и предсказуемо. Однако на микроскопическом уровне между $\pi(x)$ и $\text{Li}(x)$ всегда существует «погрешность», то есть «флуктуация». Именно природа этой флуктуации является величайшей загадкой, которую пытается разгадать гипотеза Римана.&lt;/p>
&lt;hr>
&lt;h1 id="3-дзета-функция-римана-и-произведение-эйлера">3. Дзета-функция Римана и произведение Эйлера
&lt;/h1>&lt;p>Самым мощным оружием в анализе распределения простых чисел является &lt;strong>дзета-функция Римана (Riemann Zeta Function)&lt;/strong>. Изначально это был бесконечный ряд, определенный Леонардом Эйлером для вещественных чисел $s > 1$.&lt;/p>
$$ \zeta(s) = \sum_{n=1}^\infty \frac{1}{n^s} = 1 + \frac{1}{2^s} + \frac{1}{3^s} + \frac{1}{4^s} + \dots $$
&lt;p>Одно из величайших достижений Эйлера заключалось в том, что он доказал, что этот бесконечный ряд можно представить как бесконечное произведение по всем простым числам $p$. Это и есть &lt;strong>Тождество Эйлера (Euler Product Formula)&lt;/strong>.&lt;/p>
$$ \zeta(s) = \prod_{p \text{ prime}} \frac{1}{1 - p^{-s}} = \left( \frac{1}{1 - 2^{-s}} \right) \left( \frac{1}{1 - 3^{-s}} \right) \left( \frac{1}{1 - 5^{-s}} \right) \dots $$
&lt;p>Интуитивное понимание этого доказательства заключается в том, что если мы разложим каждый член правой части как геометрическую прогрессию и перемножим их, то, согласно основной теореме арифметики (каждое натуральное число единственным образом представляется в виде произведения простых чисел), мы полностью восстановим сумму обратных величин натуральных чисел в левой части.&lt;/p>
&lt;p>&lt;strong>Эта единственная формула стала мостом, соединившим математический анализ (бесконечные ряды и непрерывные функции) и теорию чисел (простые числа и дискретные числа).&lt;/strong> Изучение дзета-функции равносильно изучению распределения простых чисел.&lt;/p>
&lt;hr>
&lt;h1 id="4-аналитическое-продолжение-и-расширение-на-комплексную-плоскость">4. Аналитическое продолжение и расширение на комплексную плоскость
&lt;/h1>&lt;p>Гениальность Римана заключалась в том, что он расширил переменную $s$ в функции $\zeta(s)$, которую Эйлер рассматривал только для вещественных чисел, до &lt;strong>комплексного числа $s = \sigma + it$ (где $\sigma$ — действительная часть, а $t$ — мнимая часть)&lt;/strong>.&lt;/p>
&lt;p>Исходный бесконечный ряд сходится только при $\sigma > 1$, но Риман, используя метод «аналитического продолжения» (Analytic Continuation), расширил определение функции так, чтобы $\zeta(s)$ имела смысл на всей комплексной плоскости, за исключением полюса в точке $s = 1$.&lt;/p>
&lt;p>Далее он вывел красивое функциональное уравнение (Functional equation), которому удовлетворяет дзета-функция.&lt;/p>
$$ \zeta(s) = 2^s \pi^{s-1} \sin\left(\frac{\pi s}{2}\right) \Gamma(1-s) \zeta(1-s) $$
&lt;p>Здесь $\Gamma(x)$ — гамма-функция. Это уравнение позволяет узнать свойства функции в левой полуплоскости на основе её свойств в правой полуплоскости.&lt;/p>
&lt;h3 id="нули-zeros-of-the-zeta-function">Нули (Zeros of the Zeta Function)
&lt;/h3>&lt;p>Комплексные числа $s$, при которых значение дзета-функции становится равным 0, называются «нулями».
Из функционального уравнения следует, что когда $s$ — отрицательное четное число ($-2, -4, -6, \dots$), значение $\sin(\pi s / 2)$ равно 0, поэтому $\zeta(s) = 0$. Они называются &lt;strong>тривиальными нулями (Trivial zeros)&lt;/strong>.&lt;/p>
&lt;p>Однако для распределения простых чисел важны другие нули — те, что находятся в «критической полосе» (Critical strip) $0 \le \sigma \le 1$, так называемые &lt;strong>нетривиальные нули (Non-trivial zeros)&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h1 id="5-суть-гипотезы-римана-и-явная-формула">5. Суть гипотезы Римана и явная формула
&lt;/h1>&lt;p>Риман вычислил несколько таких нулей и выдвинул одно поразительное предположение. Это и есть &lt;strong>гипотеза Римана&lt;/strong>.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Гипотеза Римана (Riemann Hypothesis)&lt;/strong>
Все нетривиальные нули дзета-функции Римана $\zeta(s)$ лежат на прямой, действительная часть которой равна $1/2$ ($\text{Re}(s) = 1/2$).&lt;/p>
&lt;/blockquote>
&lt;p>Эта прямая с действительной частью 1/2 называется «критической прямой» (Critical line).&lt;/p>
&lt;div class="mermaid">graph TD
A["Дзета-функция Римана ζ(s)"] --> B["Расширение на комплексную плоскость аналитическим продолжением"]
B --> C["Тривиальные нули (s = -2, -4, -6 ...)"]
B --> D["Нетривиальные нули (0 &lt;= Re(s) &lt;= 1)"]
D --> E["Гипотеза Римана"]
E --> F["Все нетривиальные нули лежат на Re(s) = 1/2"]
F --> G["К доказательству предела погрешности распределения простых чисел"]&lt;/div>
&lt;p>Почему гипотеза Римана так важна? Потому что нули дзета-функции &lt;strong>полностью&lt;/strong> определяют распределение простых чисел.&lt;/p>
&lt;p>Риман и более поздний математик фон Мангольдт вывели «явную формулу» (Explicit formula), которая точно описывает распределение простых чисел. Используя функцию Чебышёва $\psi(x)$, это можно выразить следующим образом:&lt;/p>
$$ \psi(x) = x - \sum_{\rho} \frac{x^\rho}{\rho} - \ln(2\pi) - \frac{1}{2}\ln(1 - x^{-2}) $$
&lt;p>Здесь сумма берется по всем нетривиальным нулям $\rho$ дзета-функции.
Главным членом является $x$ (что соответствует теореме о распределении простых чисел), а путем прибавления и вычитания волнообразных членов, зависящих от нулей $\rho$, восстанавливается точное ступенчатое распределение простых чисел. Можно сказать, что нетривиальные нули представляют собой «частоты (волны)» распределения простых чисел.&lt;/p>
&lt;p>Если гипотеза Римана верна, и действительная часть всех нетривиальных нулей $\rho$ в точности равна $1/2$, то член погрешности в теореме о распределении простых чисел будет укладываться в минимально возможный теоретический диапазон.&lt;/p>
$$ |\pi(x) - \text{Li}(x)| \le \frac{1}{8\pi} \sqrt{x} \ln x \quad \text{for} \quad x \ge 2657 $$
&lt;p>Другими словами, &lt;strong>если гипотеза Римана истинна, это докажет, что простые числа распределены настолько «правильно и красиво», насколько мы только можем себе представить&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h1 id="6-неразрывная-связь-современных-криптографических-технологий-и-простых-чисел">6. Неразрывная связь современных криптографических технологий и простых чисел
&lt;/h1>&lt;p>До этого момента мы находились в мире глубокой чистой математики, но именно эти свойства простых чисел фундаментально поддерживают современное цифровое общество. Ярким примером является криптография с открытым ключом, такая как &lt;strong>шифрование RSA&lt;/strong>.&lt;/p>
&lt;p>Безопасность любых коммуникаций, будь то оплата кредитной картой в интернете, передача паролей или электронные подписи в блокчейне, зависит от «простых чисел».&lt;/p>
&lt;h3 id="как-работает-шифрование-rsa">Как работает шифрование RSA
&lt;/h3>&lt;p>Безопасность RSA основана на математическом факте (задаче факторизации), что «очень сложно разложить большое составное число на простые множители».&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Генерация ключей&lt;/strong>:
Случайным образом выбираются два огромных простых числа $p$ и $q$ (например, по 2048 бит каждое).
Они перемножаются для получения $N = p \times q$. Это $N$ становится частью открытого ключа.
Используя функцию Эйлера $\phi(N) = (p-1)(q-1)$, генерируется закрытый ключ $d$.&lt;/p>
$$ e \times d \equiv 1 \pmod{\phi(N)} $$
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Шифрование и расшифрование&lt;/strong>:
Открытый текст $M$ преобразуется в зашифрованный текст $C$ с использованием открытого ключа $e, N$.
&lt;/p>
$$ C \equiv M^e \pmod{N} $$
&lt;p>
Расшифровать текст может только тот, кто обладает закрытым ключом $d$.
&lt;/p>
$$ M \equiv C^d \pmod{N} $$
&lt;/li>
&lt;/ol>
&lt;div class="mermaid">graph LR
A["Открытый текст (Plaintext)"] --> B["Шифрование открытым ключом (e, N)"]
B --> C["Зашифрованный текст (Ciphertext)"]
C --> D["Расшифрование закрытым ключом (d)"]
D --> E["Исходный открытый текст"]
F["Злоумышленник (Attacker)"] -- "Попытка факторизации N" --> C
F -.-> G["Без знания p и q вычислить d невозможно"]&lt;/div>
&lt;p>Чтобы взломать шифр RSA, нужно найти (факторизовать) исходные простые числа $p$ и $q$ из гигантского числа $N$. Считается, что даже с использованием самых передовых на сегодня алгоритмов (таких как общий метод решета числового поля, GNFS) и суперкомпьютеров, разложение числа из сотен цифр займет время, намного превышающее возраст Вселенной.&lt;/p>
&lt;hr>
&lt;h1 id="7-влияние-гипотезы-римана-на-криптографические-технологии">7. Влияние гипотезы Римана на криптографические технологии
&lt;/h1>&lt;p>Так как же пересекаются «гипотеза Римана», находящаяся на вершине чистой математики, и «криптография»?&lt;/p>
&lt;h3 id="71-алгоритмы-генерации-простых-чисел-проверка-на-простоту-и-обобщенная-гипотеза-римана-grh">7.1. Алгоритмы генерации простых чисел (проверка на простоту) и Обобщенная гипотеза Римана (GRH)
&lt;/h3>&lt;p>Для работы шифрования RSA необходимо сначала сгенерировать огромные простые числа $p$ и $q$. Однако надежно и быстро определить, «является ли число простым», не так-то просто.&lt;/p>
&lt;p>В настоящее время на практике применяется вероятностный алгоритм — &lt;strong>тест простоты Миллера-Рабина (Miller-Rabin primality test)&lt;/strong>. Этот алгоритм быстр, но существует крайне низкая вероятность того, что составное число будет ошибочно признано простым (так называемые «псевдопростые числа»).&lt;/p>
&lt;p>Однако, если предположить, что &lt;strong>«Обобщенная гипотеза Римана» (Generalized Riemann Hypothesis, GRH)&lt;/strong>, которая распространяет гипотезу Римана на L-функции Дирихле, верна, то ситуация кардинально меняется.
Если GRH истинна, математически гарантируется верхняя граница количества проверок в тесте Миллера-Рабина, и вероятностный алгоритм &lt;strong>превращается в «детерминированный алгоритм полиномиального времени»&lt;/strong> (этот важный факт был известен еще до открытия теста простоты AKS).&lt;/p>
&lt;p>Таким образом, гипотеза Римана (и ее обобщение) играет роль непосредственного гаранта того, «можно ли с абсолютной уверенностью и высокой скоростью генерировать гигантские простые числа», что является фундаментом для создания шифров.&lt;/p>
&lt;h3 id="72-связь-с-алгоритмами-факторизации">7.2. Связь с алгоритмами факторизации
&lt;/h3>&lt;p>Знания о распределении простых чисел также необходимы при оценке вычислительной сложности алгоритмов взлома шифров (таких как общий метод решета числового поля). Многие алгоритмы факторизации опираются на распределение «гладких чисел» (Smooth numbers — чисел, имеющих только небольшие простые множители).&lt;/p>
&lt;p>Чтобы точно оценить частоту появления гладких чисел, необходимо глубокое понимание распределения простых чисел. Здесь также используются методы аналитической теории чисел, напрямую связанные с дзета-функцией и гипотезой Римана. Если гипотеза Римана будет доказана, и погрешность распределения простых чисел будет полностью определена, станет возможным более точно установить пределы эффективности алгоритмов факторизации.&lt;/p>
&lt;hr>
&lt;h1 id="8-если-гипотеза-римана-будет-доказана-будут-ли-взломаны-шифры">8. Если гипотеза Римана будет доказана, будут ли взломаны шифры?
&lt;/h1>&lt;p>В качестве городской легенды иногда можно услышать, что «если гипотеза Римана будет решена, шифрование RSA рухнет в одно мгновение», но &lt;strong>математически это неверно&lt;/strong>.&lt;/p>
&lt;p>Само доказательство гипотезы Римана не приведет к мгновенному появлению магического алгоритма, который радикально ускорит факторизацию. Гипотеза Римана — это теорема о макроскопической «закономерности распределения» простых чисел, она не сообщает напрямую о том, на какие именно простые числа делится конкретное число $N$ (локальные свойства).&lt;/p>
&lt;p>Однако влияние не будет нулевым.
В процессе доказательства гипотезы Римана с огромной вероятностью &lt;strong>могут быть открыты «новые математические инструменты» и «неизвестные аналитические методы»&lt;/strong>. Как показывает история, доказательство Великой теоремы Ферма или гипотезы Пуанкаре породило новые теории, которые привели к колоссальному скачку в математике в целом.&lt;/p>
&lt;p>Если будут разработаны неизвестные ранее методы алгебраической геометрии или некоммутативной геометрии, позволяющие полностью манипулировать свойствами нулей дзета-функции Римана, нельзя исключать, что это в конечном итоге приведет к открытию революционного алгоритма факторизации (например, классического алгоритма, который снизит время вычислений до полиномиального). В этом смысле криптографы никогда не смогут отвести взгляд от того, что происходит вокруг гипотезы Римана.&lt;/p>
&lt;h3 id="квантовые-компьютеры-и-алгоритм-шора">Квантовые компьютеры и алгоритм Шора
&lt;/h3>&lt;p>Куда более прямой и реальной угрозой для криптографических технологий является не доказательство гипотезы Римана, а &lt;strong>квантовые компьютеры&lt;/strong>. Опубликованный Питером Шором (Peter Shor) в 1994 году «алгоритм Шора» доказал, что при наличии достаточно мощного квантового компьютера факторизация может быть решена за полиномиальное время. Это фундаментально разрушит шифрование RSA и криптографию на эллиптических кривых.&lt;/p>
&lt;p>В настоящее время по всему миру идет переход на &lt;strong>постквантовую криптографию (Post-Quantum Cryptography, PQC)&lt;/strong> (например, криптографию на решетках), которую не смогут взломать даже квантовые компьютеры. Криптографические технологии, основанные на простых числах, возможно, подходят к концу своего золотого века, но математическая ценность самих простых чисел не будет утрачена никогда.&lt;/p>
&lt;hr>
&lt;h1 id="9-заключение-перекресток-математической-абстракции-и-реального-мира">9. Заключение: Перекресток математической абстракции и реального мира
&lt;/h1>&lt;div class="mermaid">graph TD
A["Исследования в чистой математике"] --> B["Разгадка гипотезы Римана"]
B --> C["Полное понимание распределения простых чисел"]
C --> D["Стремительное развитие теории чисел и алгебраической геометрии"]
D -.-> E["Вероятность появления новых алгоритмов факторизации"]
E -.-> F["Обновление оценки безопасности криптографических технологий"]
A --> G["Прикладная математика и информатика"]
G --> H["Оптимизация проверки на простоту и генерации шифров"]
H --> F&lt;/div>
&lt;p>Неустанные поиски простых чисел, ведущиеся со времен Древней Греции, благодаря гению Римана превратились в прекрасную симфонию на комплексной плоскости (нули дзета-функции). И поразительно, что этот кристалл чистейшей математики спустя столетия применяется как самый мощный щит, гарантирующий безопасность интернет-общества.&lt;/p>
&lt;p>Гипотеза Римана символизирует одновременно «абстрактную красоту» математики и её «удивительную применимость в физическом мире и реальном обществе».&lt;/p>
&lt;p>Когда эта гигантская математическая гора, вершины которой еще никто не достиг, будет однажды покорена, мы не только полностью поймем истину Вселенной, скрытую в простых числах, но и обретем новый взгляд на фундамент нашего информационного общества. Изучение криптографии — это, по сути, путешествие по страницам истории человеческой мудрости.&lt;/p></description></item><item><title>Быстрый алгоритм проверки на простоту, реализованный на C++ (метод Миллера-Рабина и др.)</title><link>http://kenji.blog/ru/p/cpp-fast-prime-testing-miller-rabin/</link><pubDate>Fri, 11 Sep 2026 14:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/cpp-fast-prime-testing-miller-rabin/</guid><description>&lt;img src="http://kenji.blog/p/cpp-fast-prime-testing-miller-rabin/img/eyecatch.jpg" alt="Featured image of post Быстрый алгоритм проверки на простоту, реализованный на C++ (метод Миллера-Рабина и др.)" />&lt;h1 id="введение-почему-нужна-быстрая-проверка-на-простоту">Введение: Почему нужна быстрая проверка на простоту
&lt;/h1>&lt;p>В мире информатики, теории криптографии или спортивного программирования, определение того, «является ли число простым», — это очень важная и фундаментальная задача. Например, криптография с открытым ключом, такая как алгоритм RSA, обеспечивающая безопасность современного интернета, основана на генерации огромных простых чисел и сложности их умножения (факторизации). Поэтому способность мгновенно определять, является ли огромное число простым, можно смело назвать технологией, лежащей в основе цифрового общества.&lt;/p>
&lt;p>Кроме того, в спортивном программировании (AtCoder, Codeforces и др.) проверка на простоту — часто встречающаяся тема. Для огромных входных данных с ограничениями вида $N \le 10^{18}$, когда требуется десятки тысяч раз проверить числа на простоту за 1 секунду, традиционные наивные алгоритмы не уложатся в лимит времени (Time Limit Exceeded: TLE).&lt;/p>
&lt;p>В этой статье мы подробно разберем всё от математических основ до высокооптимизированной реализации на C++: начиная с наивного алгоритма, вероятностного метода проверки простоты «тест Ферма» и заканчивая практичным сверхбыстрым алгоритмом «Тест Миллера-Рабина (Miller-Rabin)», который преодолевает его слабые стороны. В частности, для 64-битных целых чисел ($N &lt; 2^{64}$) мы подробно объясним метод «100% достоверной проверки (детерминированный алгоритм)», выходящий за рамки вероятностной оценки, и предоставим готовый к использованию исходный код на C++.&lt;/p>
&lt;hr>
&lt;h1 id="1-основы-проверки-на-простоту-и-метод-пробных-делений-trial-division">1. Основы проверки на простоту и метод пробных делений (Trial Division)
&lt;/h1>&lt;p>Простое число (Prime number) — это натуральное число больше 1, которое не имеет положительных делителей, кроме 1 и самого себя. Строго следуя определению, чтобы проверить, является ли целое число $N$ простым, можно попробовать разделить $N$ на все целые числа от $2$ до $N-1$. Если оно ни разу не разделится без остатка, то это простое число, а если разделится хотя бы раз — составное число (не простое).&lt;/p>
&lt;p>Однако вычислительная сложность этого метода составляет $O(N)$, и если $N$ — огромное число, например $10^{18}$, то даже на современном компьютере вычисления займут колоссальное количество времени.&lt;/p>
&lt;h2 id="оптимизация-метода-пробных-делений-поиск-до-sqrtn">Оптимизация метода пробных делений: поиск до $\sqrt{N}$
&lt;/h2>&lt;p>Если составное число $N$ можно выразить как $a \times b = N$ (где $a \le b$), то обязательно $a \le \sqrt{N}$. Следовательно, цикл проверки не нужно выполнять до $N-1$, достаточно проверить до $\sqrt{N}$.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Проверка на простоту методом пробных делений (O(sqrt(N)))
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="nf">is_prime_trial_division&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Проверяем только нечетные числа, начиная с 3
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Вычислительная сложность этого алгоритма — $O(\sqrt{N})$. Для $N \le 10^{12}$ вычисления выполняются мгновенно, но при $N \approx 10^{18}$ количество итераций цикла составит около $10^9$, что даже в C++ потребует от сотен миллисекунд до нескольких секунд. Поэтому этот метод не подходит для многократных проверок.&lt;/p>
&lt;hr>
&lt;h1 id="2-тест-ферма-начало-вероятностной-проверки-на-простоту">2. Тест Ферма: Начало вероятностной проверки на простоту
&lt;/h1>&lt;p>Чтобы преодолеть ограничения метода пробных делений, были придуманы «вероятностные алгоритмы» (Probabilistic Algorithm), использующие теоремы теории чисел. Ярким примером является «Тест Ферма на простоту» (Fermat Primality Test), основанный на малой теореме Ферма.&lt;/p>
&lt;h2 id="малая-теорема-ферма-fermats-little-theorem">Малая теорема Ферма (Fermat&amp;rsquo;s Little Theorem)
&lt;/h2>&lt;p>Эта теорема, открытая Пьером де Ферма (Pierre de Fermat), утверждает следующее:&lt;/p>
&lt;blockquote>
&lt;p>Для любого простого числа $p$ и любого целого числа $a$, взаимно простого с $p$ (не кратного $p$), выполняется следующее сравнение:
&lt;/p>
$$ a^{p-1} \equiv 1 \pmod p $$
&lt;/blockquote>
&lt;p>Взяв контрапозицию этой теоремы, можно сказать: «Если для некоторого целого числа $N$ и взаимно простого с ним целого числа $a$ выполняется $a^{N-1} \not\equiv 1 \pmod N$, то $N$ гарантированно является составным». Тест Ферма использует это свойство: для проверяемого числа $N$ выбирается случайное основание (base) $a$, вычисляется $a^{N-1} \pmod N$ и проверяется, равно ли оно $1$.&lt;/p>
&lt;h2 id="быстрое-возведение-в-степень-по-модулю-метод-бинарного-возведения-в-степень">Быстрое возведение в степень по модулю (Метод бинарного возведения в степень)
&lt;/h2>&lt;p>Для выполнения теста Ферма необходимо быстро вычислить огромную степень $a^{N-1} \pmod N$. Для этого используется «метод бинарного возведения в степень» (Modular Exponentiation / Binary Exponentiation). Его вычислительная сложность составляет $O(\log N)$, что делает его очень быстрым.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Вычисление a^b mod m с помощью бинарного возведения в степень
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="nf">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128_t&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">res&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128_t&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">b&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>Здесь для предотвращения переполнения используется расширение GCC/Clang &lt;code>__int128_t&lt;/code> (128-битное целое число) для хранения промежуточных произведений.&lt;/li>
&lt;/ul>
&lt;h2 id="псевдопростые-числа-и-числа-кармайкла-carmichael-numbers">Псевдопростые числа и числа Кармайкла (Carmichael Numbers)
&lt;/h2>&lt;p>Тест Ферма очень мощный, но у него есть фатальный недостаток. Существуют такие составные числа $N$, для которых выполняется $a^{N-1} \equiv 1 \pmod N$ при всех $a$, взаимно простых с $N$.&lt;/p>
&lt;p>Такие числа называются «абсолютно псевдопростыми» или «числами Кармайкла». Наименьшее число Кармайкла — $561 = 3 \times 11 \times 17$.
Из-за существования чисел Кармайкла с помощью только теста Ферма невозможно провести детерминированную проверку со «100% вероятностью». Сколько бы различных $a$ мы ни пробовали, такие числа, как $561$, всегда будут притворяться (обманывать) простыми.&lt;/p>
&lt;hr>
&lt;h1 id="3-тест-простоты-миллера-рабина-miller-rabin-primality-test">3. Тест простоты Миллера-Рабина (Miller-Rabin Primality Test)
&lt;/h1>&lt;p>Алгоритм, блестяще преодолевший недостаток теста Ферма (существование чисел Кармайкла), — это «тест простоты Миллера-Рабина», предложенный Миллером (Gary L. Miller) и Рабином (Michael O. Rabin).
В настоящее время он наиболее широко используется как практичный и быстрый алгоритм проверки на простоту во встроенных библиотеках различных языков программирования и при генерации ключей криптографических систем.&lt;/p>
&lt;h2 id="математический-принцип">Математический принцип
&lt;/h2>&lt;p>Алгоритм Миллера-Рабина, помимо малой теоремы Ферма, использует следующее свойство: «В поле вычетов по простому модулю ($\mathbb{Z}/p\mathbb{Z}$) решениями уравнения $x^2 \equiv 1 \pmod p$ могут быть только $x \equiv 1$ или $x \equiv -1$» (в случае составного модуля могут существовать и другие, нетривиальные квадратные корни).&lt;/p>
&lt;p>Если из проверяемого нечетного числа $N$ вычесть $1$, то $N-1$ обязательно станет четным. Поэтому мы делим $N-1$ на $2$ столько раз, сколько возможно, и представляем в следующем виде:
&lt;/p>
$$ N-1 = d \cdot 2^s $$
&lt;p>
(где $d$ — нечетное, $s \ge 1$)&lt;/p>
&lt;p>Для любого основания $a$ ($1 &lt; a &lt; N-1$) мы проверяем, выполняется ли $a^{N-1} \equiv 1 \pmod N$ согласно малой теореме Ферма, но вычисляем это поэтапно.
Конкретно, мы последовательно возводим в квадрат: $a^d, a^{d \cdot 2}, a^{d \cdot 4}, \ldots, a^{d \cdot 2^s}$.&lt;/p>
&lt;p>Условием для того, чтобы тест Миллера-Рабина определил $N$ как «простое (или с высокой вероятностью простое)», является выполнение &lt;strong>любого&lt;/strong> из следующих утверждений:&lt;/p>
&lt;ol>
&lt;li>$a^d \equiv 1 \pmod N$&lt;/li>
&lt;li>Существует некоторое $r$ ($0 \le r &lt; s$), такое что $a^{d \cdot 2^r} \equiv -1 \pmod N$.
&lt;ul>
&lt;li>В C++ при вычислении по модулю $-1 \pmod N$ равно $N-1$.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>Если $N$ простое, то для любого $a$ это условие обязательно выполняется. Напротив, если $N$ составное, математически доказано, что вероятность выполнения этого условия (вероятность обмана) при выборе случайного $a$ составляет не более $\frac{1}{4}$.
Если провести $k$ независимых тестов, вероятность ошибочной оценки становится меньше или равна $\left(\frac{1}{4}\right)^k$, что на практике можно считать нулем. Не существует чисел, способных «абсолютно обмануть» алгоритм, подобных числам Кармайкла.&lt;/p>
&lt;h2 id="поток-алгоритма-метода-миллера-рабина-блок-схема-mermaid">Поток алгоритма метода Миллера-Рабина (Блок-схема Mermaid)
&lt;/h2>&lt;p>Следующая схема показывает логический поток одного теста Миллера-Рабина (для одного основания $a$).&lt;/p>
&lt;div class="mermaid">graph TD
Start["Начало теста (Вход: N, a)"] --> CalcDS["Вычисление нечетного d и s, удовлетворяющих N-1 = d * 2^s"]
CalcDS --> CalcX["Вычисление x = a^d mod N"]
CalcX --> CheckX1{"x == 1 или x == N-1 ?"}
CheckX1 -- "Да" --> ReturnTrue["Возможно простое (Probably Prime)"]
CheckX1 -- "Нет" --> LoopStart["Начало цикла от r = 1 до s-1"]
LoopStart --> LoopCondition{"r &lt; s ?"}
LoopCondition -- "Нет" --> ReturnFalse["Точно составное (Composite)"]
LoopCondition -- "Да" --> SquareX["Вычисление x = (x * x) mod N"]
SquareX --> CheckXMinus1{"x == N - 1 ?"}
CheckXMinus1 -- "Да" --> ReturnTrue
CheckXMinus1 -- "Нет" --> CheckXOne{"x == 1 ?"}
CheckXOne -- "Да" --> ReturnFalse
CheckXOne -- "Нет" --> LoopNext["Увеличение r на 1 и переход к следующему"]
LoopNext --> LoopCondition&lt;/div>
&lt;hr>
&lt;h1 id="4-детерминированная-проверка-для-64-битных-целых-чисел">4. Детерминированная проверка для 64-битных целых чисел
&lt;/h1>&lt;p>Тест простоты Миллера-Рабина изначально является «вероятностным» алгоритмом, но если верхняя граница $N$ фиксирована, можно провести проверку со «100% достоверностью», протестировав все основания $a$ из определенного набора.
Это называется &lt;strong>Детерминированным тестом Миллера-Рабина (Deterministic Miller-Rabin Test)&lt;/strong>.&lt;/p>
&lt;p>Исследования Джима Синклера (Jim Sinclair) и других показали, что для всех целых чисел $N &lt; 2^{64}$ (около $1.8 \times 10^{19}$) достаточно выбрать и проверить следующие $7$ простых чисел в качестве оснований $a$, чтобы получить полностью детерминированный результат.&lt;/p>
&lt;p>&lt;strong>Список оснований $a$ для проверки:&lt;/strong>
&lt;code>{2, 325, 9375, 28178, 450775, 9780504, 1795265022}&lt;/code>&lt;/p>
&lt;p>Альтернативно, существует другой известный набор из $12$ простых чисел, с помощью которого также можно идеально проверить числа $N &lt; 2^{64}$.
&lt;code>{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}&lt;/code>&lt;/p>
&lt;p>В этот раз, чтобы повысить простоту и надежность алгоритма, мы применим подход с использованием этих $12$ простых чисел (или более оптимизированных $7$ баз). В реализации на C++ мы применим оптимизацию, минимизирующую количество тестов путем разделения диапазонов с помощью условных переходов.&lt;/p>
&lt;hr>
&lt;h1 id="5-высокоуровневая-реализация-на-c-highly-optimized-c-implementation">5. Высокоуровневая реализация на C++ (Highly Optimized C++ Implementation)
&lt;/h1>&lt;p>Теперь давайте объединим всю математическую теорию и дизайн алгоритма, чтобы представить реализацию функции проверки простоты Миллера-Рабина высочайшего уровня в современном C++.&lt;/p>
&lt;h2 id="ключевые-моменты-реализации">Ключевые моменты реализации
&lt;/h2>&lt;ol>
&lt;li>
&lt;p>&lt;strong>Предотвращение переполнения при умножении 64-битных чисел:&lt;/strong>
При $N \approx 10^{18}$ значение $x \times x$ при умножении по модулю может достигать $10^{36}$, что с легкостью переполнит максимальное значение обычных 64-битных целых чисел (&lt;code>uint64_t&lt;/code> или &lt;code>long long&lt;/code>), равное $1.8 \times 10^{19}$.
Чтобы решить эту проблему, мы используем расширенный тип GCC или Clang &lt;code>__int128_t&lt;/code> (или &lt;code>unsigned __int128&lt;/code>) для выполнения вычислений с 128-битной точностью перед взятием модуля. Это позволяет быстро выполнять умножение по модулю без использования сложных алгоритмов.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Выбор детерминированных оснований:&lt;/strong>
Когда значение $N$ невелико, мы оптимизируем алгоритм так, чтобы требовалось протестировать лишь небольшое число оснований (bases).&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="полный-исходный-код-на-c">Полный исходный код на C++
&lt;/h2>&lt;p>Ниже представлен готовый к практическому применению полный исходный код. Этот код можно напрямую скопировать и использовать в среде спортивного программирования и т.д.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;span class="lnt">83
&lt;/span>&lt;span class="lnt">84
&lt;/span>&lt;span class="lnt">85
&lt;/span>&lt;span class="lnt">86
&lt;/span>&lt;span class="lnt">87
&lt;/span>&lt;span class="lnt">88
&lt;/span>&lt;span class="lnt">89
&lt;/span>&lt;span class="lnt">90
&lt;/span>&lt;span class="lnt">91
&lt;/span>&lt;span class="lnt">92
&lt;/span>&lt;span class="lnt">93
&lt;/span>&lt;span class="lnt">94
&lt;/span>&lt;span class="lnt">95
&lt;/span>&lt;span class="lnt">96
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;vector&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;cstdint&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;initializer_list&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Быстрое вычисление (a * b) mod m с помощью 128-битного целого числа
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kr">inline&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="nf">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="p">)((&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="n">__int128&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Вычисление (base^exp) mod m методом бинарного возведения в степень
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nf">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">exp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">res&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">base&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exp&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">res&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Детерминированная проверка 64-битных целых чисел тестом Миллера-Рабина
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">bool&lt;/span> &lt;span class="nf">is_prime_miller_rabin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Предварительная проверка граничных значений и малых простых чисел
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">5&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">7&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Разложение на вид n-1 = d * 2^s
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">int&lt;/span> &lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="n">d&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d&lt;/span> &lt;span class="o">&amp;gt;&amp;gt;=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">s&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Список оснований (bases) для тестирования
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// Оптимизация для минимизации количества проверяемых оснований в зависимости от размера N
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">bases&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">4759123141ULL&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">7&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">61&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">1122004669633ULL&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">13&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">23&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1662803&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 7 оснований, дающих детерминированный результат для всех чисел N &amp;lt; 2^64
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">bases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">325&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9375&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">28178&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">450775&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9780504&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1795265022&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Выполнение теста для каждого основания
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nl">a&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">bases&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span> &lt;span class="o">%=&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Если a кратно n, определить невозможно, но n не простое
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint64_t&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="o">||&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">continue&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Первое условие выполнено, переход к следующему основанию
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="n">composite&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Цикл s-1 раз (x = x^2 mod n)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">int&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">s&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">r&lt;/span>&lt;span class="o">++&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">x&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_mul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">composite&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// Второе условие выполнено, возможно простое
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Если ни одно из условий не выполнено, число гарантированно составное
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">composite&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Если все основания прошли проверку, число гарантированно простое
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">int&lt;/span> &lt;span class="nf">main&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Примеры для тестирования
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint64_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">test_cases&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">1000000007&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Известное простое число
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">998244353&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Известное простое число
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">1000000000000000003&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Простое число в районе 10^18
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">1000000000000000007&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Составное число (10^18 + 7)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">561&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="c1">// Число Кармайкла (составное)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="mi">18446744073709551557ULL&lt;/span> &lt;span class="c1">// Одно из наибольших простых чисел в районе 2^64
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kt">uint64_t&lt;/span> &lt;span class="nl">n&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">test_cases&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; is &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">is_prime_miller_rabin&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="s">&amp;#34;Prime&amp;#34;&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="s">&amp;#34;Composite&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">endl&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h1 id="6-временная-сложность-алгоритма-и-оценка-производительности">6. Временная сложность алгоритма и оценка производительности
&lt;/h1>&lt;p>Рассмотрим производительность реализованного алгоритма.&lt;/p>
&lt;h2 id="временная-сложность-time-complexity">Временная сложность (Time Complexity)
&lt;/h2>&lt;ul>
&lt;li>&lt;strong>Метод пробных делений:&lt;/strong> $O(\sqrt{N})$&lt;/li>
&lt;li>&lt;strong>Тест Ферма:&lt;/strong> возведение в степень $O(\log N) \times k$ (где $k$ — количество попыток)&lt;/li>
&lt;li>&lt;strong>Метод Миллера-Рабина:&lt;/strong> возведение в степень и цикл $O(\log N) \times k$&lt;/li>
&lt;/ul>
&lt;p>В 64-битной среде ($N \le 2^{64}$) описанный выше детерминированный алгоритм Миллера-Рабина проверяет максимум $7$ оснований. Следовательно, $k \le 7$ можно считать константой, и общая временная сложность строго равна $O(\log N)$.
Даже в максимальном случае ($N \approx 10^{19}$) количество шагов выполнения укладывается в $7 \times 64 = 448$ базовых операций, а время выполнения составляет менее нескольких микросекунд ($10^{-6}$ секунд). По сравнению с методом пробных делений $O(\sqrt{N})$ (около $4 \times 10^9$ итераций цикла), достигается &lt;strong>ускорение в миллионы раз&lt;/strong>.&lt;/p>
&lt;h2 id="дальнейшая-оптимизация-умножение-монтгомери-montgomery-multiplication">Дальнейшая оптимизация: Умножение Монтгомери (Montgomery Multiplication)
&lt;/h2>&lt;p>В реализации, приведенной в этой статье, используется расширенный 128-битный тип &lt;code>__int128_t&lt;/code> для операции деления (вычисление остатка &lt;code>%&lt;/code>). Даже на современных процессорах целочисленное деление (инструкция DIV) — это дорогостоящая операция, занимающая десятки тактов по сравнению со сложением или умножением.&lt;/p>
&lt;p>Создатели библиотек и спортивные программисты, стремящиеся к экстремальной оптимизации, иногда применяют метод, называемый &lt;strong>Умножением Монтгомери (Montgomery Multiplication)&lt;/strong>. Умножение Монтгомери — это потрясающий алгоритм, который заменяет дорогостоящую операцию взятия модуля (деления) на «только битовые сдвиги и умножения» за счет отображения чисел в специальное «пространство Монтгомери».
Интегрировав этот метод в модульное умножение при тесте Миллера-Рабина, можно увеличить скорость выполнения еще примерно в 2-3 раза. Поскольку это очень глубокая тема, мы подробно рассмотрим её в другой статье.&lt;/p>
&lt;hr>
&lt;h1 id="7-заключение">7. Заключение
&lt;/h1>&lt;p>В этой статье мы охватили все аспекты проверки на простоту: от основ до продвинутых концепций.
Давайте вспомним ключевые моменты.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Метод пробных делений&lt;/strong> надежен, но из-за вычислительной сложности $O(\sqrt{N})$ он теряет практичность при $N > 10^{12}$.&lt;/li>
&lt;li>&lt;strong>Тест Ферма&lt;/strong> очень быстр — $O(\log N)$, но имеет фатальный недостаток: он ошибается на абсолютно псевдопростых числах, таких как числа Кармайкла.&lt;/li>
&lt;li>&lt;strong>Тест простоты Миллера-Рабина&lt;/strong> — это практичный и мощнейший алгоритм, который устраняет слабость теста Ферма.&lt;/li>
&lt;li>В реализации на C++ использование &lt;code>__int128_t&lt;/code> позволяет безопасно обрабатывать переполнения при умножении 64-битных целых чисел.&lt;/li>
&lt;li>В пределах 64-битных целых чисел ($N &lt; 2^{64}$), выбирая $7$ или $12$ определенных простых чисел в качестве оснований, возможна &lt;strong>детерминированная (со 100% точностью) проверка на простоту&lt;/strong>, а не вероятностная.&lt;/li>
&lt;/ol>
&lt;p>Быстрая проверка на простоту — это незаменимая технология при вычислениях с огромными числами. Предоставленный в этой статье исходный код алгоритма Миллера-Рабина на C++ достаточно надежен для использования в реальных задачах. Обязательно попробуйте применить его в своих проектах или на соревнованиях по алгоритмике!&lt;/p>
&lt;div class="mermaid">graph LR
TrialDivision["Метод пробных делений (O(√N))"] --> Fermat["Тест Ферма (O(log N), есть слабые места)"]
Fermat --> MillerRabin["Метод Миллера-Рабина (O(log N), можно сделать детерминированным)"]
MillerRabin --> Montgomery["+ Умножение Монтгомери (ускорение в несколько раз)"]
style MillerRabin fill:#f9f,stroke:#333,stroke-width:2px&lt;/div>
&lt;p>Мир алгоритмов, в котором пересекаются программирование и математика, невероятно красив и глубок. Надеемся, что эта статья поможет вам в дальнейшем изучении.&lt;/p>
&lt;hr>
&lt;p>&lt;em>Reference:&lt;/em>&lt;/p>
&lt;ul>
&lt;li>&lt;em>Pomerance, C., Selfridge, J. L., &amp;amp; Wagstaff, S. S. (1980). The pseudoprimes to 25.10^9. Mathematics of Computation.&lt;/em>&lt;/li>
&lt;li>&lt;em>Sinclair, J. (2011). Deterministic Miller-Rabin primality testing.&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>【Математическое объяснение】Как работает алгоритм RSA, объяснено для старшеклассников</title><link>http://kenji.blog/ru/p/rsa-encryption-math-explained-for-beginners/</link><pubDate>Fri, 11 Sep 2026 13:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/rsa-encryption-math-explained-for-beginners/</guid><description>&lt;img src="http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/img/eyecatch.jpg" alt="Featured image of post 【Математическое объяснение】Как работает алгоритм RSA, объяснено для старшеклассников" />&lt;p>Одной из технологий, фундаментально обеспечивающих безопасность интернет-общества, является алгоритм RSA (шифрование RSA). Оплата кредитными картами в интернет-магазинах, общение с друзьями в соцсетях, отправка и получение конфиденциальной корпоративной информации — большая часть связи, которую мы небрежно используем каждый день, защищена алгоритмом RSA или технологиями-преемниками.&lt;/p>
&lt;p>Однако при слове &amp;ldquo;криптография&amp;rdquo; (шифрование) вы можете представить себе сложные шифровальные машины из шпионских фильмов или сверхсложную математику, понятную лишь избранным гениям. Безусловно, современная теория криптографии основана на продвинутой математике, но &lt;strong>фундаментальный механизм алгоритма RSA вполне можно понять, обладая знаниями математики на уровне старшей школы (свойства целых чисел, простые числа, сравнения по модулю и т.д.)&lt;/strong>.&lt;/p>
&lt;p>В этой статье, взяв за отправную точку знания школьной математики, мы шаг за шагом подробно объясним, на каких математических принципах работает алгоритм RSA и почему его так сложно взломать. Мы будем использовать конкретные примеры и объяснять все так тщательно, чтобы даже те, кто немного не в ладах с математикой, смогли во всем разобраться.&lt;/p>
&lt;hr>
&lt;h2 id="1-симметричное-и-асимметричное-шифрование-с-открытым-ключом">1. Симметричное и асимметричное шифрование (с открытым ключом)
&lt;/h2>&lt;p>Прежде чем переходить к математическим механизмам алгоритма RSA, давайте проясним базовые концепции криптографии. Методы шифрования в целом делятся на два типа: &amp;ldquo;шифрование с симметричным ключом&amp;rdquo; и &amp;ldquo;шифрование с открытым ключом&amp;rdquo; (асимметричное).&lt;/p>
&lt;h3 id="11-ограничения-симметричного-шифрования">1.1 Ограничения симметричного шифрования
&lt;/h3>&lt;p>Большинство шифров, используемых с древних времен, называются &amp;ldquo;системами симметричного шифрования&amp;rdquo;. Это метод, при котором &lt;strong>для &amp;ldquo;шифрования&amp;rdquo; (превращения сообщения в секретный шифр) и &amp;ldquo;расшифрования&amp;rdquo; (возвращения зашифрованного текста в исходное сообщение) используется один и тот же ключ&lt;/strong>.&lt;/p>
&lt;p>Например, предположим, что Алиса отправляет Бобу секретное письмо. Алиса кладет письмо в шкатулку и запирает ее на навесной замок (общий ключ). Чтобы Боб мог открыть эту шкатулку, у него должен быть точно такой же ключ, какой использовала Алиса.&lt;/p>
&lt;p>У этого метода есть серьезная проблема: &amp;ldquo;проблема распределения ключей&amp;rdquo;. Если находящиеся далеко друг от друга Алиса и Боб связываются впервые, как им безопасно обменяться ключами, чтобы их никто не перехватил? Если ключ украдет третье лицо во время пересылки, все последующие зашифрованные сообщения станут достоянием гласности.&lt;/p>
&lt;h3 id="12-революционное-изобретение-шифрование-с-открытым-ключом">1.2 Революционное изобретение: &amp;ldquo;Шифрование с открытым ключом&amp;rdquo;
&lt;/h3>&lt;p>Для решения этой проблемы с распределением ключей была изобретена &amp;ldquo;система шифрования с открытым ключом&amp;rdquo;. Алгоритм RSA относится к этому типу.&lt;/p>
&lt;p>В системе с открытым ключом используются &lt;strong>два разных ключа: &amp;ldquo;ключ для шифрования (открытый ключ)&amp;rdquo; и &amp;ldquo;ключ для расшифрования (закрытый ключ)&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;ol>
&lt;li>Получатель, Боб, создает пару ключей: &amp;ldquo;открытый ключ&amp;rdquo; и &amp;ldquo;закрытый ключ&amp;rdquo;.&lt;/li>
&lt;li>Боб публикует свой &amp;ldquo;открытый ключ&amp;rdquo; на весь мир (неважно, кто его получит).&lt;/li>
&lt;li>Отправительница, Алиса, использует &amp;ldquo;открытый ключ&amp;rdquo; Боба, чтобы зашифровать сообщение и отправить его.&lt;/li>
&lt;li>Зашифрованное сообщение можно расшифровать только с помощью &amp;ldquo;закрытого ключа&amp;rdquo;, который есть только у Боба.&lt;/li>
&lt;/ol>
&lt;p>Если снова привести аналогию с навесным замком, то Боб создает множество &amp;ldquo;открытых навесных замков (открытых ключей)&amp;rdquo; и разбрасывает их по всему миру. Алиса кладет свое сообщение для Боба в шкатулку, берет один из замков Боба и защелкивает его. Как только замок закрыт, его можно открыть только с помощью &amp;ldquo;мастер-ключа (закрытого ключа)&amp;rdquo;, который есть у Боба. Даже если кто-то украдет шкатулку по дороге, он не сможет ее открыть без мастер-ключа.&lt;/p>
&lt;div class="mermaid">graph TD
A["Алиса (отправитель)"] --> B["Открытый текст (сообщение)"]
B --> C["Процесс шифрования"]
D["Открытый ключ Боба (доступен всем)"] --> C
C --> E["Отправка через интернет: Зашифрованный текст"]
E --> F["Процесс расшифрования"]
G["Закрытый ключ Боба (только у Боба)"] --> F
F --> H["Восстановленный открытый текст (сообщение)"]
H --> I["Боб (получатель)"]&lt;/div>
&lt;p>Чтобы реализовать эту революционную систему, потребовалась своего рода &lt;strong>&amp;ldquo;односторонняя функция (математическая головоломка с односторонним движением)&amp;rdquo;&lt;/strong>, которая позволяет &amp;ldquo;легко зашифровать с помощью открытого ключа, но абсолютно невозможно расшифровать без закрытого&amp;rdquo;. И в качестве деталей для этой головоломки были выбраны хорошо знакомые нам &amp;ldquo;простые числа&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="2-математические-основы-rsa-1-простые-числа-и-факторизация">2. Математические основы RSA 1: Простые числа и факторизация
&lt;/h2>&lt;p>Безопасность алгоритма RSA основана на математическом факте: &lt;strong>&amp;ldquo;факторизация (разложение на простые множители) огромных чисел — чрезвычайно сложная задача&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;h3 id="21-что-такое-простые-числа">2.1 Что такое простые числа?
&lt;/h3>&lt;p>Простое число — это &amp;ldquo;натуральное число больше 1, которое делится без остатка только на 1 и на само себя&amp;rdquo;.
Пример: $2, 3, 5, 7, 11, 13, 17, 19, 23...$&lt;/p>
&lt;p>Простые числа — это словно &amp;ldquo;атомы&amp;rdquo; всех целых чисел. Любое натуральное число можно разложить в виде произведения простых чисел. Это называется &lt;strong>факторизацией (разложением на простые множители)&lt;/strong>. Например, $60 = 2^2 \times 3 \times 5$. Тот факт, что любое число можно разложить на простые множители единственным образом (если не учитывать порядок), известен как &amp;ldquo;основная теорема арифметики&amp;rdquo;.&lt;/p>
&lt;h3 id="22-сложность-факторизации-односторонняя-функция">2.2 Сложность факторизации (Односторонняя функция)
&lt;/h3>&lt;p>Здесь важна асимметрия: &lt;strong>&amp;ldquo;умножать легко, а раскладывать на множители — сложно&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;p>Например, попробуйте вычислить в уме произведение следующих двух простых чисел:
$11 \times 13 = ?$
Это просто. Ответ $143$.&lt;/p>
&lt;p>А как насчет следующего числа?
Разложите на простые множители $323$.
Ну как? Это должно занять некоторое время. (Ответ $17 \times 19$).&lt;/p>
&lt;p>Если числа небольшие, человек еще может как-то справиться, но по мере увеличения чисел вычисления становятся взрывообразно сложными даже для компьютеров. В распространенном сегодня алгоритме RSA используется число $N = p \times q$, полученное путем перемножения двух невероятно огромных простых чисел $p$ и $q$ размером 2048 бит (около 600 цифр в десятичной системе).&lt;/p>
&lt;p>Когда даны два огромных простых числа $p$ и $q$, вычисление $N$ занимает у компьютера долю секунды (меньше миллисекунды). Однако, если дано только $N$, поиск исходных $p$ и $q$ займет столько времени, что даже самые быстрые суперкомпьютеры современности не смогут решить эту задачу и за триллионы лет.&lt;/p>
&lt;p>Эта &lt;strong>&amp;ldquo;вычислительная асимметрия (в одну сторону легко, в обратную — сложно)&amp;rdquo;&lt;/strong> является фундаментом для создания связи между открытым и закрытым ключами.&lt;/p>
&lt;hr>
&lt;h2 id="3-математические-основы-rsa-2-сравнения-арифметика-остатков--модульная-арифметика">3. Математические основы RSA 2: Сравнения (Арифметика остатков / модульная арифметика)
&lt;/h2>&lt;p>Вычисления в алгоритме RSA выполняются не в виде привычных нам сложений и умножений, где числа могут бесконечно расти, а в мире &amp;ldquo;остатков&amp;rdquo; от деления на определенное число. Это называется &lt;strong>сравнением по модулю (модульной арифметикой)&lt;/strong>.&lt;/p>
&lt;h3 id="31-математика-часов">3.1 Математика часов
&lt;/h3>&lt;p>Модульную арифметику часто сравнивают с &amp;ldquo;математикой часов&amp;rdquo;. Предположим, сейчас 10 часов. Который час будет через 5 часов? $10 + 5 = 15$ часов, но на обычных 12-часовых часах мы ответим &amp;ldquo;3 часа&amp;rdquo;. Это потому, что остаток от деления 15 на 12 равен 3.&lt;/p>
&lt;p>В математике это записывается следующим образом:
&lt;/p>
$$ 15 \equiv 3 \pmod{12} $$
&lt;p>
Это читается как &amp;ldquo;15 сравнимо с 3 по модулю 12 (остатки от деления на 12 равны)&amp;rdquo;.&lt;/p>
&lt;h3 id="32-основные-свойства-сравнений">3.2 Основные свойства сравнений
&lt;/h3>&lt;p>Сравнения обладают очень удобными свойствами, похожими на свойства обычных уравнений ($=$). Пусть модуль (делитель) равен $N$.
Если $a \equiv b \pmod N$ и $c \equiv d \pmod N$, то выполняется следующее:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Сложение:&lt;/strong> $a + c \equiv b + d \pmod N$&lt;/li>
&lt;li>&lt;strong>Вычитание:&lt;/strong> $a - c \equiv b - d \pmod N$&lt;/li>
&lt;li>&lt;strong>Умножение:&lt;/strong> $a \times c \equiv b \times d \pmod N$&lt;/li>
&lt;li>&lt;strong>Возведение в степень:&lt;/strong> $a^k \equiv b^k \pmod N$ (где $k$ — натуральное число)&lt;/li>
&lt;/ol>
&lt;p>Особенно важным является свойство &amp;ldquo;возведения в степень&amp;rdquo;. Оно означает, что &lt;strong>&amp;ldquo;остаток от степени равен степени от остатка&amp;rdquo;&lt;/strong>.
Например, мы хотим найти остаток от деления $7^{100}$ на $5$. Честно умножать $7$ само на себя 100 раз, а затем делить на $5$ было бы очень сложно. Но используя свойства сравнений, поскольку $7 \equiv 2 \pmod 5$, мы получаем $7^{100} \equiv 2^{100} \pmod 5$, что позволяет кардинально упростить вычисления. В мире криптографии мы работаем с возведением в степень очень больших чисел, поэтому это свойство просто необходимо.&lt;/p>
&lt;hr>
&lt;h2 id="4-математические-основы-rsa-3-функция-эйлера-и-теорема-эйлера">4. Математические основы RSA 3: Функция Эйлера и теорема Эйлера
&lt;/h2>&lt;p>Здесь начинается магическая математика, составляющая ядро RSA. Появляется &amp;ldquo;теорема Эйлера&amp;rdquo;, которая является обобщением &amp;ldquo;малой теоремы Ферма&amp;rdquo;.&lt;/p>
&lt;h3 id="41-функция-эйлера-тотиент-phin">4.1 Функция Эйлера (тотиент) $\phi(N)$
&lt;/h3>&lt;p>Функция Эйлера (функция $\phi$) для некоторого натурального числа $N$ — это функция, которая возвращает &lt;strong>&amp;ldquo;количество натуральных чисел от 1 до $N$, которые взаимно просты с $N$ (наибольший общий делитель равен 1)&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;p>Давайте рассмотрим несколько примеров.&lt;/p>
&lt;ul>
&lt;li>$\phi(5)$: Среди 1, 2, 3, 4, 5 взаимно простыми с 5 являются 1, 2, 3, 4 — всего 4 числа. Следовательно, $\phi(5) = 4$.&lt;/li>
&lt;li>$\phi(6)$: Среди 1, 2, 3, 4, 5, 6 взаимно простыми с 6 являются 1, 5 — всего 2 числа. Следовательно, $\phi(6) = 2$.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>【Особое свойство для простых чисел】&lt;/strong>
Если $p$ — простое число, все числа от 1 до $p-1$ взаимно просты с $p$. Следовательно,
&lt;/p>
$$ \phi(p) = p - 1 $$
&lt;p>&lt;strong>【Особое свойство для произведения простых чисел】&lt;/strong>
Для двух различных простых чисел $p$ и $q$, если $N = p \times q$, то $\phi(N)$ можно легко вычислить следующим образом:
&lt;/p>
$$ \phi(N) = \phi(p) \times \phi(q) = (p - 1)(q - 1) $$
&lt;p>
Это свойство служит &amp;ldquo;потайной дверью&amp;rdquo; (лазейкой) в алгоритме RSA. Тот, кто знает $p$ и $q$ (создатель ключа), может вычислить $\phi(N)$ за мгновение, но третье лицо, знающее только $N$, не сможет вычислить $\phi(N)$ без разложения $N$ на простые множители.&lt;/p>
&lt;h3 id="42-теорема-эйлера">4.2 Теорема Эйлера
&lt;/h3>&lt;p>Леонард Эйлер, используя функцию $\phi(N)$, доказал следующую красивую теорему.&lt;/p>
&lt;p>&lt;strong>Теорема Эйлера:&lt;/strong>
Если целое число $a$ и $N$ взаимно просты, то выполняется следующее сравнение:
&lt;/p>
$$ a^{\phi(N)} \equiv 1 \pmod N $$
&lt;p>Это удивительное свойство: &amp;ldquo;если умножить некоторое число $a$ само на себя $\phi(N)$ раз и разделить на $N$, остаток всегда будет равен $1$&amp;rdquo;. (Если $N$ — простое число $p$, это принимает вид $a^{p-1} \equiv 1 \pmod p$ и называется малой теоремой Ферма).&lt;/p>
&lt;p>Давайте преобразуем эту теорему Эйлера. Умножим обе части на $a$ еще раз.
&lt;/p>
$$ a^{\phi(N) + 1} \equiv a \pmod N $$
&lt;p>Более того, для любого целого числа $k$, $a^{k \cdot \phi(N)}$ также будет равно $1^k = 1$, поэтому выполняется следующее равенство:
&lt;/p>
$$ a^{k \cdot \phi(N) + 1} \equiv a \pmod N $$
&lt;p>Именно эта формула является фундаментальным принципом, на котором строится магия RSA: &lt;strong>&amp;ldquo;после шифрования и расшифрования мы возвращаемся к исходному значению&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="5-алгоритм-rsa-шаги-генерации-ключей-шифрования-и-расшифрования">5. Алгоритм RSA: Шаги генерации ключей, шифрования и расшифрования
&lt;/h2>&lt;p>Теперь, когда у нас есть базовые знания, давайте посмотрим на конкретные шаги алгоритма RSA. Алгоритм RSA можно разделить на три основные фазы: &amp;ldquo;1. Генерация ключей&amp;rdquo;, &amp;ldquo;2. Шифрование&amp;rdquo; и &amp;ldquo;3. Расшифрование&amp;rdquo;.&lt;/p>
&lt;div class="mermaid">flowchart TD
A1["1. Выбрать простые числа p, q"] --> A2["Вычислить N = p × q"]
A1 --> A3["Вычислить φ(N) = (p-1)(q-1)"]
A3 --> A4["Выбрать e, взаимно простое с φ(N)"]
A3 --> A5["Вычислить d такое, что e × d ≡ 1 (mod φ(N))"]
A2 --> A6["Открытый ключ (N, e)"]
A4 --> A6
A5 --> A7["Закрытый ключ d"]
B1["2. Открытый текст (сообщение) M"] --> B2["Вычислить C ≡ M^e (mod N)"]
A6 -.-> B2
B2 --> B3["Отправить зашифрованный текст C"]
B3 --> C1["3. Полученный зашифрованный текст C"]
C1 --> C2["Вычислить M ≡ C^d (mod N)"]
A7 -.-> C2
C2 --> C3["Получить исходный открытый текст M"]&lt;/div>
&lt;h3 id="51-генерация-ключей-key-generation">5.1 Генерация ключей (Key Generation)
&lt;/h3>&lt;p>Получатель, Боб, генерирует для себя &amp;ldquo;открытый ключ&amp;rdquo; и &amp;ldquo;закрытый ключ&amp;rdquo;.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Выбор простых чисел:&lt;/strong> Случайным образом выбираются два больших простых числа $p$ и $q$.&lt;/li>
&lt;li>&lt;strong>Вычисление модуля $N$:&lt;/strong> Вычисляется $N = p \times q$. Это $N$ становится публичным.&lt;/li>
&lt;li>&lt;strong>Вычисление $\phi(N)$:&lt;/strong> Вычисляется функция Эйлера $\phi(N) = (p - 1)(q - 1)$. Это секретное число Боба.&lt;/li>
&lt;li>&lt;strong>Выбор открытого ключа $e$:&lt;/strong> Выбирается целое число $e$ такое, что $1 &lt; e &lt; \phi(N)$ и $e$ взаимно просто с $\phi(N)$.&lt;/li>
&lt;li>&lt;strong>Вычисление закрытого ключа $d$:&lt;/strong> Находится целое число $d$, удовлетворяющее следующему условию:
$$ e \times d \equiv 1 \pmod{\phi(N)} $$
Иными словами, &amp;ldquo;$d$ — это такое число, при котором остаток от деления $e \times d$ на $\phi(N)$ равен $1$&amp;rdquo;.&lt;/li>
&lt;/ol>
&lt;p>На этом подготовка ключей завершена.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Открытый ключ:&lt;/strong> Пара $(N, e)$. Публикуется для всего мира.&lt;/li>
&lt;li>&lt;strong>Закрытый ключ:&lt;/strong> $d$. Никогда никому не передается.&lt;/li>
&lt;/ul>
&lt;h3 id="52-шифрование-encryption">5.2 Шифрование (Encryption)
&lt;/h3>&lt;p>Допустим, Алиса хочет отправить Бобу секретное сообщение $M$. ($M$ — это числовое представление текста, причем $0 \le M &lt; N$). Алиса использует открытый ключ Боба $(N, e)$ и выполняет следующие вычисления:&lt;/p>
$$ C \equiv M^e \pmod N $$
&lt;p>Она вычисляет &amp;ldquo;остаток $C$ от деления сообщения $M$ в степени $e$ на $N$&amp;rdquo;. Это $C$ и есть зашифрованный текст.&lt;/p>
&lt;h3 id="53-расшифрование-decryption">5.3 Расшифрование (Decryption)
&lt;/h3>&lt;p>Боб получает зашифрованный текст $C$. Боб использует закрытый ключ $d$ и выполняет следующие вычисления:&lt;/p>
$$ M \equiv C^d \pmod N $$
&lt;p>Если вычислить &amp;ldquo;остаток от деления зашифрованного текста $C$ в степени $d$ на $N$&amp;rdquo;, то удивительным образом восстанавливается исходное сообщение $M$!&lt;/p>
&lt;hr>
&lt;h2 id="6-почему-расшифрование-возвращает-исходное-сообщение-математическое-доказательство">6. Почему расшифрование возвращает исходное сообщение? (Математическое доказательство)
&lt;/h2>&lt;p>Вы можете задаться вопросом: &amp;ldquo;Как так получается, что простое возведение $C$ в степень $d$ возвращает нас к исходному $M$?&amp;rdquo;. Вот тут-то и проявляет свою силу ранее упомянутая &amp;ldquo;теорема Эйлера&amp;rdquo;.&lt;/p>
&lt;p>Давайте подставим формулу шифрования $C = M^e$ в формулу расшифрования $C^d \pmod N$.
&lt;/p>
$$ C^d \equiv (M^e)^d \equiv M^{ed} \pmod N $$
&lt;p>Здесь вспомните шаг 5 генерации ключей. Боб при создании $d$ выбрал его так, чтобы выполнялось условие $e \times d \equiv 1 \pmod{\phi(N)}$. Это означает, что &amp;ldquo;$ed$ — это число, кратное $\phi(N)$, плюс $1$&amp;rdquo;. Используя целое число $k$, это можно записать следующим образом:
&lt;/p>
$$ ed = k \cdot \phi(N) + 1 $$
&lt;p>Подставим это в показатель степени и разложим с помощью правил возведения в степень:
&lt;/p>
$$ M^{ed} = M^{k \cdot \phi(N) + 1} = M^{k \cdot \phi(N)} \times M^1 = (M^{\phi(N)})^k \times M $$
&lt;p>Если предположить, что сообщение $M$ и $N$ взаимно просты, то согласно &lt;strong>теореме Эйлера&lt;/strong> получаем $M^{\phi(N)} \equiv 1 \pmod N$.
&lt;/p>
$$ (M^{\phi(N)})^k \times M \equiv 1^k \times M \equiv M \pmod N $$
&lt;p>Следовательно, блестяще выполняется следующая формула:
&lt;/p>
$$ C^d \equiv M \pmod N $$
&lt;p>Алиса не знает $d$, перехватчик тоже не знает $d$, поэтому получить $M$ из $C$ может только Боб, владеющий ключом $d$.&lt;/p>
&lt;hr>
&lt;h2 id="7-конкретный-пример-давайте-испытаем-rsa-вручную-используя-небольшие-простые-числа">7. Конкретный пример: давайте испытаем RSA вручную, используя небольшие простые числа
&lt;/h2>&lt;p>Давайте попробуем выполнить зашифрованную связь от Алисы к Бобу на практике, используя небольшие числа (простые числа).&lt;/p>
&lt;p>&lt;strong>【Фаза генерации ключей Боба】&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Выбираем два простых числа $p=11$, $q=13$.&lt;/li>
&lt;li>Вычисляем $N = 11 \times 13 = 143$.&lt;/li>
&lt;li>Вычисляем $\phi(N) = (11 - 1) \times (13 - 1) = 10 \times 12 = 120$.&lt;/li>
&lt;li>Выбираем открытый ключ $e$, взаимно простой с $\phi(N)=120$. Пусть $e=7$.&lt;/li>
&lt;li>Находим закрытый ключ $d$. Ищем такое $d$, чтобы выполнялось $7 \times d \equiv 1 \pmod{120}$.
В уравнении $7d = 120k + 1$, если $k=6$, получаем $721$, а $721 \div 7 = 103$.
Следовательно, $d = 103$.&lt;/li>
&lt;/ol>
&lt;ul>
&lt;li>Открытый ключ: $(N=143, e=7)$&lt;/li>
&lt;li>Закрытый ключ: $d=103$&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>【Фаза шифрования Алисы】&lt;/strong>
Предположим, мы хотим отправить сообщение $M = 9$.
Формула: $C \equiv 9^7 \pmod{143}$
$9^7 = 4,782,969$. Если разделить это число на 143, получим $33447$ с остатком $48$.
Зашифрованный текст получился $C = 48$.&lt;/p>
&lt;p>&lt;strong>【Фаза расшифрования Боба】&lt;/strong>
Боб получает зашифрованный текст $C = 48$ и использует закрытый ключ $d = 103$ для его расшифрования.
Формула: $M \equiv 48^{103} \pmod{143}$
Если запустить на калькуляторе или в программе &lt;code>(48 ** 103) % 143&lt;/code>, результат блестяще окажется равным &amp;ldquo;&lt;strong>9&lt;/strong>&amp;rdquo;! Исходное сообщение успешно получено.&lt;/p>
&lt;hr>
&lt;h2 id="8-как-найти-закрытый-ключ-d-расширенный-алгоритм-евклида">8. Как найти закрытый ключ $d$: Расширенный алгоритм Евклида
&lt;/h2>&lt;p>В примере с ручными вычислениями мы нашли $d=103$, подобрав $k$ интуитивно, но если числа состоят из сотен цифр, этот метод не сработает. В реальных программах используется алгоритм, называемый &lt;strong>&amp;ldquo;расширенным алгоритмом Евклида&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;p>Решить $7d \equiv 1 \pmod{120}$ — это то же самое, что найти целые числа $d$ и $y$, удовлетворяющие уравнению $7d + 120y = 1$. Выполняя алгоритм Евклида в обратном порядке, это можно вычислить механически.&lt;/p>
&lt;ol>
&lt;li>$120 \div 7 = 17$, остаток $1$&lt;/li>
&lt;li>Если преобразовать это, получим $1 = 120 - 17 \times 7$&lt;/li>
&lt;li>То есть, $-17 \times 7 \equiv 1 \pmod{120}$&lt;/li>
&lt;/ol>
&lt;p>В мире по модулю $120$, число $-17$ имеет то же значение, что и $120 - 17 = 103$. Следовательно, мы мгновенно находим $d = 103$. Этот метод позволяет выполнять вычисления очень быстро даже для огромных чисел.&lt;/p>
&lt;hr>
&lt;h2 id="9-другое-лицо-rsa-цифровая-подпись">9. Другое лицо RSA: Цифровая подпись
&lt;/h2>&lt;p>Замечательной особенностью алгоритма RSA является то, что, поменяв ролями открытый и закрытый ключи, его можно использовать как &lt;strong>&amp;ldquo;цифровую подпись&amp;rdquo;&lt;/strong>.&lt;/p>
&lt;p>При шифровании порядок был таким: &amp;ldquo;шифрование открытым ключом $\Rightarrow$ расшифрование закрытым ключом&amp;rdquo;,
а в цифровой подписи выполняется процедура &amp;ldquo;шифрование закрытым ключом $\Rightarrow$ расшифрование открытым ключом&amp;rdquo;.&lt;/p>
&lt;div class="mermaid">flowchart TD
A1["1. Алиса создает подпись с помощью закрытого ключа"] --> A2["S ≡ M^d (mod N)"]
A2 --> A3["Отправить сообщение M и подпись S"]
A3 --> B1["2. Боб проверяет подпись с помощью открытого ключа"]
B1 --> B2["Вычислить M' ≡ S^e (mod N)"]
B2 --> B3["Проверить, совпадают ли M' и M"]&lt;/div>
&lt;p>Алиса использует свой закрытый ключ $d$ для преобразования сообщения (это и есть подпись $S$) и отправляет его Бобу. Боб использует открытый ключ Алисы $e$ для выполнения проверочных вычислений. Если результат вычислений совпадает с исходным сообщением, это одновременно доказывает, что &amp;ldquo;эти данные могли быть созданы только с помощью закрытого ключа Алисы&amp;rdquo; и что &amp;ldquo;сообщение не было изменено в пути&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="10-rsa-на-практике-в-коде">10. RSA на практике в коде
&lt;/h2>&lt;p>Возведение в степень, которое сложно сделать вручную, очень легко реализовать с помощью Python. Ниже представлен код на Python, позволяющий опробовать основную логику алгоритма RSA.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Найти наибольший общий делитель&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">b&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">mod_inverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Найти закрытый ключ d (используется встроенная функция Python 3.8 и выше)&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. Генерация ключей&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">11&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">13&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">N&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">q&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">phi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">e&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">d&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod_inverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">phi&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Открытый ключ: (N=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">N&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, e=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">e&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">), Закрытый ключ: d=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">d&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. Шифрование&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">9&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">ciphertext&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Зашифрованный текст: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">ciphertext&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. Расшифрование&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">decrypted_message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ciphertext&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">d&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;Расшифрованное сообщение: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">decrypted_message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Функция &lt;code>pow(base, exp, mod)&lt;/code> в Python внутри использует быстрый алгоритм, называемый &amp;ldquo;алгоритмом возведения в степень справа налево&amp;rdquo; (быстрое возведение в степень по модулю), поэтому вычисления для чисел из сотен цифр завершаются мгновенно.&lt;/p>
&lt;hr>
&lt;h2 id="11-заключение-и-будущее-криптографии">11. Заключение и будущее криптографии
&lt;/h2>&lt;p>Основываясь на знаниях школьной математики, мы разобрали механизмы работы алгоритма RSA.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Сложность факторизации:&lt;/strong> Легко вычислить $p \times q = N$, но очень сложно найти $p, q$ по заданному $N$.&lt;/li>
&lt;li>&lt;strong>Сравнения и теорема Эйлера:&lt;/strong> Благодаря закону $a^{\phi(N)} \equiv 1 \pmod N$ создается волшебная потайная дверь, позволяющая &amp;ldquo;вернуться к исходному значению при возведении в определенную степень&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>Открытый и закрытый ключи:&lt;/strong> Зашифровать может кто угодно, но расшифровать способен только законный получатель.&lt;/li>
&lt;/ol>
&lt;p>Используемое в настоящее время в алгоритме RSA число $N$ состоит из более чем 600 цифр, и даже если задействовать все суперкомпьютеры в мире, разложение его на множители займет больше времени, чем возраст Вселенной. Однако, когда &amp;ldquo;квантовые компьютеры&amp;rdquo;, исследования которых ведутся в последние годы, будут введены в практическое использование в будущем, с помощью &amp;ldquo;алгоритма Шора&amp;rdquo; эту задачу факторизации можно будет решить за мгновение. По этой причине во всем мире сейчас ускоренными темпами ведется разработка &amp;ldquo;постквантовой криптографии&amp;rdquo;, которая не может быть взломана даже квантовыми компьютерами.&lt;/p>
&lt;p>Продвинутая математика, которая часто считается &amp;ldquo;бесполезной&amp;rdquo;, на самом деле фундаментально защищает нашу повседневную жизнь. Алгоритм RSA — это прекрасный учебный материал, который демонстрирует глубину и красоту математики. Мы надеемся, что благодаря этой статье вы смогли хотя бы немного почувствовать, насколько интересными могут быть криптография и математика.&lt;/p></description></item></channel></rss>