<?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/ko/tags/prime-numbers/</link><description>Recent content in Prime Numbers on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ko</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 16:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ko/tags/prime-numbers/index.xml" rel="self" type="application/rss+xml"/><item><title>리만 가설과 소수의 분포: 현대 암호 기술과의 깊은 관계</title><link>http://kenji.blog/ko/p/riemann-hypothesis-prime-distribution-cryptography/</link><pubDate>Fri, 11 Sep 2026 16:00:00 +0900</pubDate><guid>http://kenji.blog/ko/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>&amp;lsquo;소수(Prime Numbers)&amp;lsquo;는 1과 자기 자신으로만 나누어 떨어지는 자연수이며, 수학 세계에서 &amp;lsquo;원자&amp;rsquo;라고도 불립니다. 2, 3, 5, 7, 11, 13&amp;hellip; 으로 이어지는 이 수열은 언뜻 보면 무질서하고 무작위로 나타나는 것처럼 보입니다. 고대 그리스의 수학자 유클리드가 &amp;lsquo;소수가 무한히 존재한다&amp;rsquo;는 것을 증명한 이후, 수많은 수학자들이 이 소수의 배열에 숨겨진 규칙성을 밝혀내기 위해 도전해 왔습니다.&lt;/p>
&lt;p>그 소수의 수수께끼에 가장 근접한 것이 1859년 독일의 수학자 베른하르트 리만(Bernhard Riemann)이 제안한 **&amp;lsquo;리만 가설(Riemann Hypothesis)&amp;rsquo;**입니다. 리만 가설은 현대 수학에서 가장 중요하고 미해결된 난제 중 하나이며, 클레이 수학연구소가 정한 밀레니엄 현상 문제 중 하나로 100만 달러의 상금이 걸려 있습니다.&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>소수가 어떻게 분포되어 있는지를 이해하기 위해, 수학자들은 &amp;lsquo;어떤 수 $x$ 이하의 소수가 몇 개 존재하는가&amp;rsquo;를 나타내는 &lt;strong>소수 계수 함수(Prime-counting function)&lt;/strong> $\pi(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세의 천재 수학자 카를 프리드리히 가우스(Carl Friedrich Gauss)는 방대한 소수표를 계산하여 소수의 출현 빈도가 자연로그 $\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년 자크 아다마르와 샤를 장 드 라 발레 푸생에 의해 독립적으로 증명되었고, **소수 정리(Prime Number Theorem, PNT)**로서 확립되었습니다.&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)$ 사이에는 항상 &amp;lsquo;오차&amp;rsquo;, 즉 &amp;lsquo;요동(fluctuation)&amp;lsquo;이 존재합니다. 이 요동의 실체야말로 리만 가설이 밝혀내고자 하는 가장 큰 미스터리인 것입니다.&lt;/p>
&lt;hr>
&lt;h1 id="3-리만-제타-함수와-오일러-곱">3. 리만 제타 함수와 오일러 곱
&lt;/h1>&lt;p>소수의 분포를 분석하는 데 있어 가장 강력한 무기가 되는 것이 **리만 제타 함수(Riemann Zeta Function)**입니다. 원래는 레온하르트 오일러(Leonhard Euler)에 의해 실수 $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$ 에 대한 무한곱으로 표현될 수 있음을 증명한 것입니다. 이것이 **오일러 곱(Euler Product Formula)**입니다.&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>리만의 천재성은 오일러가 실수로만 생각했던 $\zeta(s)$ 의 변수 $s$ 를 **복소수 $s = \sigma + it$($\sigma$ 는 실수부, $t$ 는 허수부)**로 확장한 데 있습니다.&lt;/p>
&lt;p>원래의 무한급수는 $\sigma > 1$ 에서만 수렴하지만, 리만은 &amp;lsquo;해석적 연속(Analytic Continuation)&amp;lsquo;이라는 기법을 사용하여 $s = 1$ 의 극을 제외한 전체 복소평면상에서 $\zeta(s)$ 가 의미를 갖도록 정의를 확장했습니다.&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>제타 함수의 값이 0이 되는 복소수 $s$ 를 &amp;lsquo;영점&amp;rsquo;이라고 부릅니다.
함수 방정식에서 $s$ 가 음의 짝수($-2, -4, -6, \dots$)일 때 $\sin(\pi s / 2)$ 가 0이 되므로, $\zeta(s) = 0$ 이 됩니다. 이것들은 **자명한 영점(Trivial zeros)**이라고 불립니다.&lt;/p>
&lt;p>그러나 소수의 분포에서 중요한 것은 그 외의 영점, 즉 $0 \le \sigma \le 1$ 인 &amp;lsquo;임계 영역(Critical strip)&amp;lsquo;에 존재하는 **비자명한 영점(Non-trivial zeros)**입니다.&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인 직선을 &amp;lsquo;임계선(Critical line)&amp;lsquo;이라고 부릅니다.&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>리만과 후대의 수학자 폰 망골트(von Mangoldt)는 소수의 분포를 정확하게 기술하는 &amp;lsquo;명시 공식(Explicit formula)&amp;lsquo;을 도출했습니다. 체비쇼프 함수 $\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$ 에 의존하는 파동 같은 항을 더하고 뺌으로써 소수의 계단형태의 정확한 분포가 복원되는 것입니다. 비자명한 영점은 소수 분포의 &amp;lsquo;주파수(파동)&amp;lsquo;를 나타낸다고 할 수 있습니다.&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>리만 가설이 참이라면, 소수는 우리가 상상할 수 있는 한 가장 &amp;lsquo;규칙적이고 아름답게&amp;rsquo; 분포하고 있다&lt;/strong>는 것이 증명되는 것입니다.&lt;/p>
&lt;hr>
&lt;h1 id="6-현대-암호-기술과-소수의-불가분한-관계">6. 현대 암호 기술과 소수의 불가분한 관계
&lt;/h1>&lt;p>여기까지는 심오한 순수 수학의 세계였지만, 이 소수의 성질은 현대의 디지털 사회를 근본적으로 지탱하고 있습니다. 그 대표적인 예가 &lt;strong>RSA 암호&lt;/strong>를 비롯한 공개키 암호 방식입니다.&lt;/p>
&lt;p>인터넷에서의 신용카드 결제, 비밀번호 전송, 블록체인의 전자 서명 등 모든 통신의 안전성은 &amp;lsquo;소수&amp;rsquo;에 의존하고 있습니다.&lt;/p>
&lt;h3 id="rsa-암호의-원리">RSA 암호의 원리
&lt;/h3>&lt;p>RSA 암호의 안전성은 &amp;lsquo;자릿수가 큰 합성수의 소인수분해는 매우 어렵다&amp;rsquo;는 수학적 사실(소인수분해 문제)에 기반하고 있습니다.&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$ 은 공개키 $e, N$ 을 사용하여 암호문 $C$ 로 변환됩니다.
&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 암호를 깨기 위해서는 거대한 $N$ 에서 원래의 소수 $p$ 와 $q$ 를 찾아내야(소인수분해해야) 합니다. 현재 주류인 알고리즘(일반 수체 체: GNFS 등)을 사용하더라도, 수백 자리의 수를 소인수분해하려면 슈퍼컴퓨터를 사용해도 우주의 나이를 훨씬 초과하는 시간이 걸리는 것으로 알려져 있습니다.&lt;/p>
&lt;hr>
&lt;h1 id="7-리만-가설이-암호-기술에-미치는-영향">7. 리만 가설이 암호 기술에 미치는 영향
&lt;/h1>&lt;p>그렇다면 순수 수학의 정점에 있는 &amp;lsquo;리만 가설&amp;rsquo;과 &amp;lsquo;암호 기술&amp;rsquo;은 어떻게 교차하는 것일까요?&lt;/p>
&lt;h3 id="71-소수-생성-알고리즘소수-판별과-일반화된-리만-가설grh">7.1. 소수 생성 알고리즘(소수 판별)과 일반화된 리만 가설(GRH)
&lt;/h3>&lt;p>RSA 암호를 운용하기 위해서는 먼저 거대한 소수 $p$ 와 $q$ 를 생성해야 합니다. 하지만 &amp;lsquo;어떤 수가 소수인지&amp;rsquo;를 확실하고 빠르게 판별하는 것은 쉽지 않습니다.&lt;/p>
&lt;p>현재 실용적으로 사용되고 있는 것은 **밀러-라빈 소수 판별법(Miller-Rabin primality test)**이라는 확률적 알고리즘입니다. 이 알고리즘은 매우 빠르지만, 아주 낮은 확률로 합성수를 소수라고 잘못 판별하는 &amp;lsquo;유사소수&amp;rsquo;의 위험이 있습니다.&lt;/p>
&lt;p>하지만 리만 가설을 디리클레 L-함수로 확장한 **&amp;lsquo;일반화된 리만 가설(Generalized Riemann Hypothesis, GRH)&amp;rsquo;**이 참이라고 가정하면 이야기는 극적으로 달라집니다.
GRH가 참이라면, 밀러-라빈 판별법에서의 테스트 횟수 상한이 수학적으로 보장되어 확률적 알고리즘에서 &lt;strong>&amp;lsquo;결정론적 다항 시간 알고리즘&amp;rsquo;으로 승화&lt;/strong>하는 것입니다(이는 AKS 소수 판별법이 발견되기 이전부터 알려져 있던 중대한 사실이었습니다).&lt;/p>
&lt;p>즉, 리만 가설(및 그 확장)은 &amp;lsquo;거대한 소수를 절대적인 확신을 가지고 빠르게 생성할 수 있는가&amp;rsquo;라는 암호의 기반 생성에 직접적인 보증을 부여하는 역할을 가지고 있습니다.&lt;/p>
&lt;h3 id="72-소인수분해-알고리즘과의-관계">7.2. 소인수분해 알고리즘과의 관계
&lt;/h3>&lt;p>암호를 해독하는 쪽의 알고리즘(일반 수체 체 등)의 계산 복잡도를 평가할 때도 소수의 분포에 관한 지식이 필수적입니다. 소인수분해 알고리즘의 대부분은 &amp;lsquo;매끄러운 수(Smooth numbers: 작은 소인수만을 가지는 수)&amp;lsquo;의 분포에 의존하고 있습니다.&lt;/p>
&lt;p>매끄러운 수가 어느 정도의 빈도로 나타나는지를 엄밀하게 평가하기 위해서는 소수의 분포에 관한 깊은 이해가 필요하며, 여기서도 제타 함수나 리만 가설에 직결되는 해석적 정수론의 기법이 구사되고 있습니다. 리만 가설이 증명되어 소수 분포의 오차가 완전히 결정된다면, 소인수분해 알고리즘의 성능 한계도 더욱 정확하게 파악할 수 있게 됩니다.&lt;/p>
&lt;hr>
&lt;h1 id="8-만약-리만-가설이-증명된다면-암호는-뚫릴-것인가">8. 만약 리만 가설이 증명된다면, 암호는 뚫릴 것인가?
&lt;/h1>&lt;p>도시 괴담처럼 &amp;lsquo;리만 가설이 풀리면 RSA 암호는 순식간에 붕괴한다&amp;rsquo;고 이야기되는 경우가 있지만, &lt;strong>이는 수학적으로 부정확합니다&lt;/strong>.&lt;/p>
&lt;p>리만 가설의 증명 자체가 즉각적으로 소인수분해를 극적으로 고속화하는 마법의 알고리즘을 만들어내는 것은 아닙니다. 리만 가설은 어디까지나 소수의 &amp;lsquo;거시적인 분포의 규칙성&amp;rsquo;에 대한 정리이며, 개별 수 $N$ 이 어떤 소수로 나누어떨어지는지(국소적인 성질)를 직접 가르쳐 주는 것은 아니기 때문입니다.&lt;/p>
&lt;p>하지만 그 영향이 제로는 아닙니다.
리만 가설이 증명되는 과정에서 &lt;strong>&amp;lsquo;새로운 수학적 도구&amp;rsquo;나 &amp;lsquo;미지의 해석 기법&amp;rsquo;이 발견될 가능성&lt;/strong>이 매우 높기 때문입니다. 역사를 보더라도, 페르마의 마지막 정리나 푸앵카레 추측이 증명되었을 때 그 과정에서 개발된 새로운 이론이 수학 전체를 크게 도약시켰습니다.&lt;/p>
&lt;p>만약 리만 제타 함수의 영점의 성질을 완전히 조작할 수 있는 미지의 대수 기하학적 기법이나, 비가환 기하의 기법이 확립된다면, 그것이 결과적으로 소인수분해의 획기적인 알고리즘(예를 들어, 계산 복잡도를 다항 시간으로 줄이는 고전 알고리즘)의 발견으로 이어질 가능성은 부정할 수 없습니다. 그런 의미에서 암호학자들은 리만 가설의 동향에서 결코 눈을 뗄 수 없는 것입니다.&lt;/p>
&lt;h3 id="양자-컴퓨터와-쇼어의-알고리즘">양자 컴퓨터와 쇼어의 알고리즘
&lt;/h3>&lt;p>암호 기술에 있어 보다 직접적이고 현실적인 위협은 리만 가설의 증명이 아니라 &lt;strong>양자 컴퓨터&lt;/strong>입니다. 1994년 피터 쇼어(Peter Shor)가 발표한 &amp;lsquo;쇼어의 알고리즘&amp;rsquo;은 충분한 성능을 가진 양자 컴퓨터가 있다면 소인수분해를 다항 시간에 풀 수 있음을 증명했습니다. 이로 인해 RSA 암호나 타원곡선암호는 근본적으로 뚫리게 됩니다.&lt;/p>
&lt;p>현재 전 세계적으로 양자 컴퓨터로도 해독할 수 없는 &amp;lsquo;양자 내성 암호(Post-Quantum Cryptography, PQC)&amp;lsquo;로의 전환(격자 암호 등)이 추진되고 있습니다. 소수에 의존한 암호 기술은 어떤 의미에서 황금기를 끝내려 하고 있는지도 모르지만, 소수 그 자체의 수학적 가치가 사라지는 일은 영원히 없을 것입니다.&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>리만 가설은 수학이 가진 &amp;lsquo;추상적인 아름다움&amp;rsquo;과 &amp;lsquo;물리 세계·현실 사회에 대한 경이로운 적용력&amp;rsquo;을 동시에 상징하는 존재입니다.&lt;/p>
&lt;p>아직 아무도 정상에 도달하지 못한 이 거대한 수학의 산이 언젠가 정복될 때, 우리는 소수라는 우주의 진리를 완전히 이해함과 동시에 정보화 사회의 기반에 대해 새로운 시각을 갖게 될 것입니다. 암호 기술을 배우는 것은 그대로 인류 지혜의 역사를 따라가는 여정이기도 한 것입니다.&lt;/p></description></item><item><title>C++로 구현하는 고속 소수 판별 알고리즘 (Miller-Rabin 법 등)</title><link>http://kenji.blog/ko/p/cpp-fast-prime-testing-miller-rabin/</link><pubDate>Fri, 11 Sep 2026 14:00:00 +0900</pubDate><guid>http://kenji.blog/ko/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++로 구현하는 고속 소수 판별 알고리즘 (Miller-Rabin 법 등)" />&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>본 기사에서는 소박한 소수 판별 알고리즘부터 시작하여, 확률적 소수 판별법인 「페르마 테스트」, 그리고 그 약점을 극복한 실용상 최강 수준의 고속 알고리즘 「밀러-라빈 (Miller-Rabin) 소수 판별법」에 대해 수학적 배경부터 C++를 이용한 고도로 최적화된 구현까지 철저히 해설합니다. 특히, 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과 자기 자신 이외에 양의 약수를 가지지 않는 2 이상의 자연수입니다. 소수의 정의를 충실히 따르면, 어떤 정수 $N$ 이 소수인지 판별하려면 $2$ 부터 $N-1$ 까지의 모든 정수로 $N$ 을 나누어 보고, 한 번도 나누어떨어지지 않으면 소수, 한 번이라도 나누어떨어지면 합성수(소수가 아님)라고 판단할 수 있습니다.&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$ 와, 서로 소인($p$ 의 배수가 아닌) 임의의 정수 $a$ 에 대해 다음 합동식이 성립한다.
&lt;/p>
$$ a^{p-1} \equiv 1 \pmod p $$
&lt;/blockquote>
&lt;p>이 정리의 대우를 취하면, 「어떤 정수 $N$ 과, $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;p>※ 여기서는 오버플로우를 방지하기 위해 GCC/Clang의 확장인 &lt;code>__int128_t&lt;/code> (128비트 정수)를 사용하여 중간 곱을 유지하고 있습니다.&lt;/p>
&lt;h2 id="유사-소수와-카마이클-수-carmichael-numbers">유사 소수와 카마이클 수 (Carmichael Numbers)
&lt;/h2>&lt;p>페르마 테스트는 매우 강력하지만 치명적인 약점이 있습니다. 바로 $N$ 이 합성수임에도 불구하고, 모든 $a$ ($N$ 과 서로 소인 $a$)에 대해 $a^{N-1} \equiv 1 \pmod 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$ 이 성립한다.
※ C++의 모듈로 연산에서 $-1 \pmod N$ 은 $N-1$ 이 됩니다.&lt;/li>
&lt;/ol>
&lt;p>만약 $N$ 이 소수라면 임의의 $a$ 에 대해 이 조건이 반드시 만족됩니다. 반대로 $N$ 이 합성수라면 무작위로 $a$ 를 선택했을 때 이 조건을 만족할 확률(속을 확률)은 $\frac{1}{4}$ 이하라는 것이 수학적으로 증명되어 있습니다.
$k$ 번의 독립적인 테스트를 수행하면 오판 확률은 $\left(\frac{1}{4}\right)^k$ 이하가 되어 실용상 0으로 간주할 수 있습니다. 카마이클 수처럼 「절대적으로 속일 수 있는」 수는 존재하지 않습니다.&lt;/p>
&lt;h2 id="밀러-라빈-법의-알고리즘-흐름-mermaid-플로우차트">밀러-라빈 법의 알고리즘 흐름 (Mermaid 플로우차트)
&lt;/h2>&lt;p>아래 그림은 밀러-라빈 소수 판별법의 1회 테스트(하나의 밑 $a$ 에 대한 테스트)의 논리적인 흐름을 보여줍니다.&lt;/p>
&lt;div class="mermaid">graph TD
Start["테스트 시작 (입력: N, a)"] --> CalcDS["N-1 = d * 2^s 를 만족하는 d (홀수) 와 s 를 계산"]
CalcDS --> CalcX["x = a^d mod N 을 계산"]
CalcX --> CheckX1{"x == 1 또는 x == N-1 ?"}
CheckX1 -- "Yes" --> ReturnTrue["소수일 가능성이 있음 (Probably Prime)"]
CheckX1 -- "No" --> LoopStart["r = 1 부터 s-1 까지 루프 시작"]
LoopStart --> LoopCondition{"r &lt; s ?"}
LoopCondition -- "No" --> ReturnFalse["확실히 합성수 (Composite)"]
LoopCondition -- "Yes" --> SquareX["x = (x * x) mod N 을 계산"]
SquareX --> CheckXMinus1{"x == N - 1 ?"}
CheckXMinus1 -- "Yes" --> ReturnTrue
CheckXMinus1 -- "No" --> CheckXOne{"x == 1 ?"}
CheckXOne -- "Yes" --> ReturnFalse
CheckXOne -- "No" --> LoopNext["r 을 1 증가시키고 다음으로"]
LoopNext --> LoopCondition&lt;/div>
&lt;hr>
&lt;h1 id="4-64비트-정수에-대한-결정론적-판별">4. 64비트 정수에 대한 결정론적 판별
&lt;/h1>&lt;p>밀러-라빈 소수 판별법은 본래 「확률적」 알고리즘이지만, $N$ 의 상한이 고정되어 있는 경우 특정 여러 개의 $a$ (밑)를 모두 테스트함으로써 「100% 확실하게」 소수 판별을 수행할 수 있습니다.
이를 &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$ 의 값이 작을 경우에는 적은 수의 밑(base)만 테스트해도 되도록 최적화합니다.&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">// 128비트 정수에 의한 고속 (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="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">// 밀러-라빈 소수 판별법에 의한 64bit 정수의 결정론적 판별
&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">// N &amp;lt; 2^64 의 모든 수에 대해 결정론적이 되는 7개의 밑
&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 의 배수인 경우는 판별 불능이나 소수는 아님
&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})$ (루프 횟수 $\approx 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>)을 수행하고 있습니다. 현대의 CPU라 하더라도 정수 나눗셈(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/ko/p/rsa-encryption-math-explained-for-beginners/</link><pubDate>Fri, 11 Sep 2026 13:00:00 +0900</pubDate><guid>http://kenji.blog/ko/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>인터넷 사회의 안전을 근본적으로 지탱하고 있는 기술 중 하나가 &amp;lsquo;RSA 암호&amp;rsquo;입니다. 온라인 쇼핑에서의 신용카드 결제, 친구와의 SNS 대화, 회사의 기밀 정보 송수신 등 우리가 매일 무심코 사용하는 통신의 대부분은 이 RSA 암호나 그 후속 기술에 의해 보호받고 있습니다.&lt;/p>
&lt;p>하지만 &amp;lsquo;암호&amp;rsquo;라고 하면 스파이 영화에 나올 법한 복잡한 암호기나 일부 천재만이 이해할 수 있는 초고도 수학을 상상할지도 모릅니다. 현대 암호 이론이 고도화된 수학을 바탕으로 하는 것은 사실이지만, &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;lsquo;공통키 암호(대칭키 암호)&amp;lsquo;와 &amp;lsquo;공개키 암호&amp;rsquo; 두 가지로 나뉩니다.&lt;/p>
&lt;h3 id="11-공통키-암호-방식의-한계">1.1 공통키 암호 방식의 한계
&lt;/h3>&lt;p>예전부터 사용되어 온 암호의 대부분은 &amp;lsquo;공통키 암호 방식&amp;rsquo;이라고 불리는 것입니다. 이것은 &lt;strong>&amp;lsquo;암호화(메시지를 비밀 암호문으로 변환하는 것)&amp;lsquo;와 &amp;lsquo;복호화(암호문을 원래 메시지로 되돌리는 것)&amp;lsquo;에 같은 키를 사용하는&lt;/strong> 방식입니다.&lt;/p>
&lt;p>예를 들어, 앨리스가 밥에게 비밀 편지를 보낸다고 가정해 보겠습니다. 앨리스는 자물쇠(공통키)를 사용하여 상자에 편지를 넣고 잠급니다. 밥이 그 상자를 열기 위해서는 앨리스가 사용한 것과 똑같은 키를 가지고 있어야 합니다.&lt;/p>
&lt;p>이 방식에는 큰 문제가 있습니다. 바로 &amp;lsquo;키 분배 문제&amp;rsquo;입니다. 멀리 떨어져 있는 앨리스와 밥이 처음 통신할 경우, 어떻게 도청당하지 않고 키를 공유할 수 있을까요? 만약 키를 우편으로 보내는 도중에 제3자에게 도난당한다면, 그 이후의 암호 통신은 모두 내용이 노출되고 맙니다.&lt;/p>
&lt;h3 id="12-획기적인-발명-공개키-암호-방식">1.2 획기적인 발명 &amp;lsquo;공개키 암호 방식&amp;rsquo;
&lt;/h3>&lt;p>이 키 분배 문제를 해결하기 위해 고안된 것이 &amp;lsquo;공개키 암호 방식&amp;rsquo;입니다. RSA 암호도 이것의 일종입니다.&lt;/p>
&lt;p>공개키 암호 방식에서는 &lt;strong>&amp;lsquo;암호화하기 위한 키(공개키)&amp;lsquo;와 &amp;lsquo;복호화하기 위한 키(비밀키)&amp;lsquo;라는 서로 다른 두 개의 키&lt;/strong>를 사용합니다.&lt;/p>
&lt;ol>
&lt;li>수신자인 밥은 &amp;lsquo;공개키&amp;rsquo;와 &amp;lsquo;비밀키&amp;rsquo; 쌍을 생성합니다.&lt;/li>
&lt;li>밥은 &amp;lsquo;공개키&amp;rsquo;를 전 세계에 공개합니다 (누가 가져도 상관없습니다).&lt;/li>
&lt;li>송신자인 앨리스는 밥의 &amp;lsquo;공개키&amp;rsquo;를 사용하여 메시지를 암호화하고 전송합니다.&lt;/li>
&lt;li>암호화된 메시지는 밥만이 가지고 있는 &amp;lsquo;비밀키&amp;rsquo;로만 복호화할 수 있습니다.&lt;/li>
&lt;/ol>
&lt;p>이것을 자물쇠에 비유하자면, 밥은 &amp;lsquo;열려 있는 상태의 자물쇠(공개키)&amp;lsquo;를 많이 만들어서 전 세계에 뿌립니다. 앨리스는 밥에게 보낼 메시지를 상자에 넣고, 주운 밥의 자물쇠로 찰칵하고 잠급니다. 자물쇠는 한 번 닫히면 밥이 가지고 있는 &amp;lsquo;마스터키(비밀키)&amp;lsquo;로만 열 수 있습니다. 중간에 누군가 상자를 훔쳐도 마스터키가 없기 때문에 열 수 없는 것입니다.&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>이 획기적인 시스템을 구현하기 위해서는 &amp;lsquo;공개키로 쉽게 암호화할 수 있지만, 비밀키가 없으면 절대 복호화할 수 없다&amp;rsquo;는 일종의 **&amp;lsquo;일방향 함수(일방통행인 수학적 퍼즐)&amp;rsquo;**가 필요합니다. 그 퍼즐의 부품으로 주목받은 것이 바로 우리가 잘 아는 &amp;lsquo;소수&amp;rsquo;였습니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-rsa-암호를-지탱하는-수학적-기초-1-소수와-소인수분해">2. RSA 암호를 지탱하는 수학적 기초 1: 소수와 소인수분해
&lt;/h2>&lt;p>RSA 암호의 안전성은 **&amp;lsquo;거대한 수의 소인수분해는 매우 어렵다&amp;rsquo;**는 수학적 사실에 바탕을 두고 있습니다.&lt;/p>
&lt;h3 id="21-소수란">2.1 소수란
&lt;/h3>&lt;p>소수란 &amp;lsquo;1과 자기 자신으로밖에 나누어떨어지지 않는, 1보다 큰 자연수&amp;rsquo;를 말합니다.
예: $2, 3, 5, 7, 11, 13, 17, 19, 23...$&lt;/p>
&lt;p>소수는 모든 정수의 &amp;lsquo;원자&amp;rsquo;와 같은 것입니다. 어떤 자연수라도 소수들의 곱셈 형태로 분해할 수 있습니다. 이것을 &lt;strong>소인수분해&lt;/strong>라고 부릅니다. 예를 들어 $60 = 2^2 \times 3 \times 5$ 처럼, 순서를 무시하면 단 한 가지로 소인수분해할 수 있다는 것은 &amp;lsquo;산술의 기본 정리&amp;rsquo;로 알려져 있습니다.&lt;/p>
&lt;h3 id="22-소인수분해의-어려움-일방향-함수">2.2 소인수분해의 어려움 (일방향 함수)
&lt;/h3>&lt;p>여기서 중요한 것은 **&amp;lsquo;곱셈은 쉽지만 소인수분해는 어렵다&amp;rsquo;**는 비대칭성입니다.&lt;/p>
&lt;p>예를 들어, 다음 두 소수의 곱셈을 암산으로 계산해 보세요.
$11 \times 13 = ?$
이것은 쉽네요. 정답은 $143$입니다.&lt;/p>
&lt;p>그럼, 다음 수는 어떨까요?
$323$을 소인수분해 해 보세요.
어떠신가요? 시간이 조금 걸릴 것입니다. (정답은 $17 \times 19$입니다).&lt;/p>
&lt;p>수가 작으면 사람도 어떻게든 계산할 수 있지만, 수가 커지면 컴퓨터를 사용해도 계산이 폭발적으로 어려워집니다. 현재 주류인 RSA 암호에서는 2048비트(10진수로 약 600자리)라는 터무니없이 거대한 소수 $p$와 $q$를 곱한 수 $N = p \times q$를 사용합니다.&lt;/p>
&lt;p>거대한 두 소수 $p$와 $q$가 주어졌을 때, $N$을 계산하는 것은 컴퓨터에게는 순식간(밀리초 이하)입니다. 하지만 반대로 $N$만 주어지고 원래의 $p$와 $q$를 찾아내는 것은 현재 가장 빠른 슈퍼컴퓨터를 수조 년 돌려도 풀지 못할 정도로 오랜 시간이 걸립니다.&lt;/p>
&lt;p>이 **&amp;lsquo;계산의 비대칭성(한쪽 방향은 쉽지만 역방향은 어려움)&amp;rsquo;**이 공개키와 비밀키의 관계성을 만들어내는 토대가 됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="3-rsa-암호를-지탱하는-수학적-기초-2-합동식-모듈로-연산">3. RSA 암호를 지탱하는 수학적 기초 2: 합동식 (모듈로 연산)
&lt;/h2>&lt;p>RSA 암호의 계산은 우리가 평소 사용하는 무한히 숫자가 커지는 덧셈이나 곱셈이 아니라, 어떤 수로 나눈 &amp;lsquo;나머지&amp;rsquo;의 세계에서 이루어집니다. 이것을 **합동식(모듈로 연산)**이라고 부릅니다.&lt;/p>
&lt;h3 id="31-시계의-수학">3.1 시계의 수학
&lt;/h3>&lt;p>모듈로 연산은 종종 &amp;lsquo;시계의 수학&amp;rsquo;에 비유됩니다. 현재 10시라고 할 때, 그로부터 5시간 후는 몇 시일까요? $10 + 5 = 15$시이지만, 일반적인 12시간 시계에서는 &amp;lsquo;3시&amp;rsquo;라고 대답합니다. 이것은 15를 12로 나눈 나머지가 3이기 때문입니다.&lt;/p>
&lt;p>수학의 세계에서는 이것을 다음과 같이 표기합니다.
&lt;/p>
$$ 15 \equiv 3 \pmod{12} $$
&lt;p>
&amp;ldquo;15와 3은 12를 법으로 하여 합동이다(12로 나눈 나머지가 같다)&amp;ldquo;라고 읽습니다.&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;lsquo;거듭제곱&amp;rsquo;의 성질입니다. 이것은 **&amp;lsquo;나머지의 거듭제곱은 거듭제곱의 나머지와 같다&amp;rsquo;**는 것을 의미합니다.
예를 들어, $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;lsquo;페르마의 소정리&amp;rsquo;를 일반화한 &amp;lsquo;오일러의 정리&amp;rsquo;가 등장합니다.&lt;/p>
&lt;h3 id="41-오일러의-토션트-함수-phin">4.1 오일러의 토션트 함수 $\phi(N)$
&lt;/h3>&lt;p>오일러의 토션트 함수($\phi$ 함수)는 어떤 자연수 $N$에 대하여, **&amp;lsquo;1부터 $N$까지의 자연수 중 $N$과 서로소(최대공약수가 1)인 수의 개수&amp;rsquo;**를 반환하는 함수입니다.&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;/p>
&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>
이 성질이 RSA 암호의 &amp;lsquo;비밀 뒷문(트랩도어)&amp;rsquo; 역할을 합니다. $p$와 $q$를 아는 사람(키 생성자)은 $\phi(N)$을 순식간에 계산할 수 있지만, $N$밖에 모르는 제3자는 $N$을 소인수분해하지 않는 한 $\phi(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;quot;는 놀라운 성질입니다. ($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 암호의 **&amp;lsquo;암호화한 뒤 복호화하면 원래대로 돌아온다&amp;rsquo;**는 마법을 성립시키는 근본 원리입니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-rsa-암호-알고리즘-키-생성암호화복호화-단계">5. RSA 암호 알고리즘: 키 생성・암호화・복호화 단계
&lt;/h2>&lt;p>기초 지식이 갖춰졌으니, 이제 드디어 RSA 암호의 구체적인 절차를 살펴보겠습니다. RSA 암호는 크게 &amp;lsquo;1. 키 생성&amp;rsquo;, &amp;lsquo;2. 암호화&amp;rsquo;, &amp;lsquo;3. 복호화&amp;rsquo;의 세 단계로 나뉩니다.&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["φ(N)과 서로소인 e를 선택한다"]
A3 --> A5["e × d ≡ 1 (mod φ(N)) 이 되는 d를 계산한다"]
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;lsquo;공개키&amp;rsquo;와 &amp;lsquo;비밀키&amp;rsquo;를 생성합니다.&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> $1 &lt; e &lt; \phi(N)$ 이면서 $\phi(N)$과 서로소인 정수 $e$를 선택합니다.&lt;/li>
&lt;li>&lt;strong>비밀키 $d$ 계산:&lt;/strong> 다음 조건을 만족하는 정수 $d$를 찾습니다.
$$ e \times d \equiv 1 \pmod{\phi(N)} $$
이것은 즉 &amp;lsquo;$e \times d$를 $\phi(N)$으로 나눈 나머지가 $1$이 되는 수 $d$&amp;lsquo;를 뜻합니다.&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;lsquo;메시지 $M$을 $e$제곱하고, $N$으로 나눈 나머지 $C$&amp;lsquo;를 계산합니다. 이 $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;lsquo;암호문 $C$를 $d$제곱하고, $N$으로 나눈 나머지&amp;rsquo;를 계산하면, 놀랍게도 원래의 메시지 $M$이 복원되는 것입니다!&lt;/p>
&lt;hr>
&lt;h2 id="6-왜-복호화하면-원래대로-돌아가는가-수학적-증명">6. 왜 복호화하면 원래대로 돌아가는가? (수학적 증명)
&lt;/h2>&lt;p>&amp;ldquo;$C$를 $d$제곱하는 것만으로 어떻게 원래의 $M$으로 돌아가지?&amp;ldquo;라고 의문이 들 수 있습니다. 여기서 앞서 다룬 &amp;lsquo;오일러의 정리&amp;rsquo;가 위력을 발휘합니다.&lt;/p>
&lt;p>복호화 계산식 $C^d \pmod N$ 에 암호화 식 $C = M^e$ 를 대입해 보겠습니다.
&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;lsquo;$ed$는 $\phi(N)$의 배수에 $1$을 더한 수이다&amp;rsquo;라는 것을 의미합니다. 정수 $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$를 모르기 때문에, $C$에서 $M$을 꺼낼 수 있는 것은 $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>$\phi(N)=120$ 과 서로소인 공개키 $e$를 선택합니다. 여기서는 $e=7$ 로 하겠습니다.&lt;/li>
&lt;li>비밀키 $d$를 구합니다. $7 \times d \equiv 1 \pmod{120}$ 이 되는 $d$를 찾습니다.
방정식 $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;lsquo;&lt;strong>9&lt;/strong>&amp;lsquo;가 됩니다! 원래 메시지를 무사히 받을 수 있었습니다.&lt;/p>
&lt;hr>
&lt;h2 id="8-비밀키-d를-구하는-방법-확장-유클리드-호제법">8. 비밀키 $d$를 구하는 방법: 확장 유클리드 호제법
&lt;/h2>&lt;p>수작업 계산 예제에서는 감으로 $k$를 찾아 $d=103$ 을 발견했지만, 수가 수백 자리가 되면 이 방법은 불가능합니다. 실제 프로그램에서는 **&amp;lsquo;확장 유클리드 호제법&amp;rsquo;**이라는 알고리즘을 사용합니다.&lt;/p>
&lt;p>$7d \equiv 1 \pmod{120}$ 을 푼다는 것은 $7d + 120y = 1$ 을 만족하는 정수 $d, y$ 를 찾는 것과 같습니다. 유클리드 호제법을 역산해 나감으로써 이를 기계적으로 구할 수 있습니다.&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>$-17$ 은 법 $120$의 세계에서는 $120 - 17 = 103$ 과 같은 의미가 됩니다. 따라서 $d = 103$ 을 순식간에 구할 수 있습니다. 이 방법은 아무리 큰 수라도 매우 빠르게 계산할 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="9-rsa-암호의-또-다른-얼굴-디지털-서명">9. RSA 암호의 또 다른 얼굴: 디지털 서명
&lt;/h2>&lt;p>RSA 암호의 훌륭한 점은 공개키와 비밀키의 역할을 반대로 하여 **&amp;lsquo;디지털 서명&amp;rsquo;**으로도 사용할 수 있다는 것입니다.&lt;/p>
&lt;p>암호화할 때는 &amp;lsquo;공개키로 암호화 $\Rightarrow$ 비밀키로 복호화&amp;rsquo;였지만,
디지털 서명에서는 &amp;lsquo;비밀키로 암호화 $\Rightarrow$ 공개키로 복호화&amp;rsquo;라는 절차를 밟습니다.&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;lsquo;앨리스의 비밀키로만 만들 수 있는 데이터이다&amp;rsquo;라는 것과 &amp;lsquo;메시지가 중간에 위조되지 않았다&amp;rsquo;는 것이 동시에 증명되는 것입니다.&lt;/p>
&lt;hr>
&lt;h2 id="10-프로그램으로-체감하는-rsa-암호">10. 프로그램으로 체감하는 RSA 암호
&lt;/h2>&lt;p>수작업으로는 힘든 거듭제곱 계산도 파이썬을 사용하면 매우 쉽게 구현할 수 있습니다. 다음은 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> 함수는 내부적으로 &amp;lsquo;반복 제곱법(거듭제곱법)&amp;lsquo;이라는 고속 알고리즘을 사용하기 때문에 수백 자리의 숫자라도 순식간에 계산이 끝납니다.&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$ 은 쉽지만, $N$에서 $p, q$를 찾는 것은 매우 어렵다.&lt;/li>
&lt;li>&lt;strong>합동식과 오일러의 정리:&lt;/strong> $a^{\phi(N)} \equiv 1 \pmod N$ 이라는 법칙에 의해, &amp;lsquo;어떤 수로 거듭제곱하면 원래대로 돌아가는&amp;rsquo; 마법의 트랩도어가 완성된다.&lt;/li>
&lt;li>&lt;strong>공개키와 비밀키:&lt;/strong> 누구나 암호화할 수 있지만, 복호화할 수 있는 것은 정당한 수신자뿐이다.&lt;/li>
&lt;/ol>
&lt;p>현재 사용되는 RSA 암호의 $N$은 600자리 이상이며, 전 세계의 슈퍼컴퓨터를 총동원해도 소인수분해에는 우주의 나이 이상의 시간이 걸립니다. 하지만 최근 연구가 진행되고 있는 &amp;lsquo;양자 컴퓨터&amp;rsquo;가 장래에 실용화되면 &amp;lsquo;쇼어의 알고리즘(Shor&amp;rsquo;s algorithm)&amp;lsquo;에 의해 이 소인수분해가 순식간에 풀릴 가능성이 있습니다. 따라서 현재는 양자 컴퓨터로도 해독할 수 없는 &amp;lsquo;양자 내성 암호(Post-Quantum Cryptography)&amp;rsquo; 개발이 전 세계적으로 빠른 속도로 진행되고 있습니다.&lt;/p>
&lt;p>&amp;lsquo;쓸모없다&amp;rsquo;고 여겨지기 쉬운 고도화된 수학이 사실은 우리의 일상생활을 근본적으로 지켜주고 있습니다. RSA 암호는 그런 수학의 깊이와 아름다움을 가르쳐 주는 최고의 교재입니다. 이 글을 통해 암호와 수학의 재미를 조금이라도 느끼셨기를 바랍니다.&lt;/p></description></item></channel></rss>