<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Mathematics on kenji.blog</title><link>http://kenji.blog/ko/categories/mathematics/</link><description>Recent content in Mathematics on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ko</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 22:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ko/categories/mathematics/index.xml" rel="self" type="application/rss+xml"/><item><title>페르마의 소정리를 활용한 암호화의 기초와 구현</title><link>http://kenji.blog/ko/p/fermats-little-theorem-cryptography-implementation/</link><pubDate>Fri, 11 Sep 2026 22:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/fermats-little-theorem-cryptography-implementation/</guid><description>&lt;img src="http://kenji.blog/p/fermats-little-theorem-cryptography-implementation/img/eyecatch.jpg" alt="Featured image of post 페르마의 소정리를 활용한 암호화의 기초와 구현" />&lt;h2 id="1-시작하며-현대-암호를-지탱하는-수학의-신비">1. 시작하며: 현대 암호를 지탱하는 수학의 신비
&lt;/h2>&lt;p>현대 디지털 사회, 특히 인터넷을 통한 통신에서 &amp;lsquo;암호화&amp;rsquo;는 필수 불가결한 기반 기술이 되었습니다. 우리가 웹 브라우저에서 HTTPS를 통해 안전하게 웹사이트를 탐색하고, 온라인 뱅킹으로 금융 거래를 하며, 메시징 앱으로 사적인 대화를 나눌 수 있는 것은 고도의 수학적 이론이 뒷받침된 암호 프로토콜이 배후에서 작동하고 있기 때문입니다. 그중에서도 특히 중요한 역할을 담당하고 있는 것이 &amp;lsquo;공개키 암호 방식&amp;rsquo;이며, 그 대표적인 예가 &lt;strong>RSA 암호&lt;/strong>입니다.&lt;/p>
&lt;p>RSA 암호를 비롯한 많은 암호 알고리즘의 안전성과 정당성은 17세기 프랑스의 수학자 피에르 드 페르마(Pierre de Fermat)가 발견한 매우 아름답고 강력한 정리에 크게 의존하고 있습니다. 그것이 바로 **페르마의 소정리(Fermat&amp;rsquo;s Little Theorem)**입니다. 더 나아가 이를 일반화한 레온하르트 오일러(Leonhard Euler)의 정리 역시 암호 이론에서 결정적인 역할을 하고 있습니다.&lt;/p>
&lt;p>본 기사에서는 페르마의 소정리라는 순수 수학의 발견이 어떻게 현대의 실용적인 암호 기술, 특히 &amp;lsquo;소수 판별&amp;rsquo;과 &amp;lsquo;RSA 암호&amp;rsquo;에 응용되고 있는지를 기초부터 철저하게 해설합니다. 수학적인 증명, 암호화 및 복호화의 메커니즘, 그리고 C++와 Python을 활용한 구체적인 알고리즘 구현까지 모두 다루는 매우 상세한 기술 가이드가 될 것입니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-합동식과-모듈러-연산의-기초">2. 합동식과 모듈러 연산의 기초
&lt;/h2>&lt;p>페르마의 소정리를 이해하기 위해서는 먼저 &amp;lsquo;모듈러 연산(합동식)&amp;lsquo;이라는 수학 개념에 친숙해질 필요가 있습니다. 모듈러 연산이란 어떤 정해진 수(법, 모듈러스라고 부름)로 나눈 &amp;lsquo;나머지&amp;rsquo;에 주목한 계산 체계를 말합니다. 시계의 문자판(12시간에 1바퀴를 돎)과 같은 계산이기 때문에 &amp;lsquo;시계 산술&amp;rsquo;이라고도 불립니다.&lt;/p>
&lt;p>정수 $a$와 $b$를 양의 정수 $n$으로 나눈 나머지가 같을 때, 수학적으로는 다음과 같이 기술합니다.&lt;/p>
$$
a \equiv b \pmod n
$$
&lt;p>이것은 &amp;lsquo;$n$을 법으로 하여 $a$와 $b$는 합동이다&amp;rsquo;라고 읽습니다. 예를 들어, 17을 5로 나눈 나머지는 2이고, 12를 5로 나눈 나머지 역시 2입니다. 따라서 다음과 같이 쓸 수 있습니다.&lt;/p>
$$
17 \equiv 12 \pmod 5 \equiv 2 \pmod 5
$$
&lt;p>모듈러 연산에서는 일반적인 사칙연산(덧셈, 뺄셈, 곱셈)이 그대로 성립합니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>덧셈&lt;/strong>: $a \equiv b \pmod n$ 이고 $c \equiv d \pmod n$ 이면, $a + c \equiv b + d \pmod n$&lt;/li>
&lt;li>&lt;strong>뺄셈&lt;/strong>: $a \equiv b \pmod n$ 이고 $c \equiv d \pmod n$ 이면, $a - c \equiv b - d \pmod n$&lt;/li>
&lt;li>&lt;strong>곱셈&lt;/strong>: $a \equiv b \pmod n$ 이고 $c \equiv d \pmod n$ 이면, $a \times c \equiv b \times d \pmod n$&lt;/li>
&lt;li>&lt;strong>거듭제곱&lt;/strong>: $a \equiv b \pmod n$ 이면, 임의의 자연수 $k$에 대해 $a^k \equiv b^k \pmod n$&lt;/li>
&lt;/ol>
&lt;p>단, &lt;strong>나눗셈&lt;/strong>에 대해서는 주의가 필요합니다. 일반적으로 $a \times c \equiv b \times c \pmod n$ 이라고 해서 양변을 $c$로 나누어 $a \equiv b \pmod n$ 으로 만들 수는 없습니다. 이것이 성립하는 것은 $c$와 $n$이 서로소(최대공약수가 1)인 경우에 한합니다. 이 &amp;lsquo;모듈러 역원&amp;rsquo;의 개념은 후술할 RSA 암호의 키 생성에서 극히 중요해집니다.&lt;/p>
&lt;hr>
&lt;h2 id="3-페르마의-소정리의-수학적-배경과-증명">3. 페르마의 소정리의 수학적 배경과 증명
&lt;/h2>&lt;p>모듈러 연산의 기초를 다졌으니, 본 주제인 페르마의 소정리에 대해 살펴보겠습니다.&lt;/p>
&lt;h3 id="31-정리의-정의">3.1 정리의 정의
&lt;/h3>&lt;p>페르마의 소정리는 다음과 같이 정식화됩니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>페르마의 소정리 (Fermat&amp;rsquo;s Little Theorem)&lt;/strong>
$p$를 소수라 하고, $a$를 $p$의 배수가 아닌(즉 $a$와 $p$는 서로소인) 임의의 정수라고 하자. 이때 다음의 합동식이 성립한다.
&lt;/p>
$$ a^{p-1} \equiv 1 \pmod p $$
&lt;/blockquote>
&lt;p>또한, 조건 &amp;lsquo;$a$가 $p$의 배수가 아닐 것&amp;rsquo;을 제외하고, 모든 정수 $a$에 대해 성립하는 형태로 표현하는 것도 일반적입니다. 그 경우는 양변에 $a$를 곱하여 다음과 같이 됩니다.&lt;/p>
$$
a^p \equiv a \pmod p
$$
&lt;h3 id="32-구체적인-예를-통한-확인">3.2 구체적인 예를 통한 확인
&lt;/h3>&lt;p>정리가 정말로 성립하는지 구체적인 숫자를 사용하여 확인해 봅시다.
소수 $p = 5$라고 합시다. $p-1 = 4$입니다. $a$로서 $p$의 배수가 아닌 정수를 선택합니다.&lt;/p>
&lt;ul>
&lt;li>$a = 2$인 경우: $2^{5-1} = 2^4 = 16$. $16 \div 5 = 3$ 나머지 $1$. 따라서 $16 \equiv 1 \pmod 5$. (성립)&lt;/li>
&lt;li>$a = 3$인 경우: $3^{5-1} = 3^4 = 81$. $81 \div 5 = 16$ 나머지 $1$. 따라서 $81 \equiv 1 \pmod 5$. (성립)&lt;/li>
&lt;li>$a = 4$인 경우: $4^{5-1} = 4^4 = 256$. $256 \div 5 = 51$ 나머지 $1$. 따라서 $256 \equiv 1 \pmod 5$. (성립)&lt;/li>
&lt;/ul>
&lt;p>이처럼 어떤 $a$를 선택하든(5의 배수만 아니라면) 4제곱하여 5로 나눈 나머지는 항상 1이 됩니다. 마법처럼 보이지만, 이는 소수가 가지는 아름다운 성질에서 유래한 것입니다.&lt;/p>
&lt;h3 id="33-정리의-수학적-증명">3.3 정리의 수학적 증명
&lt;/h3>&lt;p>어째서 이런 일이 성립하는 것일까요. 여기서는 잉여류의 집합을 이용한 우아한 증명을 소개합니다.&lt;/p>
&lt;p>집합 $S = \{1, 2, 3, \dots, p-1\}$을 생각합니다. 이들은 $p$로 나눈 나머지가 $1$부터 $p-1$이 되는 정수들의 대표원입니다.
여기서 각 원소에 $p$와 서로소인 정수 $a$를 곱한 새로운 집합 $T$를 생각합니다.
&lt;/p>
$$ T = \{1a, 2a, 3a, \dots, (p-1)a\} $$
&lt;p>이 집합 $T$의 각 원소를 $p$로 나눈 나머지를 생각해 봅니다. 놀랍게도 이 나머지들은 순서는 바뀔지 몰라도 원래 집합 $S$의 원소들의 집합과 완전히 일치합니다.
왜냐하면:&lt;/p>
&lt;ol>
&lt;li>$T$의 원소가 $p$의 배수가 되는 일은 없습니다($a$도 원래의 원소도 $p$의 배수가 아니기 때문).&lt;/li>
&lt;li>$T$ 안에서 $p$를 법으로 하여 합동이 되는 서로 다른 두 원소는 존재하지 않습니다. 만약 $ia \equiv ja \pmod p$ ($i \neq j$)라고 한다면, $a$와 $p$는 서로소이므로 $a$로 나눌 수 있고, $i \equiv j \pmod p$가 되어 모순되기 때문입니다.&lt;/li>
&lt;/ol>
&lt;p>따라서 $S$의 원소를 모두 곱한 것과 $T$의 원소를 모두 곱한 것은 $p$를 법으로 하여 합동이 됩니다.&lt;/p>
$$
(1a) \times (2a) \times \dots \times ((p-1)a) \equiv 1 \times 2 \times \dots \times (p-1) \pmod p
$$
&lt;p>좌변을 정리하면 $a$가 $p-1$개 있으므로,&lt;/p>
$$
a^{p-1} \cdot (p-1)! \equiv (p-1)! \pmod p
$$
&lt;p>$(p-1)!$은 $p$와 서로소이므로 양변을 $(p-1)!$로 나눌 수 있으며, 최종적으로 다음의 정리가 도출됩니다.&lt;/p>
$$
a^{p-1} \equiv 1 \pmod p
$$
&lt;p>이것이 페르마의 소정리의 증명입니다.&lt;/p>
&lt;hr>
&lt;h2 id="4-오일러의-피-함수와-오일러의-정리">4. 오일러의 피 함수와 오일러의 정리
&lt;/h2>&lt;p>페르마의 소정리는 &amp;lsquo;소수 $p$&amp;lsquo;에 관한 정리지만, 이를 &amp;lsquo;임의의 양의 정수 $n$&amp;lsquo;으로 일반화한 사람이 레온하르트 오일러입니다. RSA 암호를 이해하려면 이 확장이 필수적입니다.&lt;/p>
&lt;h3 id="41-오일러의-피토션트-함수-phin">4.1 오일러의 피(토션트) 함수 $\phi(n)$
&lt;/h3>&lt;p>오일러의 피 함수(또는 오일러의 $\phi$ 함수) $\phi(n)$은 &amp;lsquo;$1$부터 $n$까지의 정수 중 $n$과 서로소인 것의 개수&amp;rsquo;를 나타내는 함수입니다.&lt;/p>
&lt;ul>
&lt;li>소수 $p$의 경우, $1$부터 $p-1$까지의 모든 정수가 $p$와 서로소이므로 $\phi(p) = p - 1$이 됩니다.&lt;/li>
&lt;li>서로 다른 두 소수 $p, q$에 대해, 그 곱인 $n = p \times q$의 경우 $\phi(n)$은 매우 간단한 식으로 구해집니다.
$$ \phi(p \times q) = \phi(p) \times \phi(q) = (p - 1)(q - 1) $$&lt;/li>
&lt;/ul>
&lt;p>이 성질이 RSA 암호의 키 생성에 있어 근간이 되는 로직이 됩니다.&lt;/p>
&lt;h3 id="42-오일러의-정리">4.2 오일러의 정리
&lt;/h3>&lt;p>오일러는 페르마의 소정리를 다음과 같이 일반화했습니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>오일러의 정리 (Euler&amp;rsquo;s Theorem)&lt;/strong>
양의 정수 $n$과, 그 수와 서로소인 정수 $a$에 대하여 다음이 성립한다.
&lt;/p>
$$ a^{\phi(n)} \equiv 1 \pmod n $$
&lt;/blockquote>
&lt;p>만약 $n$이 소수 $p$라면 $\phi(p) = p - 1$이므로, 이것은 페르마의 소정리 자체($a^{p-1} \equiv 1 \pmod p$)가 됩니다. 즉, 페르마의 소정리는 오일러의 정리의 특수한 경우에 불과합니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-거대한-소수-찾기-페르마의-소수-판별법">5. 거대한 소수 찾기: 페르마의 소수 판별법
&lt;/h2>&lt;p>암호 기술(RSA 암호나 Diffie-Hellman 키 교환 등)에서는 수백 자리에 달하는 &amp;lsquo;거대한 소수&amp;rsquo;를 고속으로 찾아낼 필요가 있습니다. 하지만 거대한 수 $N$이 소수인지 판별하기 위해 $2$부터 $\sqrt{N}$까지의 모든 수로 나누어 떨어지는지 시험하는 &amp;lsquo;시도 나눗셈법&amp;rsquo;으로는 우주의 수명만큼의 시간이 걸려 버립니다.&lt;/p>
&lt;p>그래서 등장하는 것이 페르마의 소정리를 역으로 이용한 &amp;lsquo;확률적 소수 판별법&amp;rsquo;인 **페르마 테스트(Fermat Primality Test)**입니다.&lt;/p>
&lt;h3 id="51-확률적-소수-판별법이란">5.1 확률적 소수 판별법이란
&lt;/h3>&lt;p>페르마의 소정리에 의하면, $p$가 소수라면 임의의 $a$ ($1 &lt; a &lt; p$)에 대해 $a^{p-1} \equiv 1 \pmod p$ 가 반드시 성립합니다.
이의 대우를 취하면, &amp;lsquo;어떤 $a$에 대해 $a^{p-1} \not\equiv 1 \pmod p$ 가 될 경우, $p$는 &lt;strong>절대로 소수가 아니다(합성수이다)&lt;/strong>&amp;lsquo;라는 것을 말할 수 있습니다.&lt;/p>
&lt;p>따라서 $N$이 소수인지 판별하고 싶은 경우, 무작위로 몇 개의 $a$를 선택하여 $a^{N-1} \pmod N$을 계산해서 $1$이 되는지를 확인합니다. 만약 한 번이라도 $1$이 아닌 답이 나오면 $N$은 합성수임이 확정됩니다. 몇 번을 시험해도 $1$이 되는 경우 $N$은 &amp;lsquo;아마도 소수일 것이다&amp;rsquo;라고 높은 확률로 판단할 수 있습니다.&lt;/p>
&lt;h3 id="52-알고리즘의-해설과-플로우차트">5.2 알고리즘의 해설과 플로우차트
&lt;/h3>&lt;p>페르마 테스트의 알고리즘은 다음과 같습니다.&lt;/p>
&lt;div class="mermaid">flowchart TD
Start["시작"] --> Input["판별 대상의 수 p 와, 테스트 횟수 k 를 입력"]
Input --> LoopStart["i = 0 부터 k-1 까지 루프"]
LoopStart --> Condition{"i &lt; k ?"}
Condition -- "예" --> RandomA["1 &lt; a &lt; p-1 의 범위에서 무작위 정수 a 를 선택"]
RandomA --> Calc["모듈러 거듭제곱 a^(p-1) mod p 를 계산"]
Calc --> CheckPrime{"결과는 1 인가?"}
CheckPrime -- "아니요" --> ReturnComposite["p 는 합성수이다 (확정)"]
CheckPrime -- "예" --> Increment["i 를 증가시킴"]
Increment --> Condition
Condition -- "아니요" --> ReturnPrime["p 는 아마도 소수일 것이다 (확률적)"]
ReturnComposite --> End["종료"]
ReturnPrime --> End&lt;/div>
&lt;h3 id="53-카마이클-수위소수의-함정">5.3 카마이클 수(위소수)의 함정
&lt;/h3>&lt;p>페르마 테스트는 매우 빠르지만 중대한 단점이 있습니다. 그것은 합성수임에도 불구하고 모든 $a$에 대해 $a^{N-1} \equiv 1 \pmod N$을 만족시켜 버리는 악마와 같은 수가 존재한다는 것입니다. 이를 **카마이클 수(Carmichael numbers)**라고 부릅니다. 가장 작은 카마이클 수는 $561$ ($3 \times 11 \times 17$)입니다.&lt;/p>
&lt;p>카마이클 수가 존재하기 때문에 순수한 페르마 테스트만으로는 절대적인 소수 판별을 할 수 없습니다. 그러므로 실제 암호화 시스템(OpenSSL 등)에서는 페르마 테스트를 개량한 &lt;strong>밀러-라빈(Miller-Rabin) 소수 판별법&lt;/strong>이 표준적으로 사용되고 있습니다. 밀러-라빈 판별법은 카마이클 수를 꿰뚫어 볼 수 있어 오판 확률을 실질적으로 제로에 가깝게 만들 수 있습니다.&lt;/p>
&lt;h3 id="54-고속-거듭제곱-나머지-연산분할-정복-거듭제곱">5.4 고속 거듭제곱 나머지 연산(분할 정복 거듭제곱)
&lt;/h3>&lt;p>소수 판별 알고리즘 내에서 $a^{N-1} \pmod N$을 계산해야 하지만, $N$이 거대한 경우 $a^{N-1}$은 천문학적인 자릿수가 되어 컴퓨터 메모리에 다 들어가지 않습니다.
이를 해결하는 것이 &lt;strong>분할 정복 거듭제곱(Exponentiation by Squaring)&lt;/strong> 또는 모듈러 거듭제곱 연산입니다. 계산의 각 단계마다 나머지(mod N)를 취함으로써 항상 값을 $N$보다 작게 유지하며 매우 고속($O(\log N)$의 시간 복잡도)으로 계산 가능하게 됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-소수-판별과-거듭제곱-나머지-구현">6. 소수 판별과 거듭제곱 나머지 구현
&lt;/h2>&lt;p>그러면 페르마의 소수 판별법과 분할 정복 거듭제곱을 C++와 Python으로 구현해 보겠습니다.&lt;/p>
&lt;h3 id="61-c를-이용한-구현">6.1 C++를 이용한 구현
&lt;/h3>&lt;p>C++에서는 표준 정수형이 오버플로우되기 쉽기 때문에 거대한 수를 다루려면 다정밀도 정수 라이브러리(GMP 등)가 필요하지만, 여기서는 알고리즘을 이해하기 위해 64비트 정수(&lt;code>unsigned long long&lt;/code>) 범위 내에서의 구현을 보여줍니다.&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;/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;random&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) - 분할 정복 거듭제곱
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="nf">power_mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&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">unsigned&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">unsigned&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">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">result&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">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="c1">// b의 최하위 비트가 1인 경우, 결과에 a를 곱함
&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">b&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">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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">__int128&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="n">result&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 class="c1">// 오버플로우 방지를 위해 128비트 확장
&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 class="c1">// a를 제곱함
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&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">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="c1">// b를 오른쪽 시프트 (절반으로 만듦)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">/=&lt;/span> &lt;span class="mi">2&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">result&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="kt">bool&lt;/span> &lt;span class="nf">fermat_is_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">iterations&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">5&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">p&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">p&lt;/span> &lt;span class="o">&amp;lt;=&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">p&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="n">random_device&lt;/span> &lt;span class="n">rd&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">mt19937_64&lt;/span> &lt;span class="n">gen&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rd&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">uniform_int_distribution&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">dis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&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">2&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">int&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="n">i&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="o">++&lt;/span>&lt;span class="n">i&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">unsigned&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="o">=&lt;/span> &lt;span class="n">dis&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">gen&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// a^(p-1) mod p 가 1 이 아니면 합성수
&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">power_mod&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">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">p&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="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">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="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 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="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="kt">unsigned&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">=&lt;/span> &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="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">fermat_is_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&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">num&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; is probably prime.&amp;#34;&lt;/span> &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 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="n">cout&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">&amp;lt;&amp;lt;&lt;/span> &lt;span class="s">&amp;#34; is composite.&amp;#34;&lt;/span> &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 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;h3 id="62-python을-이용한-구현">6.2 Python을 이용한 구현
&lt;/h3>&lt;p>Python의 표준 정수형은 다정밀도 정수를 지원하므로 자릿수 넘침을 걱정할 필요가 없습니다. 게다가 Python의 내장 함수 &lt;code>pow(a, b, m)&lt;/code>는 내부적으로 분할 정복 거듭제곱을 사용하기 때문에 매우 빠릅니다.&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;/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="kn">import&lt;/span> &lt;span class="nn">random&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">fermat_is_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">iterations&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">5&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;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> 페르마의 소수 판별법을 이용한 확률적 소수 판별
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">p&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mi">3&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="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">p&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&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="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">iterations&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2 에서 p-2 사이의 무작위 수 a 를 고름&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">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">randint&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&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">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># a^(p-1) mod p 를 계산. 내장 pow 는 고속.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">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">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">p&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="kc">False&lt;/span> &lt;span class="c1"># 합성수 확정&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="kc">True&lt;/span> &lt;span class="c1"># 아마도 소수&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="n">number_to_test&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">104729&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="n">fermat_is_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">number_to_test&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">10&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">number_to_test&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 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="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">number_to_test&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;hr>
&lt;h2 id="7-rsa-암호에의-응용-페르마와-오일러가-결실을-맺는-곳">7. RSA 암호에의 응용: 페르마와 오일러가 결실을 맺는 곳
&lt;/h2>&lt;p>페르마의 소정리(및 오일러의 정리)의 가장 위대한 응용처가 1977년에 Rivest, Shamir, Adleman 세 사람에 의해 개발된 &lt;strong>RSA 암호&lt;/strong>입니다.
RSA 암호는 &amp;lsquo;공개키 암호&amp;rsquo;라는 획기적인 시스템으로, 암호화하기 위한 키(공개키)는 전 세계에 공개해 두면서, 복호화하기 위한 키(비밀키)는 수신자 본인만이 알고 있는 구조를 구현하고 있습니다.&lt;/p>
&lt;p>이 비대칭성은 &amp;lsquo;거대한 합성수의 소인수분해는 지극히 어렵다&amp;rsquo;라는 계산 복잡도 기반 안전성에 근거하고 있습니다.&lt;/p>
&lt;h3 id="71-rsa-암호의-구조-키-생성-암호화-복호화">7.1 RSA 암호의 구조 (키 생성, 암호화, 복호화)
&lt;/h3>&lt;p>RSA 암호의 전체적인 통신 흐름을 Mermaid 시퀀스 다이어그램으로 확인해 봅시다.&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant Alice["앨리스 (수신자)"]
participant Bob["밥 (송신자)"]
Alice->>Alice: "거대한 소수 p, q 를 생성"
Alice->>Alice: "N = p * q, φ(N) = (p-1)(q-1) 을 계산"
Alice->>Alice: "공개키 e 와 비밀키 d 를 계산 (e*d ≡ 1 mod φ(N))"
Alice->>Bob: "공개키 (N, e) 를 전송"
Note over Bob: "평문 M 을 준비 (M &lt; N)"
Bob->>Bob: "암호문 C = M^e mod N 을 계산"
Bob->>Alice: "암호문 C 를 전송"
Alice->>Alice: "평문 M = C^d mod N 을 계산하여 복호화"&lt;/div>
&lt;p>아래에 수학적인 세부 단계를 해설합니다.&lt;/p>
&lt;h4 id="단계-1-키-생성-수신자-앨리스의-작업">단계 1: 키 생성 (수신자 앨리스의 작업)
&lt;/h4>&lt;ol>
&lt;li>두 개의 거대한 소수 $p$와 $q$를 무작위로 생성합니다(여기서 앞서 말한 소수 판별법이 사용됩니다).&lt;/li>
&lt;li>그 곱인 $N = p \times q$를 계산합니다. 이 $N$은 공개됩니다.&lt;/li>
&lt;li>오일러의 피 함수를 사용하여 $\phi(N) = (p-1)(q-1)$을 계산합니다.&lt;/li>
&lt;li>$\phi(N)$과 서로소인 정수 $e$(공개 지수)를 선택합니다(종종 $e = 65537$이 사용됩니다).&lt;/li>
&lt;li>$e$의 모듈러 역원 $d$(비밀 지수)를 계산합니다. 즉, 다음을 만족하는 $d$를 찾습니다.
$$ e \cdot d \equiv 1 \pmod{\phi(N)} $$
이 계산에는 &lt;strong>확장 유클리드 호제법&lt;/strong>이 사용됩니다.&lt;/li>
&lt;/ol>
&lt;p>이것으로 &lt;strong>공개키는 $(N, e)$&lt;/strong>, **비밀키는 $(N, d)$**가 됩니다. ($p, q, \phi(N)$은 즉시 파기하거나 엄중히 숨깁니다).&lt;/p>
&lt;h4 id="단계-2-암호화-송신자-밥의-작업">단계 2: 암호화 (송신자 밥의 작업)
&lt;/h4>&lt;p>밥은 앨리스에게 메시지 $M$을 보내고 싶다고 합시다($M$은 문자를 수치화한 것으로 $0 \le M &lt; N$입니다).
밥은 앨리스의 공개키 $(N, e)$를 사용하여 다음 계산을 수행해 암호문 $C$를 만듭니다.&lt;/p>
$$
C \equiv M^e \pmod N
$$
&lt;p>이 $C$를 네트워크를 통해 앨리스에게 송신합니다.&lt;/p>
&lt;h4 id="단계-3-복호화-수신자-앨리스의-작업">단계 3: 복호화 (수신자 앨리스의 작업)
&lt;/h4>&lt;p>암호문 $C$를 받은 앨리스는 자신만이 알고 있는 비밀키 $d$를 사용하여 다음 계산을 수행합니다.&lt;/p>
$$
M' \equiv C^d \pmod N
$$
&lt;p>놀랍게도 이 계산 결과 $M'$은 원래의 메시지 $M$과 완전히 일치합니다.&lt;/p>
&lt;h3 id="72-왜-복호화가-가능한가-수학적-증명">7.2 왜 복호화가 가능한가? (수학적 증명)
&lt;/h3>&lt;p>여기서 페르마의 소정리(오일러의 정리)가 진가를 발휘합니다. 어째서 $C^d \pmod N$이 $M$으로 되돌아가는 것일까요?&lt;/p>
&lt;p>복호화 식을 전개해 봅니다.
$C \equiv M^e \pmod N$ 이므로,
&lt;/p>
$$ C^d \equiv (M^e)^d \equiv M^{ed} \pmod N $$
&lt;p>키 생성 단계에서 $e \cdot d \equiv 1 \pmod{\phi(N)}$ 이 되도록 $d$를 선택했습니다. 이것은 어떤 정수 $k$가 존재하여 다음과 같이 쓸 수 있음을 의미합니다.
&lt;/p>
$$ e \cdot d = 1 + k \cdot \phi(N) $$
&lt;p>이것을 위의 식에 대입합니다.
&lt;/p>
$$ M^{ed} = M^{1 + k \cdot \phi(N)} = M \cdot M^{k \cdot \phi(N)} = M \cdot (M^{\phi(N)})^k \pmod N $$
&lt;p>여기서 &lt;strong>오일러의 정리&lt;/strong> ($M^{\phi(N)} \equiv 1 \pmod N$)가 등장합니다. (※엄밀히는 $M$과 $N$이 서로소여야 하지만, RSA에서는 $M$과 $N$이 서로소가 아닐 확률이 천문학적으로 낮으며 중국인의 나머지 정리를 사용하면 서로소가 아니어도 성립함이 증명 가능합니다).&lt;/p>
&lt;p>오일러의 정리를 적용하면 $M^{\phi(N)} \equiv 1$ 이므로,
&lt;/p>
$$ M \cdot (1)^k \equiv M \pmod N $$
&lt;p>보기 좋게 $M$이 복원되었습니다! 페르마와 오일러가 수백 년 전에 발견한 수의 성질이 현대 디지털 통신의 기밀성을 완벽하게 보장하고 있는 것입니다.&lt;/p>
&lt;hr>
&lt;h2 id="8-rsa-암호의-토이-구현-python">8. RSA 암호의 토이 구현 (Python)
&lt;/h2>&lt;p>이론만으로는 실감이 나지 않기 때문에 Python을 사용하여 실제로 RSA 암호의 키 생성, 암호화, 복호화의 과정을 구현해 보겠습니다. 이것은 교육용 &amp;lsquo;토이(장난감) 구현&amp;rsquo;이지만 사용된 수학은 진짜와 완전히 같습니다.&lt;/p>
&lt;p>모듈러 역원 $d$를 구하기 위한 &amp;lsquo;확장 유클리드 호제법&amp;rsquo;도 구현에 포함시킵니다.&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;/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="kn">import&lt;/span> &lt;span class="nn">random&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="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="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="c1"># 확장 유클리드 호제법 (ax + by = gcd(a,b) 의 x, y 를 구함)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># e*d ≡ 1 (mod φ(N)) 의 d 를 찾기 위해 사용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">extended_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="k">if&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>&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="n">b&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&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="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="n">g&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&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">extended_gcd&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">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&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="n">g&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">-&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">a&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y&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">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="n">g&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">y&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">extended_gcd&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="k">if&lt;/span> &lt;span class="n">g&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="k">raise&lt;/span> &lt;span class="ne">Exception&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;역원이 존재하지 않습니다&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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="k">return&lt;/span> &lt;span class="n">x&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="n">phi&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="k">def&lt;/span> &lt;span class="nf">generate_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&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="kc">True&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getrandbits&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&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="k">if&lt;/span> &lt;span class="n">p&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&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="n">p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="ow">and&lt;/span> &lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&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="n">p&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">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"># RSA 키 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">generate_keypair&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">q&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># p 와 q 가 같아지지 않도록 함&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">p&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">q&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">q&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_prime&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&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="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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># e 는 65537 등의 소수가 자주 사용되지만, 여기서는 무작위로 고름&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="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">randrange&lt;/span>&lt;span class="p">(&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 class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">gcd&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="k">while&lt;/span> &lt;span class="n">g&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="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">randrange&lt;/span>&lt;span class="p">(&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 class="n">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">gcd&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="c1"># 비밀키 d 계산&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="c1"># 공개키 (e, n), 비밀키 (d, n)&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="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 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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">encrypt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">pk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">plaintext&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="p">,&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># plaintext^e mod n 을 계산&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cipher&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">ord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">char&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 class="k">for&lt;/span> &lt;span class="n">char&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">plaintext&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="n">cipher&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">decrypt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">sk&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ciphertext&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="p">,&lt;/span> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sk&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># cipher^d mod n 을 계산하고 문자로 되돌림&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">plain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="nb">chr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">char&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 class="k">for&lt;/span> &lt;span class="n">char&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">ciphertext&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="s1">&amp;#39;&amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">plain&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"># 실행 예시&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s1">&amp;#39;__main__&amp;#39;&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="s2">&amp;#34;--- RSA 암호 토이 구현 ---&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="n">public_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">private_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">generate_keypair&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bits&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1"># 12비트의 소수 사용&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;공개키 (e, n): &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">public_key&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 class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;비밀키 (d, n): &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">private_key&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="n">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Hello Math!&amp;#34;&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="se">\n&lt;/span>&lt;span class="s2">원래의 메시지: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">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;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="n">encrypted_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">encrypt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">public_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">message&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">encrypted_msg&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"># 복호화&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">decrypted_msg&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">decrypt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">private_key&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">encrypted_msg&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_msg&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;/p>
&lt;hr>
&lt;h2 id="9-마치며-수학의-아름다움과-실용성의-교차점">9. 마치며: 수학의 아름다움과 실용성의 교차점
&lt;/h2>&lt;p>피에르 드 페르마가 이 &amp;lsquo;소정리&amp;rsquo;를 발견한 17세기 당시, 이것이 어딘가에 쓸모가 있을 것이라고 생각한 사람은 아무도 없었습니다. 페르마 자신도 순수한 수학적 탐구심에서 정수론 연구를 하고 있었습니다.&lt;/p>
&lt;p>하지만 약 300년 후인 1970년대, 컴퓨터 네트워크의 여명기에 안전한 통신 프로토콜을 확립하기 위해 없어서는 안 될 암호 기술로서 페르마의 정리는 극적인 부활을 이뤘습니다. 페르마의 소정리에 기반한 소수 판별 기술과 오일러의 정리에 기반한 RSA 암호는 현대의 인터넷 인프라를 말 그대로 지탱하고 있습니다.&lt;/p>
&lt;p>우리가 매일 무심코 보내는 메신저의 메시지도, 쇼핑몰에서의 물건 구매도, 모든 것은 이 $a^{p-1} \equiv 1 \pmod p$ 라는 심플하고 아름다운 수식 위에서 춤추고 있는 것입니다. 수학이 아무리 추상적이라 할지라도 언젠가는 반드시 인류에게 도움이 될 때가 온다는 것을 페르마의 소정리는 가르쳐 주고 있습니다.&lt;/p>
&lt;p>프로그래밍이나 암호 이론을 배울 때 그 기초에 있는 수학적 구조를 이해하는 것은 블랙박스로 제공되는 라이브러리의 동작을 깊이 이해하고, 더 안전한 시스템을 설계하기 위한 큰 무기가 될 것입니다.&lt;/p></description></item><item><title>격자 암호(Lattice-based cryptography)의 수학적 직관</title><link>http://kenji.blog/ko/p/lattice-based-cryptography-math-intuition/</link><pubDate>Fri, 11 Sep 2026 21:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/lattice-based-cryptography-math-intuition/</guid><description>&lt;img src="http://kenji.blog/p/lattice-based-cryptography-math-intuition/img/eyecatch.jpg" alt="Featured image of post 격자 암호(Lattice-based cryptography)의 수학적 직관" />&lt;h1 id="1-도입-포스트-양자-암호pqc의-여명과-격자-암호의-대두">1. 도입: 포스트 양자 암호(PQC)의 여명과 격자 암호의 대두
&lt;/h1>&lt;p>현대 사회의 디지털 인프라를 지탱하고 있는 것은 RSA 암호나 타원곡선암호(ECC)를 비롯한 공개키 암호 기술입니다. 이러한 암호 방식은 &amp;lsquo;소인수분해 문제&amp;rsquo;나 &amp;lsquo;이산대수 문제&amp;rsquo;와 같은, 기존의 고전 컴퓨터로는 효율적으로 풀 수 없다고(지수 함수적인 시간이 소요된다고) 믿어지는 수학적 난해성에 안전성의 근거를 두고 있습니다.&lt;/p>
&lt;p>그러나 1994년 피터 쇼어(Peter Shor)가 발표한 &amp;lsquo;쇼어 알고리즘(Shor&amp;rsquo;s algorithm)&amp;lsquo;은 암호계에 큰 충격을 주었습니다. 이 알고리즘은 대규모 양자 컴퓨터가 실현될 경우 소인수분해 문제나 이산대수 문제를 다항식 시간 내에 풀어버린다는 것을 수학적으로 증명한 것입니다. 이는 즉, 현재 널리 이용되고 있는 공개키 암호가 장래에는 완전히 해독 가능해진다는 것을 의미합니다.&lt;/p>
&lt;p>이러한 &amp;lsquo;양자 컴퓨터의 위협(Quantum Threat)&amp;lsquo;에 대응하기 위해, 양자 컴퓨터를 사용해도 해독이 어려운 새로운 암호 방식의 연구가 시급해졌습니다. 이것이 &amp;lsquo;포스트 양자 암호(Post-Quantum Cryptography: PQC)&amp;rsquo; 또는 &amp;lsquo;양자 내성 암호&amp;rsquo;라고 불리는 분야입니다.&lt;/p>
&lt;p>PQC에는 몇 가지 유력한 후보가 존재합니다. 해시 기반 암호, 코드 기반 암호, 다변수 다항식 암호, 아이소제니 기반 암호 등을 들 수 있지만, 그 중에서도 현재 가장 주목받고 있으며 NIST(미국 국립표준기술연구소)의 PQC 표준화 프로세스의 중심이 되고 있는 것이 바로 &amp;lsquo;격자 암호(Lattice-based cryptography)&amp;lsquo;입니다. 격자 암호는 다른 방식과 비교하여 암호화 및 복호화 처리 속도가 매우 빠르며, 또한 &amp;lsquo;최악의 경우 복잡도(Worst-case complexity)&amp;lsquo;를 &amp;lsquo;평균적인 경우 복잡도(Average-case complexity)&amp;lsquo;로 환원하는, 암호 이론에 있어서 극히 강력한 안전성 증명을 갖는다는 두드러진 특징을 가지고 있습니다.&lt;/p>
&lt;p>본 기사에서는 이 격자 암호의 기초가 되는 &amp;lsquo;격자(Lattice)&amp;lsquo;의 수학적 정의부터 출발하여, 격자 상의 어려운 문제인 SVP(최단 벡터 문제)나 CVP(최근접 벡터 문제), 그리고 현대 격자 암호의 심장부라고도 할 수 있는 &amp;lsquo;LWE 문제(Learning With Errors)&amp;lsquo;에 대해 수식과 기하학적인 직관, 그리고 구체적인 수치 예를 섞어 철저하고 깊이 있게 해설하겠습니다.&lt;/p>
&lt;h1 id="2-격자lattice의-수학적-정의와-기하학적-직관">2. 격자(Lattice)의 수학적 정의와 기하학적 직관
&lt;/h1>&lt;h2 id="21-벡터-공간과-격자">2.1 벡터 공간과 격자
&lt;/h2>&lt;p>수학에서 &amp;lsquo;격자(Lattice)&amp;lsquo;란 $n$차원 실수 벡터 공간 $\mathbb{R}^n$ 내에 규칙적으로 늘어선 이산적인 점들의 집합을 말합니다. 선형대수학에서 배우는 벡터 공간(Vector Space)과 비슷하지만 결정적인 차이가 있습니다. 벡터 공간이 기저 벡터의 &amp;lsquo;실수 계수&amp;rsquo;를 통한 선형 결합으로 표현되는 연속적인 공간인 반면, 격자는 기저 벡터의 &amp;lsquo;정수 계수&amp;rsquo;를 통한 선형 결합으로 표현되는 이산적인 공간입니다.&lt;/p>
&lt;p>수학적으로 엄밀한 정의를 내려보겠습니다. $m$차원 실수 벡터 공간 $\mathbb{R}^m$에서의 $n$개($n \le m$)의 선형 독립적인 벡터 $\mathbf{b}_1, \mathbf{b}_2, \dots, \mathbf{b}_n$을 생각해 봅시다. 이들 벡터를 열벡터로 가지는 행렬을 $B = [\mathbf{b}_1, \mathbf{b}_2, \dots, \mathbf{b}_n] \in \mathbb{R}^{m \times n}$이라고 합니다. 이 $B$를 격자의 &amp;lsquo;기저(Basis)&amp;lsquo;라고 부릅니다.&lt;/p>
&lt;p>이 기저 $B$에 의해 생성되는 격자 $\mathcal{L}(B)$는 다음과 같이 정의됩니다.&lt;/p>
$$
\mathcal{L}(B) = \left\{ \sum_{i=1}^{n} x_i \mathbf{b}_i \mathrel{\bigg|} x_i \in \mathbb{Z} \right\} = \{ B \mathbf{x} \mid \mathbf{x} \in \mathbb{Z}^n \}
$$
&lt;p>여기서 중요한 것은 계수 $x_i$가 실수 $\mathbb{R}$이 아니라 정수 $\mathbb{Z}$로 한정되어 있다는 점입니다. 이로 인해 공간 내에 무수히 존재하는 연속적인 점이 아니라, 일정한 간격으로 배치된 교차점과 같은 &amp;lsquo;이산적인 점들의 집합&amp;rsquo;이 형성됩니다.&lt;/p>
&lt;h2 id="22-기하학적-이미지">2.2 기하학적 이미지
&lt;/h2>&lt;p>2차원 평면 $\mathbb{R}^2$의 예로 생각해 봅시다. 기저 벡터로서 $\mathbf{b}_1 = \begin{pmatrix} 1 \\ 0 \end{pmatrix}$와 $\mathbf{b}_2 = \begin{pmatrix} 0 \\ 1 \end{pmatrix}$을 선택한 경우, 이들에 의해 생성되는 격자는 좌표평면 상의 모든 정수 좌표 $(x, y) \in \mathbb{Z}^2$의 집합이 됩니다. 이는 가장 단순한 &amp;lsquo;정방격자&amp;rsquo;입니다.&lt;/p>
&lt;p>그러나 격자가 항상 직교하는 것은 아닙니다. 예를 들어, $\mathbf{b}_1 = \begin{pmatrix} 2 \\ 1 \end{pmatrix}$과 $\mathbf{b}_2 = \begin{pmatrix} 1 \\ 3 \end{pmatrix}$이라는 기저를 생각해보면, 생성되는 점은 비스듬하게 왜곡된 그물망의 교차점처럼 됩니다.&lt;/p>
&lt;h2 id="23-기저의-비유일성과-유니모듈러-변환">2.3 기저의 비유일성과 유니모듈러 변환
&lt;/h2>&lt;p>격자 암호의 안전성 근간과 관련된 중요한 성질이 있습니다. 그것은 &amp;lsquo;동일한 격자를 생성하는 기저는 무수히 존재한다&amp;rsquo;는 것입니다.&lt;/p>
&lt;p>예를 들어, 앞서 설명한 $\mathbf{b}_1 = (1, 0)^T, \mathbf{b}_2 = (0, 1)^T$라는 기저가 생성하는 $\mathbb{Z}^2$ 격자는 $\mathbf{b}'_1 = (1, 1)^T, \mathbf{b}'_2 = (2, 3)^T$라는 기저를 사용해도 완전히 같은 격자 $\mathbb{Z}^2$를 생성합니다.&lt;/p>
&lt;p>어떤 기저 $B$와 다른 기저 $B'$가 같은 격자를 생성하기 위한 필요충분조건은, 정수 성분을 가진 행렬 $U \in \mathbb{Z}^{n \times n}$이면서 행렬식이 $\det(U) = \pm 1$이 되는 것이 존재하여,
&lt;/p>
$$ B' = B U $$
&lt;p>
로 표현될 수 있다는 것입니다. 이러한 행렬 $U$를 &amp;lsquo;유니모듈러 행렬(Unimodular matrix)&amp;lsquo;이라고 부릅니다.&lt;/p>
&lt;p>암호에 응용할 때의 기본적인 아이디어는 &amp;lsquo;좋은 기저(직교에 가깝고 짧은 벡터로 이루어진 기저)&amp;lsquo;를 비밀키로 하고, &amp;lsquo;나쁜 기저(서로 극단적으로 비스듬하게 교차하며 매우 긴 벡터로 이루어진 기저)&amp;lsquo;를 공개키로 사용하는 것입니다. 나쁜 기저로부터 좋은 기저를 계산해내는 것은 차원이 높아질수록 매우 어려워집니다. 이것이 격자 암호의 기본적인 직관입니다.&lt;/p>
&lt;h1 id="3-격자에서의-계산이-어려운-문제">3. 격자에서의 계산이 어려운 문제
&lt;/h1>&lt;p>격자 암호의 안전성은 격자 상의 특정한 수학적 문제를 푸는 것의 어려움에 의존하고 있습니다. 여기서는 가장 기본적이면서 유명한 두 가지 문제를 소개합니다.&lt;/p>
&lt;h2 id="31-최단-벡터-문제shortest-vector-problem-svp">3.1 최단 벡터 문제(Shortest Vector Problem: SVP)
&lt;/h2>&lt;p>SVP는 격자 이론에서 가장 고전적이고 유명한 문제입니다.&lt;/p>
&lt;p>&lt;strong>정의 (SVP):&lt;/strong>
임의의 격자 기저 $B$가 주어졌을 때, 그 격자 $\mathcal{L}(B)$에 속하는 영(zero)이 아닌 벡터 중에서 유클리드 노름(길이)이 최소가 되는 벡터 $\mathbf{v}$를 찾아라.&lt;/p>
&lt;p>수식으로 표현하면, $\min_{\mathbf{v} \in \mathcal{L}(B) \setminus \{\mathbf{0}\}} \| \mathbf{v} \|$가 되는 $\mathbf{v}$를 구하는 문제입니다. 이 최소 길이를 $\lambda_1(\mathcal{L})$로 쓰고, &amp;lsquo;격자의 제1연속 최소값(First successive minimum)&amp;lsquo;이라고 부릅니다.&lt;/p>
&lt;p>2차원이나 3차원처럼 낮은 차원이라면, 그림을 그려서 눈으로 가장 짧은 벡터를 찾을 수 있습니다. 또는 가우스의 격자 축소 알고리즘(Gauss lattice reduction algorithm) 등을 사용하여 효율적으로 풀 수 있습니다. 그러나 차원 $n$이 수백에서 수천과 같은 고차원이 되면, SVP를 엄밀하게 푸는 것은 NP-hard임이 알려져 있습니다.&lt;/p>
&lt;p>실제 암호에서는 엄밀한 최단 벡터가 아니라, &amp;lsquo;근사적으로 짧은 벡터&amp;rsquo;를 찾는 근사 SVP($\gamma$-SVP)가 사용됩니다. 근사 계수 $\gamma$가 다항식 크기일 경우, 이 문제는 여전히 매우 어려운 것으로 간주됩니다.&lt;/p>
&lt;h2 id="32-최근접-벡터-문제closest-vector-problem-cvp">3.2 최근접 벡터 문제(Closest Vector Problem: CVP)
&lt;/h2>&lt;p>CVP 또한 격자 암호에 있어서 매우 중요한 문제입니다.&lt;/p>
&lt;p>&lt;strong>정의 (CVP):&lt;/strong>
임의의 격자 기저 $B$와, 공간 내의 임의의 타겟 벡터 $\mathbf{t} \in \mathbb{R}^m$ (반드시 격자점일 필요는 없음)이 주어졌을 때, 격자점 중에서 $\mathbf{t}$에 가장 가까운 격자점 $\mathbf{v} \in \mathcal{L}(B)$를 찾아라.&lt;/p>
&lt;p>수식으로 표현하면, $\min_{\mathbf{v} \in \mathcal{L}(B)} \| \mathbf{v} - \mathbf{t} \|$가 되는 격자점 $\mathbf{v}$를 찾는 문제입니다.&lt;/p>
&lt;p>CVP 역시 SVP와 마찬가지로 고차원에서는 NP-hard입니다. 암호에의 응용이라는 관점에서, 후술할 LWE 문제는 이 CVP의 특수한 변형(Bounded Distance Decoding: BDD)과 밀접한 관계가 있습니다.&lt;/p>
&lt;h2 id="33-왜-고차원이-되면-풀-수-없는가-lll과-bkz의-한계">3.3 왜 고차원이 되면 풀 수 없는가? (LLL과 BKZ의 한계)
&lt;/h2>&lt;p>고차원의 격자 문제를 풀기 위한 유명한 알고리즘으로 LLL 알고리즘(Lenstra-Lenstra-Lovász algorithm)이 있습니다. LLL 알고리즘은 다항식 시간에 동작하며, 격자 기저를 어느 정도 &amp;lsquo;좋은 기저&amp;rsquo;로 축소(Reduction)할 수 있습니다. 그러나 LLL 알고리즘이 찾을 수 있는 최단 벡터는 진정한 최단 벡터의 길이에 대해 지수 함수적인($2^{\mathcal{O}(n)}$) 근사 계수를 가지기 때문에, 암호의 안전성을 깨뜨리기에는 역부족입니다.&lt;/p>
&lt;p>LLL을 개량한 BKZ(Block Korkine-Zolotarev) 알고리즘과 같은 더 강력한 기저 축소 알고리즘을 사용하면 더 짧은 벡터를 찾을 수 있지만, 그 계산량은 블록 크기에 대해 지수 함수적으로 증가합니다. 격자 암호에서는 이 BKZ 알고리즘의 실행 시간을 추정함으로써 안전한 파라미터(차원 $n$의 크기 등)를 결정하고 있습니다. 현재 PQC의 표준 파라미터에서는 차원 $n$이 500에서 1000 이상의 값으로 선택되고 있으며, 슈퍼컴퓨터나 미래의 양자 컴퓨터를 사용하더라도 해독하는 데에는 우주의 나이 이상의 시간이 걸릴 것으로 여겨집니다.&lt;/p>
&lt;h1 id="4-lwe-문제learning-with-errors의-수학적-정식화">4. LWE 문제(Learning With Errors)의 수학적 정식화
&lt;/h1>&lt;p>현대 격자 암호의 대부분은 2005년 오데드 레게브(Oded Regev)가 제안한 &amp;lsquo;LWE 문제(Learning With Errors)&amp;lsquo;를 기반으로 하고 있습니다. LWE 문제의 아름다움은 그 정식화의 단순함과 &amp;lsquo;최악의 경우 복잡도에서 평균적인 경우 복잡도로의 환원&amp;rsquo;이라는 강력한 수학적 증명을 가지고 있다는 점에 있습니다.&lt;/p>
&lt;h2 id="41-노이즈가-없는-연립-일차-방정식">4.1 노이즈가 없는 연립 일차 방정식
&lt;/h2>&lt;p>LWE 문제를 이해하기 위해, 우선 노이즈가 없는 단순한 연립 일차 방정식을 생각해 봅시다.
미지의 비밀 벡터 $\mathbf{s} \in \mathbb{Z}_q^n$ (각 성분은 $0$부터 $q-1$까지의 정수)가 있다고 가정합니다. 여기서 $q$는 소수라고 합니다.&lt;/p>
&lt;p>랜덤한 계수 벡터 $\mathbf{a}_1, \mathbf{a}_2, \dots \in \mathbb{Z}_q^n$을 선택하고, 비밀 벡터 $\mathbf{s}$와의 내적을 법 $q$로 계산합니다.
$b_1 = \langle \mathbf{a}_1, \mathbf{s} \rangle \pmod q$
$b_2 = \langle \mathbf{a}_2, \mathbf{s} \rangle \pmod q$
$\vdots$&lt;/p>
&lt;p>충분한 수($n$개 이상)의 $(\mathbf{a}_i, b_i)$ 쌍이 주어질 경우, 우리는 선형대수의 &amp;lsquo;가우스 소거법(Gaussian elimination)&amp;lsquo;을 사용하여 쉽게 비밀 벡터 $\mathbf{s}$를 복원할 수 있습니다. 이는 다항식 시간 내에 간단히 풀 수 있는 문제입니다.&lt;/p>
&lt;h2 id="42-lwe-문제의-정의-노이즈를-더하다">4.2 LWE 문제의 정의: 노이즈를 더하다
&lt;/h2>&lt;p>그렇다면, 이 문제에 약간의 &amp;lsquo;노이즈(오차)&amp;lsquo;를 더하면 어떻게 될까요?
이것이 LWE 문제의 본질입니다.&lt;/p>
&lt;p>미지의 비밀 벡터 $\mathbf{s} \in \mathbb{Z}_q^n$에 대하여, 각 방정식의 결과에 작은 오차 $e_i \in \mathbb{Z}_q$를 더합니다.
$b_i = \langle \mathbf{a}_i, \mathbf{s} \rangle + e_i \pmod q$&lt;/p>
&lt;p>여기서 $e_i$는 평균이 0이고 표준편차가 비교적 작은(예를 들어 정규분포와 같은, 이산 가우스 분포에서 선택된) 작은 정수값입니다.
주어지는 정보는 랜덤한 벡터 $\mathbf{a}_i$와, 거기에 오차를 더해 계산된 $b_i$ 쌍의 리스트입니다.
$( \mathbf{a}_1, b_1 ), ( \mathbf{a}_2, b_2 ), \dots, ( \mathbf{a}_m, b_m )$&lt;/p>
&lt;p>이를 행렬로 표현하면 매우 깔끔해집니다.
랜덤한 행렬 $A \in \mathbb{Z}_q^{m \times n}$, 비밀 벡터 $\mathbf{s} \in \mathbb{Z}_q^n$, 오차 벡터 $\mathbf{e} \in \mathbb{Z}_q^m$을 사용하여,
&lt;/p>
$$ \mathbf{b} = A \mathbf{s} + \mathbf{e} \pmod q $$
&lt;p>
라고 쓸 수 있습니다. 주어지는 것은 $A$와 $\mathbf{b}$뿐입니다. 여기서 $\mathbf{s}$를 구하는 것이 &amp;lsquo;탐색 LWE 문제(Search LWE problem)&amp;lsquo;입니다.&lt;/p>
&lt;p>오차 $e_i$가 들어있기 때문에, 가우스 소거법을 사용하려고 하면 방정식을 더하고 빼는 과정에서 오차가 지수 함수적으로 증폭되어 올바른 답에 도달할 수 없게 됩니다. 얼핏 보면 단순한 연립 일차 방정식처럼 보이지만, 이 작은 노이즈가 더해지는 것만으로 문제의 난이도가 NP-hard 수준으로 뛰어오르게 됩니다.&lt;/p>
&lt;h2 id="43-결정-lwe-문제decision-lwe">4.3 결정 LWE 문제(Decision LWE)
&lt;/h2>&lt;p>암호 이론의 증명에서 빈번하게 사용되는 것은 탐색 LWE 문제의 변형인 &amp;lsquo;결정 LWE 문제(Decision LWE problem)&amp;lsquo;입니다.&lt;/p>
&lt;p>결정 LWE 문제란, 다음의 2가지 분포에서 얻은 샘플의 리스트가 주어졌을 때, 그것이 어느 분포에서 온 것인지를 판정하는 문제입니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>LWE 분포&lt;/strong>: 의도적으로 계산된 $(A, \mathbf{b} = A\mathbf{s} + \mathbf{e} \pmod q)$&lt;/li>
&lt;li>&lt;strong>균등 랜덤 분포&lt;/strong>: 완전히 랜덤하게 선택된 행렬 $A$와 벡터 $\mathbf{u}$로 이루어진 $(A, \mathbf{u})$&lt;/li>
&lt;/ol>
&lt;p>놀랍게도, LWE 문제의 파라미터를 적절히 선택하면, LWE 분포에서 얻은 쌍은 완전히 랜덤한 데이터 쌍과 &amp;lsquo;계산량적으로 식별 불가능(Computationally Indistinguishable)&amp;lsquo;해집니다. 이 성질이 LWE 기반 암호가 &amp;lsquo;난수와 구별할 수 없는 암호문&amp;rsquo;을 생성할 수 있는 근거가 됩니다.&lt;/p>
&lt;h2 id="44-최악의-경우-복잡도에서-평균적인-경우-복잡도로의-환원레게브의-정리">4.4 최악의 경우 복잡도에서 평균적인 경우 복잡도로의 환원(레게브의 정리)
&lt;/h2>&lt;p>오데드 레게브의 가장 큰 공적은 이 LWE 문제의 어려움을 앞서 언급한 격자 문제(SVP나 CVP)의 어려움에 수학적으로 연결시킨 것입니다.&lt;/p>
&lt;p>그는 양자 환원(Quantum reduction)을 사용하여, &amp;lsquo;만약 LWE 문제를 평균적으로(랜덤하게 선택된 $A$와 $\mathbf{e}$에 대해) 풀 수 있는 다항식 시간 알고리즘이 존재한다면, 임의의 격자의 최악의 경우(가장 어려운 경우)의 Gap-SVP를 풀 수 있는 다항식 시간의 양자 알고리즘이 존재한다&amp;rsquo;는 것을 증명했습니다. (나중에 페이커트(Peikert) 등에 의해 고전적인 환원도 증명되었습니다.)&lt;/p>
&lt;p>이는 암호 이론에 있어 꿈과 같은 성질입니다. 왜냐하면, &amp;lsquo;암호가 깨지는 것은 우리가 우연히 약한 키(평균적인 경우의 일부)를 선택해 버렸기 때문일지도 모른다&amp;rsquo;는 우려를 불식시키고, &amp;lsquo;평균적인 LWE를 풀 수 있다면, 격자의 모든 어려운 문제를 풀 수 있게 된다(그러므로 LWE는 절대적으로 어렵다)&amp;lsquo;는 강력한 보증을 해주기 때문입니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["최악의 경우 격자 문제 (Gap-SVP, SIVP)"] -->|양자/고전적 환원| B["평균적인 경우 LWE 문제"]
B -->|암호학적 구성| C["LWE 기반 암호 시스템 (PKE, KEM, FHE)"]
style A fill:#ffcccc,stroke:#ff0000,stroke-width:2px,color:#000
style B fill:#ccffcc,stroke:#00aa00,stroke-width:2px,color:#000
style C fill:#ccccff,stroke:#0000ff,stroke-width:2px,color:#000&lt;/div>
&lt;h1 id="5-lwe를-이용한-공개키-암호-방식레게브-암호의-구성">5. LWE를 이용한 공개키 암호 방식(레게브 암호)의 구성
&lt;/h1>&lt;p>LWE 문제의 어려움을 이해했으니, 그것을 사용하여 어떻게 암호화와 복호화를 수행하는지, 오데드 레게브가 제안한 기본적인 공개키 암호 방식을 살펴봅시다. 여기서는 1비트의 메시지 $M \in \{0, 1\}$를 암호화하는 가장 기본적인 메커니즘을 설명합니다.&lt;/p>
&lt;h2 id="51-키-생성key-generation">5.1 키 생성(Key Generation)
&lt;/h2>&lt;ol>
&lt;li>시스템 파라미터로서 법이 되는 소수 $q$, 차원 $n$, 방정식의 수 $m$($m > n \log q$)을 결정합니다.&lt;/li>
&lt;li>비밀키로서 벡터 $\mathbf{s} \in \mathbb{Z}_q^n$를 랜덤하게 선택합니다.&lt;/li>
&lt;li>랜덤한 행렬 $A \in \mathbb{Z}_q^{m \times n}$을 생성합니다.&lt;/li>
&lt;li>작은 오차 벡터 $\mathbf{e} \in \mathbb{Z}_q^m$를 이산 가우스 분포 등의 오차 분포에서 선택합니다.&lt;/li>
&lt;li>벡터 $\mathbf{b} = A \mathbf{s} + \mathbf{e} \pmod q$를 계산합니다.&lt;/li>
&lt;li>공개키(Public Key)는 $(A, \mathbf{b})$가 됩니다.&lt;/li>
&lt;li>비밀키(Secret Key)는 $\mathbf{s}$가 됩니다.&lt;/li>
&lt;/ol>
&lt;p>공개키는 그야말로 &amp;lsquo;LWE 문제의 인스턴스&amp;rsquo; 그 자체입니다. 공개키 $(A, \mathbf{b})$로부터 비밀키 $\mathbf{s}$를 구하는 것은 탐색 LWE 문제를 푸는 것과 같기 때문에 안전성이 보장됩니다.&lt;/p>
&lt;h2 id="52-암호화encryption">5.2 암호화(Encryption)
&lt;/h2>&lt;p>앨리스는 밥의 공개키 $(A, \mathbf{b})$를 사용하여, 1비트의 메시지 $M \in \{0, 1\}$를 암호화합니다.&lt;/p>
&lt;ol>
&lt;li>랜덤한 이진 벡터(성분이 0 또는 1) $\mathbf{r} \in \{0, 1\}^m$를 선택합니다.&lt;/li>
&lt;li>암호문의 전반부로서 벡터 $\mathbf{u} = A^T \mathbf{r} \pmod q$를 계산합니다. ($A^T$는 $A$의 전치 행렬입니다. 즉, $A$의 행 중에서 $\mathbf{r}$의 성분이 1인 행들을 더하는 것입니다).&lt;/li>
&lt;li>암호문의 후반부로서 스칼라 $v = \mathbf{b}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor \pmod q$를 계산합니다.
(메시지 $M$이 0이면 아무것도 더하지 않고, $1$이면 $q$의 정확히 절반 값인 $\lfloor \frac{q}{2} \rfloor$를 더합니다).&lt;/li>
&lt;li>암호문(Ciphertext)은 $(\mathbf{u}, v)$가 됩니다.&lt;/li>
&lt;/ol>
&lt;p>암호화의 직관적인 의미는 공개키의 행렬 $A$와 벡터 $\mathbf{b}$에 대하여 &amp;lsquo;랜덤한 부분 집합의 합&amp;rsquo;을 취하는 것입니다. 결정 LWE 문제의 어려움에 의해 이 암호문 $(\mathbf{u}, v)$는 완전히 랜덤한 벡터 및 균등 난수와 구별이 불가능한 것처럼 보입니다(의미론적 안전성: Semantic Security).&lt;/p>
&lt;div class="mermaid">flowchart LR
M["메시지 M in {0,1}"] --> Enc
PK["공개키 (A, b)"] --> Enc
r["랜덤 이진 벡터 r"] --> Enc
subgraph Enc ["암호화 과정"]
direction TB
u_calc["u = A^T * r mod q"]
v_calc["v = b^T * r + M * floor(q/2) mod q"]
end
Enc --> CT["암호문 (u, v)"]&lt;/div>
&lt;h2 id="53-복호화decryption">5.3 복호화(Decryption)
&lt;/h2>&lt;p>밥은 비밀키 $\mathbf{s}$를 사용하여 암호문 $(\mathbf{u}, v)$를 복호화합니다.&lt;/p>
&lt;ol>
&lt;li>다음 값을 계산합니다: $D = v - \mathbf{s}^T \mathbf{u} \pmod q$&lt;/li>
&lt;li>계산한 결과가 $0$에 가까우면 $M=0$, $\lfloor \frac{q}{2} \rfloor$에 가까우면 $M=1$로 출력합니다.&lt;/li>
&lt;/ol>
&lt;p>왜 이렇게 복호화할 수 있는지 수학적으로 전개해 봅시다.
$\mathbf{b} = A \mathbf{s} + \mathbf{e}$ 였음을 상기해 주십시오.&lt;/p>
$$
\begin{aligned}
v - \mathbf{s}^T \mathbf{u} &amp;= (\mathbf{b}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor) - \mathbf{s}^T (A^T \mathbf{r}) \\
&amp;= ((A \mathbf{s} + \mathbf{e})^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor) - \mathbf{s}^T A^T \mathbf{r} \\
&amp;= (\mathbf{s}^T A^T \mathbf{r} + \mathbf{e}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor) - \mathbf{s}^T A^T \mathbf{r} \\
&amp;= \mathbf{e}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor \pmod q
\end{aligned}
$$
&lt;p>여기서, 식 안에서 $\mathbf{s}^T A^T \mathbf{r}$가 깔끔하게 상쇄되어 사라졌습니다!
남은 것은 $\mathbf{e}^T \mathbf{r} + M \cdot \lfloor \frac{q}{2} \rfloor$ 입니다.&lt;/p>
&lt;p>$\mathbf{e}$는 성분이 매우 작은 노이즈 벡터이고, $\mathbf{r}$는 성분이 0 또는 1인 이진 벡터입니다. 따라서 이들의 내적인 $\mathbf{e}^T \mathbf{r}$도 (파라미터를 적절히 선택하면) 비교적 작은 값에 머물게 됩니다.&lt;/p>
&lt;ul>
&lt;li>만약 $M=0$이라면 결과는 $\mathbf{e}^T \mathbf{r}$가 되어 $0$에 가까운 작은 값이 됩니다.&lt;/li>
&lt;li>만약 $M=1$이라면 결과는 $\mathbf{e}^T \mathbf{r} + \lfloor \frac{q}{2} \rfloor$가 되어 $q$의 절반 값인 $\lfloor \frac{q}{2} \rfloor$의 주변에 위치하게 됩니다.&lt;/li>
&lt;/ul>
&lt;p>오차 $\mathbf{e}^T \mathbf{r}$의 절댓값이 $\frac{q}{4}$ 미만으로 수렴하도록 파라미터가 설계되어 있다면, 밥은 계산 결과가 $0$과 $\lfloor \frac{q}{2} \rfloor$ 중 어느 쪽에 더 가까운지를 보는 것만으로 메시지 $M$을 정확하게 판정(복호화)할 수 있습니다. 이것이 LWE 기반 암호가 기능하는 아름다운 메커니즘입니다.&lt;/p>
&lt;div class="mermaid">flowchart LR
CT["암호문 (u, v)"] --> Dec
SK["비밀키 s"] --> Dec
subgraph Dec ["복호화 과정"]
direction TB
calc["D = v - s^T * u mod q 계산"]
check["D가 0 또는 q/2 중 어디에 가까운지 확인"]
end
calc --> check
Dec --> M_out["복원된 메시지 M"]&lt;/div>
&lt;h1 id="6-구체적인-수치를-이용한-lwe-암호의-토이-이그잼플">6. 구체적인 수치를 이용한 LWE 암호의 토이 이그잼플
&lt;/h1>&lt;p>수식의 나열만으로는 실감하기 어려울 것이라 생각하므로, 실제로 매우 작은 수치 파라미터를 설정하여 암호화부터 복호화까지의 계산을 따라가 보겠습니다.
(※실제 암호 시스템에서는 안전성 확보를 위해 $n$은 500 이상, $q$는 수천 이상의 값이 사용됩니다)&lt;/p>
&lt;p>&lt;strong>【파라미터 설정】&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>법 $q = 17$ (소수. 따라서 값은 $0$부터 $16$까지의 범위를 갖습니다)&lt;/li>
&lt;li>차원 $n = 2$&lt;/li>
&lt;li>방정식의 수 $m = 4$&lt;/li>
&lt;li>메시지 $M = 1$을 암호화한다고 가정합니다.&lt;/li>
&lt;li>메시지의 시프트 양: $\lfloor \frac{q}{2} \rfloor = \lfloor \frac{17}{2} \rfloor = 8$&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>【1. 키 생성 단계】&lt;/strong>
밥은 비밀키 $\mathbf{s}$와 행렬 $A$, 오차 벡터 $\mathbf{e}$를 랜덤하게 선택합니다.
&lt;/p>
$$ \mathbf{s} = \begin{pmatrix} 3 \\ 4 \end{pmatrix} \in \mathbb{Z}_{17}^2 $$
$$ A = \begin{pmatrix} 2 &amp; 15 \\ 1 &amp; 8 \\ 14 &amp; 5 \\ 9 &amp; 10 \end{pmatrix} \in \mathbb{Z}_{17}^{4 \times 2} $$
$$ \mathbf{e} = \begin{pmatrix} 1 \\ -1 \\ 0 \\ 2 \end{pmatrix} \equiv \begin{pmatrix} 1 \\ 16 \\ 0 \\ 2 \end{pmatrix} \pmod{17} $$
&lt;p>다음으로 공개키 $\mathbf{b}$를 계산합니다.
&lt;/p>
$$ A \mathbf{s} = \begin{pmatrix} 2 &amp; 15 \\ 1 &amp; 8 \\ 14 &amp; 5 \\ 9 &amp; 10 \end{pmatrix} \begin{pmatrix} 3 \\ 4 \end{pmatrix} = \begin{pmatrix} 2\times 3 + 15\times 4 \\ 1\times 3 + 8\times 4 \\ 14\times 3 + 5\times 4 \\ 9\times 3 + 10\times 4 \end{pmatrix} = \begin{pmatrix} 6 + 60 \\ 3 + 32 \\ 42 + 20 \\ 27 + 40 \end{pmatrix} = \begin{pmatrix} 66 \\ 35 \\ 62 \\ 67 \end{pmatrix} $$
&lt;p>
이를 법 17로 계산합니다. ($66 = 17 \times 3 + 15$ 등)
&lt;/p>
$$ A \mathbf{s} \pmod{17} = \begin{pmatrix} 15 \\ 1 \\ 11 \\ 16 \end{pmatrix} $$
&lt;p>
오차 벡터 $\mathbf{e}$를 더합니다.
&lt;/p>
$$ \mathbf{b} = A \mathbf{s} + \mathbf{e} = \begin{pmatrix} 15 \\ 1 \\ 11 \\ 16 \end{pmatrix} + \begin{pmatrix} 1 \\ 16 \\ 0 \\ 2 \end{pmatrix} = \begin{pmatrix} 16 \\ 17 \\ 11 \\ 18 \end{pmatrix} \equiv \begin{pmatrix} 16 \\ 0 \\ 11 \\ 1 \end{pmatrix} \pmod{17} $$
&lt;p>공개키는 $A$와 $\mathbf{b} = (16, 0, 11, 1)^T$가 됩니다.&lt;/p>
&lt;p>&lt;strong>【2. 암호화 단계】&lt;/strong>
앨리스는 메시지 $M = 1$을 암호화합니다.
랜덤한 벡터 $\mathbf{r}$를 선택합니다. 여기서는 $\mathbf{r} = (1, 0, 1, 0)^T$로 합니다.&lt;/p>
&lt;p>$\mathbf{u}$를 계산합니다.
&lt;/p>
$$ \mathbf{u} = A^T \mathbf{r} = \begin{pmatrix} 2 &amp; 1 &amp; 14 &amp; 9 \\ 15 &amp; 8 &amp; 5 &amp; 10 \end{pmatrix} \begin{pmatrix} 1 \\ 0 \\ 1 \\ 0 \end{pmatrix} = \begin{pmatrix} 2 \times 1 + 14 \times 1 \\ 15 \times 1 + 5 \times 1 \end{pmatrix} = \begin{pmatrix} 16 \\ 20 \end{pmatrix} \equiv \begin{pmatrix} 16 \\ 3 \end{pmatrix} \pmod{17} $$
&lt;p>$v$를 계산합니다.
&lt;/p>
$$ \mathbf{b}^T \mathbf{r} = (16, 0, 11, 1) \begin{pmatrix} 1 \\ 0 \\ 1 \\ 0 \end{pmatrix} = 16 \times 1 + 11 \times 1 = 27 \equiv 10 \pmod{17} $$
&lt;p>
메시지 $M=1$에 해당하는 값 $\lfloor 17/2 \rfloor = 8$을 더합니다.
&lt;/p>
$$ v = \mathbf{b}^T \mathbf{r} + M \cdot 8 = 10 + 1 \times 8 = 18 \equiv 1 \pmod{17} $$
&lt;p>앨리스는 암호문으로 $(\mathbf{u}, v) = \left( \begin{pmatrix} 16 \\ 3 \end{pmatrix}, 1 \right)$을 밥에게 전송합니다.&lt;/p>
&lt;p>&lt;strong>【3. 복호화 단계】&lt;/strong>
암호문을 받은 밥은 비밀키 $\mathbf{s} = (3, 4)^T$를 사용하여 복호화합니다.
복호화 처리식: $D = v - \mathbf{s}^T \mathbf{u} \pmod{17}$을 계산합니다.&lt;/p>
$$ \mathbf{s}^T \mathbf{u} = (3, 4) \begin{pmatrix} 16 \\ 3 \end{pmatrix} = 3 \times 16 + 4 \times 3 = 48 + 12 = 60 \equiv 9 \pmod{17} $$
$$ D = v - \mathbf{s}^T \mathbf{u} = 1 - 9 = -8 \pmod{17} $$
&lt;p>여기서 법 17의 세계에서는 $-8$이 $9$와 같아집니다($-8 + 17 = 9$).
얻어진 값 $D = 9$를 $0$과 $8$($\lfloor 17/2 \rfloor$) 중 어디에 가까운지 판정합니다.
$9$는 $0$보다 $8$에 명백히 더 가깝기 때문에 밥은 올바르게 $M = 1$을 복원할 수 있었습니다!&lt;/p>
&lt;p>왜 $9$가 되었을까요? 앞서 설명한 증명을 상기해 봅시다.
오차 부분은 $\mathbf{e}^T \mathbf{r} = (1, -1, 0, 2) (1, 0, 1, 0)^T = 1 \times 1 + 0 \times 1 = 1$로 되어 있습니다.
따라서 계산 결과는 $\mathbf{e}^T \mathbf{r} + M \cdot 8 = 1 + 8 = 9$가 되어, 이론대로의 값이 계산되었음을 확인할 수 있었습니다.&lt;/p>
&lt;h1 id="7-실용화를-향한-진화-ring-lwe와-module-lwe">7. 실용화를 향한 진화: Ring-LWE와 Module-LWE
&lt;/h1>&lt;p>지금까지 설명한 표준적인 LWE 문제(Standard LWE)는 매우 강력한 안전성 증명을 가지고 있지만, 실용상 치명적인 약점이 있습니다. 그것은 &amp;lsquo;키의 크기가 거대해진다는 것&amp;rsquo;과 &amp;lsquo;계산 비용이 높다는 것&amp;rsquo;입니다.&lt;/p>
&lt;p>Standard LWE에서는 공개키에 거대한 행렬 $A \in \mathbb{Z}_q^{m \times n}$이 포함됩니다. 파라미터 $n$이 수백에서 수천이 되면 이 행렬의 크기는 수 메가바이트에 달해, 인터넷 상의 통신 프로토콜(TLS 등)에서 매번 송수신하기에는 너무 무겁습니다. 또한 행렬과 벡터의 곱셈에는 $\mathcal{O}(n^2)$의 계산량이 듭니다.&lt;/p>
&lt;p>이 문제를 해결하기 위해 도입된 것이 다항식 환(Polynomial rings)이라는 대수적인 구조를 격자에 결합한 &amp;lsquo;Ring-LWE(RLWE)&amp;lsquo;나 &amp;lsquo;Module-LWE(MLWE)&amp;lsquo;입니다.&lt;/p>
&lt;h2 id="71-ring-lwe의-직관">7.1 Ring-LWE의 직관
&lt;/h2>&lt;p>Ring-LWE에서는 벡터나 행렬을 다항식 환 $\mathcal{R}_q = \mathbb{Z}_q[X]/(X^n + 1)$ 상의 요소(다항식)로 대체합니다. (여기서 $n$은 2의 거듭제곱으로 선택됩니다).&lt;/p>
&lt;p>Standard LWE의 공개키가 행렬 $A$였던 것에 반해, Ring-LWE에서는 단일 다항식 $a(x)$를 사용합니다. 비밀키 $s(x)$나 오차 $e(x)$도 다항식이 됩니다.
방정식은 다음과 같이 됩니다.
&lt;/p>
$$ b(x) = a(x) \cdot s(x) + e(x) \pmod q $$
&lt;p>이는 다항식의 곱셈이므로 고속 푸리에 변환(FFT)과 유사한 &amp;lsquo;수론 변환(Number Theoretic Transform: NTT)&amp;lsquo;을 사용함으로써 계산량을 $\mathcal{O}(n \log n)$까지 극적으로 줄일 수 있습니다. 게다가 공개키의 크기도 행렬에서 단일 다항식으로 작아지기 때문에 데이터 크기가 $\mathcal{O}(n)$으로 감소합니다. 이는 통신 대역폭에 있어서 압도적인 우위를 가져옵니다.&lt;/p>
&lt;p>수학적으로 보면, Ring-LWE는 일반적인 격자가 아니라 &amp;lsquo;아이디얼 격자(Ideal Lattice)&amp;lsquo;라고 불리는 특수한 대칭성을 가진 격자 상의 문제로 귀결됩니다.&lt;/p>
&lt;h2 id="72-module-lwe와-nist의-표준화-kyber--ml-kem">7.2 Module-LWE와 NIST의 표준화 (Kyber / ML-KEM)
&lt;/h2>&lt;p>Ring-LWE는 효율적이지만, 아이디얼 격자의 특수한 대수적 구조가 장래의 공격 실마리가 되지 않을까 하는 일말의 우려가 있었습니다. 그래서 Standard LWE의 보수적인 안전성과 Ring-LWE의 효율성의 &amp;lsquo;장점만을 취한&amp;rsquo; 것이 &amp;lsquo;Module-LWE(MLWE)&amp;lsquo;입니다.&lt;/p>
&lt;p>Module-LWE에서는 다항식을 요소로 하는 작은 행렬과 벡터를 생각합니다. 즉, 환 상의 모듈(가군)을 다룹니다.
현재 NIST가 PQC의 키 캡슐화 메커니즘(KEM) 표준으로 선정한 &amp;lsquo;CRYSTALS-Kyber&amp;rsquo;(표준화 명칭: ML-KEM)는 바로 이 Module-LWE 문제의 어려움에 기반하여 구축되어 있습니다.&lt;/p>
&lt;h1 id="8-왜-양자-컴퓨터에-대해-안전한가">8. 왜 양자 컴퓨터에 대해 안전한가?
&lt;/h1>&lt;p>마지막으로, &amp;lsquo;왜 격자 암호는 양자 컴퓨터를 사용해도 해독되지 않는다고 여겨지는가?&amp;lsquo;라는 핵심 부분을 짚고 넘어가겠습니다.&lt;/p>
&lt;p>양자 컴퓨터가 RSA 암호나 타원곡선암호를 깨는 쇼어 알고리즘은 본질적으로 &amp;lsquo;은닉 부분군 문제(Hidden Subgroup Problem: HSP)&amp;lsquo;를 푸는 알고리즘입니다. RSA나 ECC의 배경에 있는 수학적 구조(유한 아벨 군)는 주기성을 가지고 있으며, 양자 푸리에 변환(QFT)이라는 양자 알고리즘 특유의 조작을 사용함으로써 이 주기(숨겨진 부분군)를 단번에 추출할 수 있습니다.&lt;/p>
&lt;p>그러나 격자 문제는 근본적으로 다릅니다. 격자에도 주기성은 있지만, SVP나 CVP에서 요구되는 것은 &amp;lsquo;최단 거리&amp;rsquo;나 &amp;lsquo;노이즈 제거&amp;rsquo;라는 기하학적이고 비선형적인 성질입니다. 쇼어 알고리즘과 같은 &amp;lsquo;아벨 군 상의 양자 푸리에 변환&amp;rsquo;을 그대로 적용하더라도 격자 문제의 해답이 되는 유용한 정보를 효율적으로 추출할 수 없습니다. 현재까지 SVP나 LWE에 대해 다항식 시간에 풀 수 있는 양자 알고리즘은 발견되지 않았으며, 양자 컴퓨터의 병렬 계산 능력을 동원하더라도 무차별 대입에 가까운 탐색(그로버 알고리즘에 의한 제곱근 수준의 고속화 정도)만이 유효한 수단이라고 널리 믿어지고 있습니다.&lt;/p>
&lt;h1 id="9-요약">9. 요약
&lt;/h1>&lt;p>본 기사에서는 격자 암호의 수학적 직관에 대해 격자의 기하학적 정의부터 시작하여 LWE 문제의 정식화, 그리고 공개키 암호의 구성에 이르기까지 상세히 해설했습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>격자(Lattice)&lt;/strong> 는 기저 벡터의 정수 계수 선형 결합으로 표현되는 이산적인 공간이며, 고차원에서는 직교에 가까운 &amp;lsquo;좋은 기저&amp;rsquo;를 찾는 것(SVP)이 어려워집니다.&lt;/li>
&lt;li>&lt;strong>LWE 문제(Learning With Errors)&lt;/strong> 는 노이즈가 포함된 연립 일차 방정식을 푸는 문제이며, 이것이 격자의 최악의 경우 문제의 어려움에 연결되어 있기 때문에 강력한 안전성의 근거를 제공합니다.&lt;/li>
&lt;li>LWE 문제를 이용함으로써 노이즈를 의도적으로 더하거나 소거하는 교묘한 메커니즘을 통해 암호화 및 복호화(&lt;strong>레게브 암호&lt;/strong>)가 실현됩니다.&lt;/li>
&lt;li>실제 프로토콜에서는 통신 효율과 계산 속도를 높이기 위해 다항식 환을 사용한 &lt;strong>Ring-LWE&lt;/strong>나 &lt;strong>Module-LWE&lt;/strong>가 채택되고 있으며, NIST 표준인 &lt;strong>ML-KEM&lt;/strong>의 기반이 되고 있습니다.&lt;/li>
&lt;/ol>
&lt;p>양자 컴퓨터라는 전대미문의 계산 패러다임 시프트가 다가오는 가운데, 고전적인 선형대수와 정수론의 심연에서 탄생한 &amp;lsquo;격자 암호&amp;rsquo;가 미래 인터넷 보안의 기반을 담당한다는 것은 매우 낭만적인 이야기입니다. 격자 암호의 기초가 되는 수학은 결코 너무 난해한 것이 아니며, 선형대수와 확률에 대한 기초 지식이 있다면 충분히 그 아름다운 구조를 이해할 수 있습니다. 본 기사가 PQC의 핵심이 되는 격자 암호에 대한 이해에 도움이 되기를 바랍니다.&lt;/p></description></item><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><item><title>완전동형암호(FHE)란? 차세대 보안의 핵심을 해설</title><link>http://kenji.blog/ko/p/fully-homomorphic-encryption-fhe-explained/</link><pubDate>Fri, 11 Sep 2026 11:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/fully-homomorphic-encryption-fhe-explained/</guid><description>&lt;img src="http://kenji.blog/p/fully-homomorphic-encryption-fhe-explained/img/eyecatch.jpg" alt="Featured image of post 완전동형암호(FHE)란? 차세대 보안의 핵심을 해설" />&lt;p>클라우드 컴퓨팅과 AI 기술이 사회의 기반으로 자리 잡은 가운데, &amp;lsquo;데이터의 프라이버시&amp;rsquo;와 &amp;lsquo;데이터의 활용&amp;rsquo;의 트레이드오프는 가장 중요한 과제 중 하나가 되었습니다. 의료 데이터, 금융 정보, 개인의 생체 정보 등 기밀성이 높은 데이터를 클라우드 상에서 AI에게 분석시키고자 하는 수요는 증가하고 있지만, 보안상의 우려로 데이터의 외부 전송을 주저하는 기업이 적지 않습니다.&lt;/p>
&lt;p>기존의 암호화 기술(AES나 RSA 등)은 스토리지에 저장되어 있는 데이터(Data at Rest)나 네트워크 상을 흐르는 데이터(Data in Transit)를 보호하는 데에는 능숙합니다. 하지만, 서버 측에서 데이터에 대해 검색이나 기계 학습 등의 &lt;strong>처리(계산)를 수행할 때(Data in Use)에는 한 번 암호를 복호화하여 평문으로 되돌려야 합니다&lt;/strong>. 만약 이 복호화된 타이밍에 서버가 해킹당하거나, 내부의 악의적인 관리자가 데이터를 엿본다면 정보 유출로 직결됩니다.&lt;/p>
&lt;p>이 &amp;lsquo;처리 시의 복호화&amp;rsquo;라는 근본적인 약점을 극복하는 꿈의 기술이 **완전동형암호(Fully Homomorphic Encryption: FHE)**입니다. FHE를 이용하면 데이터를 암호화한 채로 일절 복호화하지 않고 계산 처리를 수행하고, 그 결과의 암호문만을 클라이언트에게 반환하는 것이 가능해집니다.&lt;/p>
&lt;p>본 기사에서는 이 FHE의 개념부터 역사, Craig Gentry에 의한 획기적인 돌파구, 수학적인 기초(Ring-LWE 등), 최대 과제인 &amp;lsquo;노이즈&amp;rsquo;와 그 해결책(부트스트래핑), 그리고 최신 구현 라이브러리에 이르기까지 차세대 보안의 핵심인 FHE를 철저하고 깊이 있게 해설합니다.&lt;/p>
&lt;hr>
&lt;h2 id="1-동형암호란-무엇인가-기본적인-개념">1. 동형암호란 무엇인가? 기본적인 개념
&lt;/h2>&lt;p>&amp;lsquo;동형(Homomorphic)&amp;lsquo;이란 대수학의 용어로, 어떤 구조를 가진 집합 간에 연산의 구조를 유지한 채 사상할 수 있는 성질을 뜻합니다. 암호 이론에서의 &amp;lsquo;동형성&amp;rsquo;이란 &lt;strong>평문 공간에서의 연산이 암호문 공간에서의 연산에 대응한다&lt;/strong>는 성질입니다.&lt;/p>
&lt;p>단순한 수식으로 나타내면, 평문 $m_1$ 과 $m_2$ 에 대한 암호화 함수를 $E(\cdot)$ 라 하고, 복호화 함수를 $D(\cdot)$ 라 합니다. 평문 상에서의 연산(덧셈이나 곱셈 등)을 $\circ$ , 암호문 상에서의 연산을 $\diamond$ 라 했을 때, 이하의 관계가 성립합니다.&lt;/p>
$$ D(E(m_1) \diamond E(m_2)) = m_1 \circ m_2 $$
&lt;p>즉, 암호문 $E(m_1)$ 과 $E(m_2)$ 에 대해 어떤 연산 $\diamond$ 를 가한 결과를 복호화하면, 원래의 평문끼리 연산 $\circ$ 를 한 결과와 일치한다는 것입니다.&lt;/p>
&lt;h3 id="클라우드-컴퓨팅에서의-데이터-흐름">클라우드 컴퓨팅에서의 데이터 흐름
&lt;/h3>&lt;p>FHE를 이용한 클라우드 처리의 아키텍처는 기존의 것과 전혀 다릅니다. 다음 그림은 FHE를 활용한 안전한 데이터 처리의 흐름을 보여줍니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["클라이언트 (비밀키를 보유)"] -->|1. 평문 x 를 암호화: E(x)| B["클라우드 서버 (암호화된 데이터만 존재)"]
B -->|2. 암호문인 채로 함수 f 를 적용: E(f(x))| B
B -->|3. 계산 결과의 암호문 E(y)| A
A -->|4. 비밀키로 복호화: y = f(x)| A
style A fill:#d4edda,stroke:#28a745
style B fill:#f8d7da,stroke:#dc3545&lt;/div>
&lt;p>서버는 암호화된 데이터 $E(x)$ 를 받지만, 비밀키를 가지고 있지 않기 때문에 데이터의 내용을 아는 것은 절대 불가능합니다. 그러나 FHE의 성질을 이용함으로써, 암호문에 대해 함수 $f$(예를 들어 기계 학습의 추론 모델)를 적용하여 $E(f(x))$ 를 생성할 수 있습니다. 클라이언트는 이것을 받아 자신의 비밀키로 복호화함으로써 목적하는 결과 $y = f(x)$ 를 얻게 됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-동형암호의-진화-역사-phe-she-fhe">2. 동형암호의 진화 역사: PHE, SHE, FHE
&lt;/h2>&lt;p>동형암호는 단번에 현재의 &amp;lsquo;완전&amp;rsquo;한 형태가 된 것은 아닙니다. 실현할 수 있는 연산의 종류나 횟수에 따라 크게 3가지 단계로 분류됩니다.&lt;/p>
&lt;h3 id="partially-homomorphic-encryption-phe-부분동형암호">Partially Homomorphic Encryption (PHE: 부분동형암호)
&lt;/h3>&lt;p>PHE는 덧셈이나 곱셈 중 &lt;strong>어느 한쪽만&lt;/strong>을 무제한으로 수행할 수 있는 암호 방식입니다. 사실 이 성질을 가진 암호는 예전부터 존재했습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>RSA 암호 (곱셈에 대한 동형성)&lt;/strong>
RSA 암호는 의도치 않게 곱셈의 동형성을 가지고 있었습니다. 평문 $m_1, m_2$, 공개키 $(e, N)$ 이라고 하면:
$$ E(m_1) = m_1^e \pmod N $$
$$ E(m_2) = m_2^e \pmod N $$
이들을 곱하면:
$$ E(m_1) \times E(m_2) = (m_1 \cdot m_2)^e \pmod N = E(m_1 \times m_2) $$
이와 같이 암호문끼리의 곱셈이 평문의 곱셈에 대응합니다.&lt;/li>
&lt;li>&lt;strong>Paillier 암호 (덧셈에 대한 동형성)&lt;/strong>
1999년에 고안된 Paillier 암호는 덧셈에 대한 동형성을 가집니다. 전자투표(암호화된 표를 집계하고, 최종 결과만 복호화하는 등) 등에서 실용화되어 있습니다.&lt;/li>
&lt;/ul>
&lt;h3 id="somewhat-homomorphic-encryption-she-유한동형암호">Somewhat Homomorphic Encryption (SHE: 유한동형암호)
&lt;/h3>&lt;p>덧셈과 곱셈 &lt;strong>모두&lt;/strong>를 실행할 수 있지만, 연산할 수 있는 &lt;strong>횟수(회로의 깊이)에 제한&lt;/strong>이 있는 방식입니다. 후술할 &amp;lsquo;노이즈&amp;rsquo;의 축적으로 인해 특정 횟수 이상의 곱셈을 수행하면 복호화가 불가능해집니다. 2005년의 BGN (Boneh-Goh-Nissim) 암호 등이 이에 해당하지만, 실용적이고 복잡한 계산(딥러닝 등)을 수행하기에는 한계가 있었습니다.&lt;/p>
&lt;h3 id="fully-homomorphic-encryption-fhe-완전동형암호">Fully Homomorphic Encryption (FHE: 완전동형암호)
&lt;/h3>&lt;p>덧셈과 곱셈 모두를 &lt;strong>횟수 제한 없이&lt;/strong> 실행할 수 있는 암호 방식입니다. 정보 이론에서의 튜링 완전성과 마찬가지로, 덧셈(XOR에 해당)과 곱셈(AND에 해당)을 무한히 조합할 수 있다면 원리상 어떠한 계산 가능한 함수나 알고리즘도 암호화된 채로 실행할 수 있음을 의미합니다.&lt;/p>
&lt;p>FHE는 오랫동안 &amp;lsquo;암호계의 성배&amp;rsquo;로 불리며 실현 불가능한 것이 아닌가 하는 이야기도 있었습니다. 하지만 2009년 당시 스탠포드 대학교 박사과정에 있던 &lt;strong>Craig Gentry&lt;/strong>가 이상 격자(Ideal Lattices)를 이용한 최초의 FHE 체계를 제안하며 세계에 충격을 안겨주었습니다.&lt;/p>
&lt;hr>
&lt;h2 id="3-fhe의-수학적-기반-lwe-문제와-ring-lwe">3. FHE의 수학적 기반: LWE 문제와 Ring-LWE
&lt;/h2>&lt;p>현재 주류가 되고 있는 FHE 체계의 대부분은 내양자 컴퓨터 암호(Post-Quantum Cryptography)로도 알려진 &amp;lsquo;격자 기반 암호(Lattice-based Cryptography)&amp;lsquo;의 수학적 난제인 &lt;strong>LWE (Learning With Errors) 문제&lt;/strong>에 기반하고 있습니다.&lt;/p>
&lt;h3 id="lwe-문제의-직관적인-이해">LWE 문제의 직관적인 이해
&lt;/h3>&lt;p>연립일차방정식을 푸는 것은 가우스 소거법 등을 이용하면 간단합니다.&lt;/p>
$$ \begin{cases} 3s_1 + 4s_2 + 2s_3 \equiv 12 \pmod{17} \\ 1s_1 + 9s_2 + 5s_3 \equiv 8 \pmod{17} \\ \vdots \end{cases} $$
&lt;p>하지만 이 방정식의 결과에 아주 약간의 &amp;lsquo;무작위 오차(노이즈)&amp;rsquo; $e$ 를 더하면 어떻게 될까요.&lt;/p>
$$ \begin{cases} 3s_1 + 4s_2 + 2s_3 + e_1 \equiv 13 \pmod{17} \\ 1s_1 + 9s_2 + 5s_3 + e_2 \equiv 7 \pmod{17} \\ \vdots \end{cases} $$
&lt;p>이 오차 $e$ 가 더해지는 것만으로 비밀 변수 벡터 $\vec{s}$ 를 찾아내는 문제는 현재의 슈퍼컴퓨터나 양자컴퓨터를 이용해도 해독하기 어려운 NP-난해(NP-hard) 문제로 변모합니다. 이것이 LWE 문제입니다.&lt;/p>
&lt;h3 id="ring-lwe-문제-rlwe">Ring-LWE 문제 (RLWE)
&lt;/h3>&lt;p>표준 LWE 문제는 행렬 연산을 포함하기 때문에 키의 크기가 매우 크고(기가바이트 단위가 되기도 함) 계산 효율도 나쁘다는 문제가 있었습니다. 이를 해결하기 위해 도입된 것이 다항식 환(Polynomial Ring) 위에서의 연산을 이용하는 &lt;strong>Ring-LWE (RLWE) 문제&lt;/strong>입니다.&lt;/p>
&lt;p>RLWE에서는 원소가 다항식 환 $R_q = \mathbb{Z}_q[x] / (x^N + 1)$ 에 속합니다(여기서 $N$ 은 2의 거듭제곱, $q$ 는 법이 되는 소수).
비밀키를 다항식 $s(x)$ 라 하고, 무작위 다항식 $a(x)$, 작은 노이즈 다항식 $e(x)$ 라고 하면, 공개키는 다음과 같은 쌍이 됩니다.&lt;/p>
$$ (a(x), b(x)) \quad \text{where} \quad b(x) = -a(x) \cdot s(x) + e(x) \pmod q $$
&lt;p>암호화할 때는 이 다항식의 성질을 이용하여 평문 $m(x)$ 를 인코딩하고 암호문을 생성합니다.&lt;/p>
&lt;hr>
&lt;h2 id="4-최대의-장벽-노이즈와-gentry의-부트스트래핑">4. 최대의 장벽 &amp;lsquo;노이즈&amp;rsquo;와 Gentry의 부트스트래핑
&lt;/h2>&lt;p>FHE의 이해에 있어 가장 중요한 개념이 **&amp;lsquo;노이즈의 관리&amp;rsquo;**입니다.&lt;/p>
&lt;p>LWE/RLWE 기반의 암호에서는 안전성을 담보하기 위해 의도적으로 작은 &amp;lsquo;노이즈(오차)&amp;lsquo;를 포함하고 있습니다.
평문 $m$ 의 암호문 $c$ 를 복호화하는 과정은 대략적으로 말하면 다음과 같은 수식으로 표현됩니다.&lt;/p>
$$ D(c) = (c \cdot s) \pmod q = m + \text{noise} $$
&lt;p>복호화 시에는 이 &lt;code>noise&lt;/code> 를 반올림 처리 등으로 제거함으로써 올바른 평문 $m$ 을 얻습니다. 그러나 암호문끼리 동형 연산(특히 곱셈)을 수행하면 이 노이즈가 극적으로 증폭되어 버립니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>동형 덧셈&lt;/strong>: 노이즈는 덧셈적으로 증가합니다($e_1 + e_2$). 이는 비교적 완만한 증가입니다.&lt;/li>
&lt;li>&lt;strong>동형 덧셈에 의한 동형성의 수식 표현&lt;/strong>:
$$ E(m_1) \oplus E(m_2) = E(m_1 + m_2) $$&lt;/li>
&lt;li>&lt;strong>동형 곱셈&lt;/strong>: 노이즈는 곱셈적으로 폭발합니다($e_1 \times e_2$ 등을 포함하기 때문). 몇 번 곱셈하는 것만으로 노이즈가 임계값 $q/2$ 를 넘어버려 올바른 반올림 처리가 불가능해 복호화에 실패합니다.&lt;/li>
&lt;li>&lt;strong>동형 곱셈에 의한 동형성의 수식 표현&lt;/strong>:
$$ E(m_1) \otimes E(m_2) = E(m_1 \times m_2) $$&lt;/li>
&lt;/ul>
&lt;p>이것이 오랫동안 FHE가 실현되지 못하고 SHE(횟수 제한 있음)에 머물러 있었던 이유입니다.&lt;/p>
&lt;h3 id="부트스트래핑bootstrapping의-마법">부트스트래핑(Bootstrapping)의 마법
&lt;/h3>&lt;p>Craig Gentry의 천재적인 공헌은 **&amp;lsquo;부트스트래핑&amp;rsquo;**이라 불리는 노이즈 감소 기법을 발명한 것입니다. 이는 암호학에 있어서 패러다임 전환이었습니다.&lt;/p>
&lt;p>직관적으로는 &amp;lsquo;암호문이 노이즈 투성이가 되어 망가지기 전에, 암호화된 채로 &amp;ldquo;복호화&amp;quot;하여 깨끗하게 만들고 새로운 암호문에 다시 넣는다&amp;rsquo;는 조작입니다.&lt;/p>
&lt;ol>
&lt;li>노이즈가 커진 암호문 $C_{noisy}$ 가 있다고 가정합니다.&lt;/li>
&lt;li>클라이언트는 비밀키 $sk$ 를 &amp;lsquo;공개키로 암호화한 것&amp;rsquo; $E_{pk}(sk)$ (이를 부트스트래핑 키라고 부름)를 미리 서버에 건네줍니다.&lt;/li>
&lt;li>서버는 $C_{noisy}$ 에 대해 동형적으로 **복호화 회로(Decryption Circuit)**를 실행합니다.&lt;/li>
&lt;li>구체적으로는 $E_{pk}(C_{noisy})$ 에 대해 $E_{pk}(sk)$ 를 이용하여 &amp;lsquo;암호화된 공간 내에서의 복호화&amp;rsquo;를 수행합니다.&lt;/li>
&lt;li>이 복호화 회로 자체도 동형 연산이기 때문에 새로운 노이즈를 낳지만, 출력되는 새로운 암호문 $C_{fresh}$ 의 노이즈는 일정한 &amp;lsquo;고정 레벨&amp;rsquo;로 초기화(리셋)됩니다.&lt;/li>
&lt;/ol>
&lt;div class="mermaid">graph LR
A["높은 노이즈 암호문 C_noisy"] --> B["동형 복호화 회로 (Eval_Dec)"]
C["암호화된 비밀키 E(sk)"] --> B
B --> D["낮은 노이즈 암호문 C_fresh"]
style B fill:#ffeeba,stroke:#ffc107&lt;/div>
&lt;p>이 부트스트래핑을 계산 도중에 정기적으로 실행함으로써, 이론상으로는 무한한 깊이의 회로를 계산 가능(FHE의 달성)하게 만들었습니다. 하지만 초기의 Gentry 방식은 이 부트스트래핑 처리 1회당 수십 분에서 수 시간씩 걸리는 등 절망적일 정도로 계산 비용이 높았습니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-fhe의-세대와-주요-체계의-진화">5. FHE의 세대와 주요 체계의 진화
&lt;/h2>&lt;p>FHE의 실용화를 향해 전 세계의 암호학자들이 앞다투어 알고리즘을 개량해왔습니다. 현재 FHE는 크게 4개의 세대/제품군으로 분류됩니다.&lt;/p>
&lt;h3 id="제2세대-정수의-정확한-연산-bgv-bfv">제2세대: 정수의 정확한 연산 (BGV, BFV)
&lt;/h3>&lt;p>2011~2012년에 걸쳐 등장한 &lt;strong>BGV (Brakerski-Gentry-Vaikuntanathan)&lt;/strong> 와 &lt;strong>BFV (Brakerski/Fan-Vercauteren)&lt;/strong> 체계입니다. 이들은 RLWE를 기반으로 하고 있으며 정수의 모듈러 연산(정확한 계산)에 적합합니다.
SIMD (Single Instruction, Multiple Data)와 같은 배칭(Batching) 기술을 지원하고 있어 하나의 거대한 다항식 암호문 속에 수천 개의 데이터 슬롯을 담아 한 번에 병렬 계산할 수 있는 것이 특징입니다.&lt;/p>
&lt;h3 id="제3세대-부트스트래핑의-고속화-gsw-fhew-tfhe">제3세대: 부트스트래핑의 고속화 (GSW, FHEW, TFHE)
&lt;/h3>&lt;p>2013년의 &lt;strong>GSW (Gentry-Sahai-Waters)&lt;/strong> 체계는 FHE의 구조를 보다 단순하게 만들었습니다. 그리고 이를 발전시킨 것이 현재의 주류 중 하나인 &lt;strong>TFHE (Fast Fully Homomorphic Encryption over the Torus)&lt;/strong> 입니다.
TFHE의 특징은 부트스트래핑이 매우 빠르다는(밀리초 단위) 점입니다. 게이트 단위(AND, XOR 등 논리 회로)에서의 연산에 강하며, 암호문의 크기도 비교적 작기 때문에 임의의 논리 회로를 고속으로 평가하는 데 적합합니다.&lt;/p>
&lt;h3 id="제4세대-근사-계산과-기계-학습에의-특화-ckks">제4세대: 근사 계산과 기계 학습에의 특화 (CKKS)
&lt;/h3>&lt;p>2017년 Cheon 등에 의해 제안된 &lt;strong>CKKS (Cheon-Kim-Kim-Song)&lt;/strong> 체계는 현재 AI·기계 학습의 프라이버시 보호에 있어서 결정판이라 할 수 있는 기술입니다.
지금까지의 FHE가 &amp;lsquo;정확한 정수 계산&amp;rsquo;을 고집했던 것에 반해, CKKS는 **&amp;lsquo;부동소수점 수의 근사 계산&amp;rsquo;**을 암호화된 채로 지원합니다. 신경망의 학습이나 추론 등 작은 오차가 허용되는 실수 계산에 있어서 압도적인 성능을 발휘합니다.&lt;/p>
&lt;p>다음 표는 목적별로 어떤 체계를 선택해야 하는지 요약한 것입니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">체계 이름&lt;/th>
&lt;th style="text-align:left">특기인 데이터 형&lt;/th>
&lt;th style="text-align:left">권장되는 유스케이스&lt;/th>
&lt;th style="text-align:left">특징&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>BFV / BGV&lt;/strong>&lt;/td>
&lt;td style="text-align:left">정수 (Integer)&lt;/td>
&lt;td style="text-align:left">정확한 통계 계산, 금융 데이터 집계, DB 검색&lt;/td>
&lt;td style="text-align:left">SIMD 배칭에 의한 높은 처리량&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>CKKS&lt;/strong>&lt;/td>
&lt;td style="text-align:left">실수 (Real/Complex)&lt;/td>
&lt;td style="text-align:left">기계 학습 (DNN, 로지스틱 회귀), 신호 처리&lt;/td>
&lt;td style="text-align:left">근사 계산에 의한 고속화, 리스케일링&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>TFHE&lt;/strong>&lt;/td>
&lt;td style="text-align:left">불리언 값 (Boolean)&lt;/td>
&lt;td style="text-align:left">임의의 논리 회로, 문자열 검색, 비선형 함수의 평가&lt;/td>
&lt;td style="text-align:left">초고속 부트스트래핑 (밀리초 대)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="6-실천-fhe-라이브러리와-개념적인-코드">6. 실천: FHE 라이브러리와 개념적인 코드
&lt;/h2>&lt;p>현재는 암호학에 대한 깊은 지식이 없어도 FHE를 이용할 수 있는 오픈소스 라이브러리가 다수 제공되고 있습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Microsoft SEAL (Simple Encrypted Arithmetic Library)&lt;/strong>: BFV, BGV, CKKS를 지원하는 C++ 라이브러리. 업계 표준 중 하나입니다. Python 바인딩인 &lt;strong>TenSEAL&lt;/strong> 이 AI 엔지니어들 사이에서 인기가 있습니다.&lt;/li>
&lt;li>&lt;strong>Zama (Concrete)&lt;/strong>: TFHE를 기반으로 한 프레임워크. Rust/Python으로 작성할 수 있으며 기존의 PyTorch 모델을 컴파일하여 FHE 상에서 구동하는 기능(Concrete ML)을 제공합니다.&lt;/li>
&lt;li>&lt;strong>OpenFHE&lt;/strong>: PALISADE의 후속으로, 모든 주요 체계를 지원하는 포괄적인 C++ 라이브러리입니다.&lt;/li>
&lt;/ul>
&lt;h3 id="python-tenseal-을-이용한-fhe-프로그래밍-예시">Python (TenSEAL) 을 이용한 FHE 프로그래밍 예시
&lt;/h3>&lt;p>여기서는 CKKS 체계를 이용해 암호화된 채로 실수 벡터를 더하고 곱하는 개념적인 Python 코드의 예시를 보여줍니다.&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;/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="kn">import&lt;/span> &lt;span class="nn">tenseal&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">ts&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="c1"># CKKS 체계를 사용하며 다항식의 차수를 8192로 설정&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">SCHEME_TYPE&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">CKKS&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">poly_modulus_degree&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">8192&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">coeff_mod_bit_sizes&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">60&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">40&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">60&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="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">generate_galois_keys&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">global_scale&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">40&lt;/span> &lt;span class="c1"># 실수의 스케일링 팩터&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">vector1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mf">1.5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">2.5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">3.5&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">vector2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mf">2.0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">3.0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mf">4.0&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"># 평문 벡터를 암호문으로 변환 (원래는 클라이언트 측에서 실행)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enc_v1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ckks_vector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vector1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enc_v2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ckks_vector&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">vector2&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. 서버 측: 암호화된 채로 수행하는 연산 (Data in Use 보호)&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="n">enc_add&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enc_v1&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">enc_v2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">enc_mul&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enc_v1&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">enc_v2&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"># 4. 클라이언트 측: 결과의 복호화&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="n">res_add&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enc_add&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decrypt&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">res_mul&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">enc_mul&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">decrypt&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;복호화된 덧셈 결과: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">res_add&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 class="c1"># 출력 예: [3.5000001, 5.5000001, 7.5000002] (근사 계산이기 때문에 미세한 오차가 포함됨)&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;복호화된 곱셈 결과: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">res_mul&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 class="c1"># 출력 예: [3.0000002, 7.5000005, 14.0000003]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>위의 코드에서 알 수 있듯이, &lt;code>enc_v1 + enc_v2&lt;/code> 처럼 일반적인 Python의 연산자를 오버로딩하여 직관적으로 암호문끼리의 계산을 작성할 수 있습니다. 서버 측에서는 벡터의 내용을 모른 채로 벡터 연산을 완료하고 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="7-fhe의-과제-성능과-하드웨어-가속">7. FHE의 과제: 성능과 하드웨어 가속
&lt;/h2>&lt;p>FHE는 이론적으로 완벽한 보안을 제공하지만, 실용화에 있어서 최대의 과제는 **&amp;lsquo;성능의 오버헤드&amp;rsquo;**입니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>계산의 오버헤드&lt;/strong>: 평문에서의 계산에 비해 암호문에서의 계산은 CPU 상에서 수천 배~수만 배 느려집니다. 다항식의 곱셈이나 부트스트래핑에는 방대한 FFT(고속 푸리에 변환)나 NTT(수론 변환)의 계산이 필요합니다.&lt;/li>
&lt;li>&lt;strong>데이터 크기의 팽창 (Ciphertext Expansion)&lt;/strong>: 수 바이트의 평문이 암호화되면 수 메가바이트가 될 수 있습니다. 이는 메모리 대역폭이나 네트워크 대역폭을 강하게 압박합니다.&lt;/li>
&lt;/ol>
&lt;h3 id="하드웨어에-의한-해결-접근">하드웨어에 의한 해결 접근
&lt;/h3>&lt;p>이 오버헤드를 극복하기 위해 전 세계에서 FHE 전용 하드웨어 가속기(ASIC, FPGA, GPU 대응)의 개발이 진행되고 있습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>GPU 가속&lt;/strong>: NVIDIA 등의 강력한 GPU를 이용해 NTT 연산이나 부트스트래핑을 병렬화하는 연구가 진행되고 있으며 소프트웨어 구현 대비 수십 배의 고속화가 보고되고 있습니다(예: 100x.ai, Zama의 TFHE-rs CUDA backend).&lt;/li>
&lt;li>&lt;strong>DARPA DPRIVE 프로젝트&lt;/strong>: 미국 국방고등연구계획국(DARPA)은 FHE의 계산 속도를 평문 처리의 실행 속도와 동등한 수준(오버헤드 10배 이내)으로 끌어올리기 위한 전용 하드웨어 개발 프로젝트 &amp;lsquo;DPRIVE (Data Protection in Virtual Environments)&amp;lsquo;를 추진하고 있으며, Intel이나 Microsoft, Intellectual Ventures 등이 참여하고 있습니다.&lt;/li>
&lt;li>&lt;strong>FPU (FHE Processing Unit) 의 등장&lt;/strong>: Cornami나 Optalysys 같은 스타트업이 광 컴퓨팅이나 특수한 실리콘 아키텍처를 이용한 FHE 전용 칩 개발에 나서고 있습니다.&lt;/li>
&lt;/ul>
&lt;p>가까운 미래에 AI에서의 NPU(Neural Processing Unit)처럼 서버나 클라우드 인프라에 &amp;lsquo;FPU&amp;rsquo;가 표준 탑재되는 시대가 올지도 모릅니다.&lt;/p>
&lt;hr>
&lt;h2 id="8-기대되는-유스케이스">8. 기대되는 유스케이스
&lt;/h2>&lt;p>FHE가 실용적인 속도에 도달해가는 지금, 다음과 같은 분야에서의 파괴적 혁신이 기대되고 있습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>의료·유전체 분석의 프라이버시 보호&lt;/strong>:
여러 병원이 가진 환자의 진료 기록이나 DNA 데이터를 FHE로 암호화한 채 클라우드의 AI에게 학습시킴으로써 프라이버시 법(HIPAA나 GDPR)을 위반하지 않고 고정밀 암 진단 모델이나 신약 개발을 진행할 수 있습니다.&lt;/li>
&lt;li>&lt;strong>금융 기관의 부정 탐지·자금 세탁 방지 (AML)&lt;/strong>:
경쟁하는 은행끼리 고객의 계좌 정보나 거래 내역을 밝히지 않고 암호화된 상태로 서로의 데이터를 대조하여 거대한 부정 송금 네트워크를 탐지하는 교차 은행 분석이 가능해집니다.&lt;/li>
&lt;li>&lt;strong>안전한 AI 추론 API (MaaS: Model as a Service)&lt;/strong>:
사용자는 자신의 음성이나 얼굴 이미지, 프롬프트를 암호화하여 AI 서비스(ChatGPT와 같은 LLM 등)에 전송합니다. AI 제공자는 사용자의 입력을 일절 알지 못한 채로 답변을 생성하여 암호문으로 반환합니다. 이를 통해 &amp;lsquo;AI에게 개인 정보가 학습되거나 도난당한다&amp;rsquo;는 우려가 완전히 불식됩니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="9-결론-암호의-미래는-보이지-않는-계산으로">9. 결론: 암호의 미래는 &amp;lsquo;보이지 않는 계산&amp;rsquo;으로
&lt;/h2>&lt;p>1970년대에 공개키 암호(RSA)가 발명되어 인터넷 상의 안전한 통신(HTTPS 등)이 가능해진 것처럼, Craig Gentry에 의한 FHE의 발명은 암호의 역사에 있어서 가장 중요한 이정표 중 하나입니다.&lt;/p>
&lt;p>현재 완전동형암호(FHE)는 연구실의 이론에서 벗어나 Microsoft, IBM, Intel, Google, 그리고 많은 스타트업들이 실용화를 향해 치열하게 경쟁하는 단계에 접어들었습니다. 계산 비용이나 데이터 크기라는 과제는 여전히 존재하지만 알고리즘의 세련화와 하드웨어 가속기의 진화에 의해 무어의 법칙을 뛰어넘는 속도로 성능 향상이 이어지고 있습니다.&lt;/p>
&lt;p>수년 뒤, &amp;lsquo;데이터를 암호화한 채로 계산하는&amp;rsquo; 것은 특별한 일이 아니라 클라우드 서비스에서의 표준적인 데이터 보호 모범 사례가 될 것입니다. FHE는 데이터 주도형 사회에서의 &lt;strong>궁극적인 프라이버시와 데이터 활용의 양립&lt;/strong>을 실현하는 차세대 보안의 핵심입니다.&lt;/p></description></item><item><title>타원곡선 암호(ECC)의 수학적 기초와 C++에서의 구현</title><link>http://kenji.blog/ko/p/elliptic-curve-cryptography-math-cpp/</link><pubDate>Fri, 11 Sep 2026 10:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/elliptic-curve-cryptography-math-cpp/</guid><description>&lt;img src="http://kenji.blog/p/elliptic-curve-cryptography-math-cpp/img/eyecatch.jpg" alt="Featured image of post 타원곡선 암호(ECC)의 수학적 기초와 C++에서의 구현" />&lt;h1 id="타원곡선-암호ecc의-수학적-기초와-c에서의-구현">타원곡선 암호(ECC)의 수학적 기초와 C++에서의 구현
&lt;/h1>&lt;p>현대 암호 기술에서 **타원곡선 암호(Elliptic Curve Cryptography: ECC)**는 매우 중요한 역할을 담당하고 있습니다. 우리의 일상적인 인터넷 통신(HTTPS/TLS)부터 스마트폰의 보안 엔클레이브(Secure Enclave), SSH를 통한 서버 인증, FIDO와 같은 패스워드리스 인증, 심지어 비트코인이나 이더리움과 같은 암호화폐에 이르기까지 현대 디지털 사회의 신뢰 기반은 ECC에 의해 지탱되고 있다고 해도 과언이 아닙니다.&lt;/p>
&lt;p>본 기사에서는 이 타원곡선 암호가 어떻게 기능하는지, 그 이면에 있는 아름답고도 난해한 수학적 이론(유한체 상의 대수기하학)에서 출발하여 실제 C++를 이용한 구현 방법, 나아가 부채널 공격(타이밍 공격)을 방지하기 위한 안전한 코딩 기법까지 압도적인 분량으로 철저하게 해설합니다.&lt;/p>
&lt;hr>
&lt;h2 id="1-왜-타원곡선-암호인가-rsa와의-비교">1. 왜 타원곡선 암호인가? (RSA와의 비교)
&lt;/h2>&lt;p>공개키 암호 방식의 대명사라고 하면 오랫동안 &lt;strong>RSA 암호&lt;/strong>를 떠올렸습니다. RSA 암호는 &amp;lsquo;거대한 합성수의 소인수분해의 어려움&amp;rsquo;을 안전성의 근거로 삼고 있습니다. 하지만 컴퓨터의 계산 능력이 향상됨에 따라 안전성을 유지하기 위해서는 RSA의 키 길이(모듈러스의 비트 수)를 지속적으로 늘려야 할 필요성이 생겼습니다. 현재는 최소 2048비트, 더 안전을 기한다면 3072비트나 4096비트의 키 길이가 권장되고 있습니다.&lt;/p>
&lt;p>이에 반해, 타원곡선 암호(ECC)는 **&amp;lsquo;타원곡선 상의 이산대수 문제(ECDLP)&amp;rsquo;**라는 또 다른 수학적 어려움을 안전성의 근거로 삼고 있습니다. ECDLP를 풀기 위한 효율적인 알고리즘(준지수 시간 알고리즘 등)은 현재까지 발견되지 않았으며, 알려진 가장 효율적인 공격 기법조차도 지수 함수적인 시간을 필요로 합니다.&lt;/p>
&lt;p>이러한 성질로 인해 ECC는 &lt;strong>매우 짧은 키 길이로 RSA와 동등한 보안 강도를 구현할 수 있다&lt;/strong>는 결정적인 장점을 가집니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">보안 강도(비트)&lt;/th>
&lt;th style="text-align:center">RSA 암호의 키 길이(비트)&lt;/th>
&lt;th style="text-align:center">타원곡선 암호의 키 길이(비트)&lt;/th>
&lt;th style="text-align:center">키 길이 비율&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">80&lt;/td>
&lt;td style="text-align:center">1024&lt;/td>
&lt;td style="text-align:center">160&lt;/td>
&lt;td style="text-align:center">1:6&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">112&lt;/td>
&lt;td style="text-align:center">2048&lt;/td>
&lt;td style="text-align:center">224&lt;/td>
&lt;td style="text-align:center">1:9&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">128&lt;/td>
&lt;td style="text-align:center">3072&lt;/td>
&lt;td style="text-align:center">256&lt;/td>
&lt;td style="text-align:center">1:12&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">192&lt;/td>
&lt;td style="text-align:center">7680&lt;/td>
&lt;td style="text-align:center">384&lt;/td>
&lt;td style="text-align:center">1:20&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">256&lt;/td>
&lt;td style="text-align:center">15360&lt;/td>
&lt;td style="text-align:center">512&lt;/td>
&lt;td style="text-align:center">1:30&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>위의 표에서 알 수 있듯이, 128비트의 보안 강도(현재 표준적인 강도)를 얻기 위해 RSA에서는 3072비트의 키가 필요하지만, ECC라면 단 256비트면 충분합니다. 이를 통해 계산량 감소, 메모리 사용량 절감, 네트워크 대역폭 절약이 가능해지며, 특히 리소스가 제한된 IoT 기기나 스마트카드 환경에서 압도적인 우위를 자랑합니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-수학적-준비-군론과-유한체의-세계">2. 수학적 준비: 군론과 유한체의 세계
&lt;/h2>&lt;p>타원곡선 암호를 진정으로 이해하기 위해서는 추상대수학(군론 및 체론)의 기본 개념을 파악해 둘 필요가 있습니다. 여기서는 ECC를 구성하기 위한 사전 지식을 간결하게 정리합니다.&lt;/p>
&lt;h3 id="21-군group과-아벨-군">2.1. 군(Group)과 아벨 군
&lt;/h3>&lt;p>**군(Group)**이란 어떤 집합 $G$와 그 집합 상의 이항 연산(여기서는 덧셈 $+$로 합니다)의 쌍 $(G, +)$이며, 다음의 4가지 공리를 만족하는 것을 말합니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>닫힘(Closure)&lt;/strong>: 임의의 $a, b \in G$에 대해 $a + b \in G$이다.&lt;/li>
&lt;li>&lt;strong>결합 법칙(Associativity)&lt;/strong>: 임의의 $a, b, c \in G$에 대해 $(a + b) + c = a + (b + c)$가 성립한다.&lt;/li>
&lt;li>&lt;strong>항등원의 존재(Identity element)&lt;/strong>: 임의의 $a \in G$에 대해 $a + e = e + a = a$가 되는 원소 $e \in G$가 존재한다. 덧셈군의 경우, 이 항등원을 보통 $0$ 또는 $\mathcal{O}$로 표기합니다.&lt;/li>
&lt;li>&lt;strong>역원의 존재(Inverse element)&lt;/strong>: 임의의 $a \in G$에 대해 $a + b = b + a = e$가 되는 원소 $b \in G$가 존재한다. 이 $b$를 $-a$로 표기합니다.&lt;/li>
&lt;/ol>
&lt;p>더 나아가 연산의 순서를 바꾸어도 결과가 변하지 않는, 즉 다음 조건을 만족하는 군을 **아벨 군(가환군)**이라고 부릅니다.&lt;/p>
&lt;ol start="5">
&lt;li>&lt;strong>교환 법칙(Commutativity)&lt;/strong>: 임의의 $a, b \in G$에 대해 $a + b = b + a$가 성립한다.&lt;/li>
&lt;/ol>
&lt;p>타원곡선 상의 점들의 집합은 특정한 덧셈 규칙을 정의함으로써 이 &lt;strong>아벨 군&lt;/strong>을 구성합니다.&lt;/p>
&lt;h3 id="22-유한체finite-field">2.2. 유한체(Finite Field)
&lt;/h3>&lt;p>암호 이론에서는 실수나 복소수처럼 연속적이고 무한한 요소를 가진 체가 아니라, 원소의 개수가 유한한 &lt;strong>유한체(Finite Field)&lt;/strong> 또는 갈루아 체(Galois Field)를 사용합니다.&lt;/p>
&lt;p>가장 기본적인 유한체는 소수 $p$를 사용한 **소체 $\mathbb{F}_p$**입니다. 이는 $\{0, 1, 2, \dots, p-1\}$의 정수 집합에 모듈로 $p$($p$로 나눈 나머지)에서의 사칙연산(덧셈, 뺄셈, 곱셈, 나눗셈)을 정의한 것입니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>덧셈&lt;/strong>: $(a + b) \pmod p$&lt;/li>
&lt;li>&lt;strong>뺄셈&lt;/strong>: $(a - b) \pmod p$&lt;/li>
&lt;li>&lt;strong>곱셈&lt;/strong>: $(a \times b) \pmod p$&lt;/li>
&lt;li>&lt;strong>나눗셈&lt;/strong>: $a \times b^{-1} \pmod p$ (여기서 $b^{-1}$은 모듈로 $p$에서의 $b$의 곱셈 역원)&lt;/li>
&lt;/ul>
&lt;p>**곱셈 역원(Modular Multiplicative Inverse)**의 계산은 암호 구현에서 매우 중요합니다. $b \times b^{-1} \equiv 1 \pmod p$를 만족하는 $b^{-1}$을 구하기 위해서는 주로 다음 두 가지 알고리즘이 사용됩니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>확장 유클리드 호제법(Extended Euclidean Algorithm)&lt;/strong>: 빠르지만, 구현에 따라 처리 시간이 입력값에 의존하기 때문에 타이밍 공격의 위험이 있습니다.&lt;/li>
&lt;li>&lt;strong>페르마의 소정리(Fermat&amp;rsquo;s Little Theorem)&lt;/strong>: $p$가 소수이고 $b \neq 0$일 때, $b^{p-1} \equiv 1 \pmod p$가 성립합니다. 양변을 $b$로 나누면 $b^{p-2} \equiv b^{-1} \pmod p$가 됩니다. 즉, $b$의 $p-2$ 제곱을 계산함으로써 역원을 구할 수 있습니다. 거듭제곱 연산은 상수 시간(Constant-Time)으로 구현하기 쉽기 때문에, 암호 구현에서는 이 방법을 선호합니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="3-타원곡선의-방정식과-기하학">3. 타원곡선의 방정식과 기하학
&lt;/h2>&lt;h3 id="31-바이어슈트라스의-표준형">3.1. 바이어슈트라스의 표준형
&lt;/h3>&lt;p>**타원곡선(Elliptic Curve)**은 일반적으로 다음의 **바이어슈트라스 표준형(Weierstrass normal form)**이라고 불리는 방정식으로 정의되는 평면 곡선입니다.&lt;/p>
$$ y^2 = x^3 + ax + b $$
&lt;p>여기서 $a$와 $b$는 상수이며, 곡선이 특이점(자기 교차점이나 뾰족점)을 가지지 않기(매끄러운 곡선이기) 위한 조건으로 다음의 **판별식(Discriminant) $\Delta$**가 0이 아니어야 합니다.&lt;/p>
$$ \Delta = -16(4a^3 + 27b^2) \neq 0 $$
&lt;p>특이점을 가진 곡선은 암호학적 안전성을 손상시키기 때문에, 반드시 이 조건을 만족하는 계수 $a, b$가 선택됩니다.&lt;/p>
&lt;h3 id="32-무한원점point-at-infinity">3.2. 무한원점(Point at Infinity)
&lt;/h3>&lt;p>타원곡선을 수학적으로 완전한 군으로 만들기 위해, 평면 상의 점에 더해 **&amp;lsquo;무한원점(Point at Infinity)&amp;rsquo;**이라고 불리는 가상의 점을 도입합니다. 이를 $\mathcal{O}$(오)라고 표기합니다.&lt;/p>
&lt;p>무한원점 $\mathcal{O}$는 모든 수직선이 무한히 먼 곳에서 만나는 점으로 정의됩니다. 군론에서 이 무한원점 $\mathcal{O}$는 &lt;strong>덧셈에 대한 항등원&lt;/strong>(0)으로 기능합니다.&lt;/p>
&lt;p>즉, 곡선 상의 임의의 점 $P$에 대해 다음이 성립합니다.
&lt;/p>
$$ P + \mathcal{O} = \mathcal{O} + P = P $$
&lt;p>또한, 점 $P = (x, y)$의 역원 $-P$는 x축에 대해 대칭인 점 $(x, -y)$로 정의됩니다. 따라서:
&lt;/p>
$$ P + (-P) = \mathcal{O} $$
&lt;p>
가 됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="4-타원곡선-상의-군-연산점의-덧셈과-2배-연산">4. 타원곡선 상의 군 연산(점의 덧셈과 2배 연산)
&lt;/h2>&lt;p>타원곡선 암호의 근간을 이루는 것이 바로 곡선 상의 점들 간의 **&amp;lsquo;덧셈(Addition)&amp;rsquo;**이라는 연산입니다. 이는 일반적인 정수의 덧셈과는 다르며 기하학적인 조작을 바탕으로 정의되어 있습니다.&lt;/p>
&lt;h3 id="41-기하학적-덧셈tangent-and-chord-method">4.1. 기하학적 덧셈(Tangent and Chord Method)
&lt;/h3>&lt;p>곡선 상의 서로 다른 두 점 $P$와 $Q$를 더하여 새로운 점 $R$ ($R = P + Q$)을 구하는 절차는 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>점 $P$와 점 $Q$를 지나는 직선(현)을 긋습니다.&lt;/li>
&lt;li>이 직선은 타원곡선과 반드시 또 다른 한 점(이를 $-R$이라 합니다)에서 만납니다. (※ 대수기하학의 정리에 따름)&lt;/li>
&lt;li>교점 $-R$을 x축에 대해 대칭으로 뒤집은 점(y좌표의 부호를 반전시킨 점)이 구하고자 하는 점 $R$이 됩니다.&lt;/li>
&lt;/ol>
&lt;div class="mermaid">graph TD
Step1["P(x1, y1) 와 Q(x2, y2) 를 잇는 직선을 긋는다"] --> Step2["곡선과의 제3의 교점 -R 을 찾는다"]
Step2 --> Step3["-R 을 x축으로 뒤집어, R(x3, y3) 을 얻는다"]
Step3 -.-> Result["이것이 R = P + Q"]&lt;/div>
&lt;h3 id="42-점의-2배-연산point-doubling">4.2. 점의 2배 연산(Point Doubling)
&lt;/h3>&lt;p>점 $P$에 같은 점 $P$를 더하는 경우($P + P = 2P$), 두 점을 지나는 직선을 그을 수 없습니다. 이 경우에는 **점 $P$에서의 곡선의 접선(Tangent)**을 긋습니다.&lt;/p>
&lt;ol>
&lt;li>점 $P$에서의 곡선의 접선을 긋습니다.&lt;/li>
&lt;li>이 접선은 곡선과 또 다른 한 점 $-R$에서 만납니다.&lt;/li>
&lt;li>교점을 x축에 대해 대칭으로 뒤집은 점이 구하고자 하는 점 $R = 2P$가 됩니다.&lt;/li>
&lt;/ol>
&lt;h3 id="43-대수적인-계산-공식">4.3. 대수적인 계산 공식
&lt;/h3>&lt;p>기하학적인 조작을 컴퓨터로 계산할 수 있도록 대수적인 공식으로 옮겨냅니다.
연산은 모두 **유한체 $\mathbb{F}_p$ 상(모듈로 $p$)**에서 이루어집니다.&lt;/p>
&lt;p>점 $P = (x_1, y_1)$, 점 $Q = (x_2, y_2)$라고 합시다.
또한 계산 결과인 점을 $R = P + Q = (x_3, y_3)$이라 합시다.&lt;/p>
&lt;p>직선의 기울기를 $\lambda$(람다)라고 합니다.&lt;/p>
&lt;p>&lt;strong>【케이스 1: $P \neq Q$ 인 경우 (점의 덧셈)】&lt;/strong>
기울기 $\lambda$는 두 점 사이의 변화율입니다.
&lt;/p>
$$ \lambda \equiv \frac{y_2 - y_1}{x_2 - x_1} \pmod p $$
$$ \lambda \equiv (y_2 - y_1) \cdot (x_2 - x_1)^{-1} \pmod p $$
&lt;p>이 $\lambda$를 사용하여 $x_3, y_3$은 다음과 같이 구합니다.
&lt;/p>
$$ x_3 \equiv \lambda^2 - x_1 - x_2 \pmod p $$
$$ y_3 \equiv \lambda(x_1 - x_3) - y_1 \pmod p $$
&lt;p>&lt;strong>【케이스 2: $P = Q$ 인 경우 (점의 2배 연산)】&lt;/strong>
기울기 $\lambda$는 미분을 통해 구한 접선의 기울기가 됩니다. ($y^2 = x^3 + ax + b$를 암묵적으로 미분합니다)
&lt;/p>
$$ 2y \cdot y' = 3x^2 + a \implies y' = \frac{3x^2 + a}{2y} $$
&lt;p>
따라서,
&lt;/p>
$$ \lambda \equiv (3x_1^2 + a) \cdot (2y_1)^{-1} \pmod p $$
&lt;p>$x_3, y_3$의 식은 덧셈과 같은 형태이지만, $x_2 = x_1$이므로 다음과 같이 됩니다.
&lt;/p>
$$ x_3 \equiv \lambda^2 - 2x_1 \pmod p $$
$$ y_3 \equiv \lambda(x_1 - x_3) - y_1 \pmod p $$
&lt;blockquote>
&lt;p>[!IMPORTANT]
이러한 공식에는 $(x_2 - x_1)^{-1}$이나 $(2y_1)^{-1}$과 같은 **나눗셈(모듈로 역원의 계산)**이 포함되어 있습니다. 모듈로 역원의 계산은 계산 비용이 매우 높기 때문에, 실제 구현에서는 나눗셈을 지연시키는 &lt;strong>&amp;lsquo;야코비 좌표계(Jacobian Coordinates)&amp;rsquo;&lt;/strong> 등의 사영 좌표계가 일반적으로 사용됩니다.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h2 id="5-스칼라-곱셈과-타원곡선-이산대수-문제ecdlp">5. 스칼라 곱셈과 타원곡선 이산대수 문제(ECDLP)
&lt;/h2>&lt;p>타원곡선 암호에서 가장 계산량이 많고, 동시에 보안의 핵심을 이루는 연산이 바로 **스칼라 곱셈(Scalar Multiplication)**입니다.&lt;/p>
&lt;h3 id="51-스칼라-곱셈이란">5.1. 스칼라 곱셈이란
&lt;/h3>&lt;p>어떤 점 $P$를 $k$번 더하는 조작을 스칼라 곱셈이라 부르며, $kP$라고 표기합니다.
&lt;/p>
$$ kP = \underbrace{P + P + \dots + P}_{k\text{번}} $$
&lt;p>여기서 $k$는 매우 큰 정수(예를 들어 256비트의 정수)입니다.&lt;/p>
&lt;h3 id="52-타원곡선-이산대수-문제ecdlp">5.2. 타원곡선 이산대수 문제(ECDLP)
&lt;/h3>&lt;p>타원곡선 암호의 안전성은 다음 문제의 어려움에 의존하고 있습니다.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>타원곡선 이산대수 문제(Elliptic Curve Discrete Logarithm Problem: ECDLP)&lt;/strong>
알려진 점 $P$(베이스 포인트)와 계산 결과인 점 $Q$가 주어졌을 때, $Q = kP$를 만족하는 스칼라 $k$를 구하시오.&lt;/p>
&lt;/blockquote>
&lt;p>$k$와 $P$로부터 $Q$를 계산하는 것(정방향)은 후술할 알고리즘을 사용하면 쉽지만(다항식 시간), $P$와 $Q$로부터 $k$를 역산하는 것(역방향)은 전수조사와 같은 무차별 대입 이외에는 효율적인 해법이 없어 사실상 불가능합니다(일방향 함수).
암호 프로토콜에서는 **$k$가 &amp;lsquo;개인키&amp;rsquo;, $Q$가 &amp;lsquo;공개키&amp;rsquo;**에 해당합니다.&lt;/p>
&lt;h3 id="53-double-and-add-알고리즘">5.3. Double-and-Add 알고리즘
&lt;/h3>&lt;p>$k$가 거대한 수(예: $2^{256}$)일 경우, $P$를 우직하게 $k$번 더하는 것은 우주의 수명이 다해도 끝나지 않습니다. 그래서 고속으로 스칼라 곱셈을 수행하기 위해 **Double-and-Add 방법(바이너리 방법)**이 사용됩니다.&lt;/p>
&lt;p>이는 정수의 거듭제곱을 고속으로 계산하는 &amp;lsquo;반복 제곱법&amp;rsquo;의 타원곡선 버전입니다. 스칼라 $k$를 2진수로 표현하고, 최상위 비트부터 차례대로 처리합니다.&lt;/p>
&lt;ol>
&lt;li>결과를 유지할 점 $R$을 $\mathcal{O}$로 초기화한다.&lt;/li>
&lt;li>$k$의 최상위 비트부터 최하위 비트까지 다음을 반복한다:
&lt;ul>
&lt;li>$R$을 2배로 한다 (Point Doubling: $R = 2R$)&lt;/li>
&lt;li>만약 현재 비트가 &lt;code>1&lt;/code>이라면, $R$에 $P$를 더한다 (Point Addition: $R = R + P$)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>이 알고리즘을 통해 계산량은 $O(k)$에서 $O(\log_2 k)$로 극적으로 감소하며, 현실적인 시간(밀리초 단위) 내에 계산이 가능해집니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-타원곡선-디피-헬먼ecdh-키-교환">6. 타원곡선 디피-헬먼(ECDH) 키 교환
&lt;/h2>&lt;p>여기서는 ECC의 가장 대표적인 응용 사례인 &lt;strong>ECDH(Elliptic Curve Diffie-Hellman) 키 교환 프로토콜&lt;/strong>의 작동 원리를 해설합니다. ECDH는 도청이 가능한 통신 채널 상에서 앨리스와 밥이 안전하게 공통의 비밀키(세션 키)를 생성 및 공유하기 위한 메커니즘입니다(TLS 핸드셰이크의 핵심입니다).&lt;/p>
&lt;p>&lt;strong>【전제 파라미터(도메인 파라미터)】&lt;/strong>
양측은 미리 사용할 타원곡선 $E$, 소수 $p$, 그리고 베이스 포인트 $G$를 공유하고 있습니다. (예: NIST P-256이나 secp256k1 등)&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant Alice as "Alice (앨리스)"
participant Bob as "Bob (밥)"
Note over Alice,Bob: "공개 파라미터: 곡선 E, 베이스 포인트 G, 소수 p"
Alice->>Alice: "개인키 d_A (랜덤한 정수) 를 생성"
Alice->>Alice: "공개키 Q_A = d_A * G 를 계산"
Bob->>Bob: "개인키 d_B (랜덤한 정수) 를 생성"
Bob->>Bob: "공개키 Q_B = d_B * G 를 계산"
Alice->>Bob: "공개키 Q_A 를 전송 (평문)"
Bob->>Alice: "공개키 Q_B 를 전송 (평문)"
Alice->>Alice: "공유 시크릿 S = d_A * Q_B 를 계산"
Bob->>Bob: "공유 시크릿 S = d_B * Q_A 를 계산"
Note over Alice,Bob: "S = d_A * (d_B * G) = d_B * (d_A * G) = (d_A * d_B) * G"
Note over Alice,Bob: "양측의 계산 결과 S 는 완전히 일치한다!"&lt;/div>
&lt;p>도청자(이브)는 통신 경로 상에 흐르는 $G$, $Q_A$, $Q_B$를 가로챌 수 있지만, ECDLP의 어려움으로 인해 $Q_A = d_A \cdot G$로부터 앨리스의 개인키 $d_A$를 알아낼 수는 없습니다. 또한, $Q_A$와 $Q_B$를 곱해도 공유키 $S$가 되지 않으므로 도청자는 $S$를 계산할 수 없습니다.&lt;/p>
&lt;hr>
&lt;h2 id="7-구현의-함정-부채널-공격과-대책">7. 구현의 함정: 부채널 공격과 대책
&lt;/h2>&lt;p>이론적으로 완벽한 암호 알고리즘이라 하더라도 그것을 프로그램으로 구현하는 과정에서 취약점이 발생할 수 있습니다. 그것이 바로 **&amp;lsquo;부채널 공격(Side-Channel Attack)&amp;rsquo;**입니다.&lt;/p>
&lt;h3 id="71-타이밍-공격timing-attack">7.1. 타이밍 공격(Timing Attack)
&lt;/h3>&lt;p>앞서 언급한 Double-and-Add 알고리즘을 되돌아봅시다.&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;/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">// 취약한 Double-and-Add 의 의사 코드
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">Point&lt;/span> &lt;span class="n">R&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Infinity&lt;/span>&lt;span class="p">;&lt;/span>
&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">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">255&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="n">i&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="n">i&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">R&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PointDoubling&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R&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">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">i&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="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">R&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PointAddition&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">);&lt;/span> &lt;span class="c1">// 비트가 1일 때만 실행됨!
&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 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>1&lt;/code>일 때는 Point Addition이 실행되기 때문에 비트가 &lt;code>0&lt;/code>일 때보다 &lt;strong>계산 시간이 약간 더 길어집니다&lt;/strong>. 또한 프로세서의 분기 예측이나 캐시 메모리의 동작도 변하게 됩니다.
공격자가 이 계산 시간(혹은 소비 전력)의 미세한 차이를 수천 번 통계적으로 관측함으로써, &lt;strong>개인키 $k$의 비트열을 1비트씩 완전히 복원&lt;/strong>해 낼 수 있습니다. 이것이 타이밍 공격입니다.&lt;/p>
&lt;h3 id="72-constant-time-구현-montgomery-ladder">7.2. Constant-Time 구현: Montgomery Ladder
&lt;/h3>&lt;p>타이밍 공격을 방지하기 위해서는, &lt;strong>개인키의 비트 값과 관계없이 실행되는 명령어의 시퀀스와 계산 시간이 항상 일정한(Constant-Time)&lt;/strong> 알고리즘을 채택해야 합니다.&lt;/p>
&lt;p>그 대표적인 예가 **Montgomery Ladder(몽고메리 사다리)**입니다.&lt;/p>
&lt;div class="mermaid">graph TD
Start["초기화: R0 = O, R1 = P"] --> LoopStart["각 비트 i 에 대해 (최상위부터)"]
LoopStart --> Cond{"k_i 의 값은?"}
Cond -->|0| Branch0["R1 = R0 + R1&lt;br>R0 = 2 * R0"]
Cond -->|1| Branch1["R0 = R0 + R1&lt;br>R1 = 2 * R1"]
Branch0 --> LoopEnd["다음 비트로"]
Branch1 --> LoopEnd
LoopEnd --> LoopStart
LoopStart -.->|"모든 비트 완료"| End["종료: R0 가 결과 (kP)"]&lt;/div>
&lt;p>Montgomery Ladder의 아름다운 점은 비트가 &lt;code>0&lt;/code>이든 &lt;code>1&lt;/code>이든, **&amp;ldquo;항상 1번의 Point Addition과 1번의 Point Doubling&amp;rdquo;**이 실행된다는 것입니다. 이를 통해 계산 시간의 데이터 의존성이 완전히 배제됩니다.&lt;/p>
&lt;p>단, 분기(&lt;code>if (k_i == 0)&lt;/code>) 자체가 존재하면 컴파일러의 최적화나 CPU의 분기 예측에 의해 실행 시간이 변동할 위험이 남아 있습니다. 따라서 실제 Constant-Time 구현에서는 조건 분기(&lt;code>if&lt;/code>문)를 배제하고, **비트 연산을 이용한 조건부 스왑(Conditional Swap)**을 활용합니다.&lt;/p>
&lt;hr>
&lt;h2 id="8-c를-이용한-타원곡선-암호의-구현">8. C++를 이용한 타원곡선 암호의 구현
&lt;/h2>&lt;p>이제부터는 이론을 C++ 코드로 구현해 보겠습니다. 실용적인 암호 라이브러리(OpenSSL이나 libsodium 등)는 고도화된 어셈블리 최적화나 야코비 좌표계를 사용하지만, 여기서는 수학적 이해를 돕기 위해 &lt;strong>아핀(Affine) 좌표계를 이용한 알기 쉬운 Constant-Time 구현&lt;/strong>의 뼈대를 제시합니다.&lt;/p>
&lt;p>거대한 정수의 연산에는 &lt;code>boost::multiprecision::cpp_int&lt;/code>를 사용한다고 가정합니다.&lt;/p>
&lt;h3 id="81-모듈로-연산과-역원">8.1. 모듈로 연산과 역원
&lt;/h3>&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;/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;stdexcept&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;boost/multiprecision/cpp_int.hpp&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">boost&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">multiprecision&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">// 예시로 secp256k1 의 소수 p 와 파라미터
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="nf">p&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F&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="k">const&lt;/span> &lt;span class="n">cpp_int&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">const&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">7&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">// 양의 나머지를 반환하는 모듈로 연산
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="nf">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&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="n">cpp_int&lt;/span> &lt;span class="n">r&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">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">return&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="n">r&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="nl">m&lt;/span> &lt;span class="p">:&lt;/span> &lt;span class="n">r&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">// 거듭제곱 나머지 계산 (x^y 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="n">cpp_int&lt;/span> &lt;span class="nf">powerMod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="n">base&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">exp&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&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="n">cpp_int&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">mod&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="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">%&lt;/span> &lt;span class="mi">2&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">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&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">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&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">base&lt;/span> &lt;span class="o">*&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">/=&lt;/span> &lt;span class="mi">2&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">// 페르마의 소정리를 이용한 모듈로 역원
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="nf">modInverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&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="c1">// m이 소수라는 전제: n^(m-2) ≡ n^(-1) 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="k">return&lt;/span> &lt;span class="n">powerMod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="mi">2&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="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="82-점의-표현과-군-연산덧셈2배-연산">8.2. 점의 표현과 군 연산(덧셈·2배 연산)
&lt;/h3>&lt;p>무한원점을 플래그로 관리하는 &lt;code>Point&lt;/code> 구조체와 덧셈 공식을 구현합니다.&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;/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="k">struct&lt;/span> &lt;span class="nc">Point&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">;&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">isInfinity&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">// 무한원점의 생성
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">isInfinity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">true&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>&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">Point&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cpp_int&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">y&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">:&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">y&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">isInfinity&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">false&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="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">// 타원곡선 상의 점의 덧셈 (R = P + Q)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">Point&lt;/span> &lt;span class="nf">pointAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">const&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">Q&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">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">isInfinity&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">Q&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">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">isInfinity&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">return&lt;/span> &lt;span class="n">P&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">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">P&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">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&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="k">return&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">();&lt;/span> &lt;span class="c1">// P + (-P) = 무한원점
&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="n">cpp_int&lt;/span> &lt;span class="n">lambda&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">P&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">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">x&lt;/span> &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&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">// Point Doubling (P = Q 인 경우)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// lambda = (3x^2 + a) / 2y
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">P&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">P&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">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">cpp_int&lt;/span> &lt;span class="n">den&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">modInverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">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">lambda&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">den&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&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">// Point Addition (P != Q 인 경우)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// lambda = (y2 - y1) / (x2 - x1)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">num&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Q&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">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">cpp_int&lt;/span> &lt;span class="n">den&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">modInverse&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Q&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">P&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">p&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">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">lambda&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">num&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">den&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&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="n">cpp_int&lt;/span> &lt;span class="n">x3&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lambda&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">lambda&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">P&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">Q&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">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">cpp_int&lt;/span> &lt;span class="n">y3&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mod&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lambda&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">P&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">x3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&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">return&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y3&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;h3 id="83-constant-time-conditional-swap-의-구현">8.3. Constant-Time Conditional Swap 의 구현
&lt;/h3>&lt;p>개인키의 비트 값에 따라 변수의 내용을 교환할 때, &lt;code>if&lt;/code>문을 사용하지 않고 비트 연산(마스크)만으로 교환을 수행합니다. 이를 통해 실행 경로가 완전히 일정해집니다.&lt;/p>
&lt;blockquote>
&lt;p>[!TIP]
실제 구현에서는 &lt;code>cpp_int&lt;/code>와 같이 동적으로 할당되는 다정밀도 정수 클래스는 Constant-Time 처리에 적합하지 않습니다. 메모리 할당이나 배열 크기의 변동에 따라 타이밍 정보가 유출되기 때문입니다. 실용적인 라이브러리에서는 고정 길이(예: uint64_t × 4요소 배열)로 표현하고, 비트 단위의 마스크 처리를 구현합니다. 다음은 개념적인 예시입니다.&lt;/p>
&lt;/blockquote>
&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;/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">// 개념적인 Constant-Time Swap (고정 길이 정수를 가정한 경우)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// bit 가 1 이면 P1 과 P2 를 교환하고, 0 이면 교환하지 않음
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">void&lt;/span> &lt;span class="nf">cswap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">P1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">P2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">bit&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">// bit 는 0 또는 1. 마스크는 bit=1 이면 모든 비트가 1(0xFF..), 0이면 모든 비트가 0.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// (여기서는 설명을 위해 고정 길이 BigInt 클래스의 각 워드를 w라 가정)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="cm">/*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> uint64_t mask = 0 - (uint64_t)bit;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> for (int i = 0; i &amp;lt; NUM_WORDS; i++) {
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> uint64_t dummy = mask &amp;amp; (P1.x.words[i] ^ P2.x.words[i]);
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> P1.x.words[i] ^= dummy;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> P2.x.words[i] ^= dummy;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> // y좌표나 isInfinity 플래그도 동일하게 처리
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> }
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cm"> */&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">// ※ boost::multiprecision 에서 완벽한 상수 시간 스왑은 어렵지만,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&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">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">bit&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">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">P1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">P2&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="84-montgomery-ladder-에-의한-스칼라-곱셈">8.4. Montgomery Ladder 에 의한 스칼라 곱셈
&lt;/h3>&lt;p>앞서 설명한 &lt;code>pointAdd&lt;/code>와 &lt;code>cswap&lt;/code>을 결합하여 안전한 스칼라 곱셈을 구현합니다.&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;/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">// 스칼라 곱셈 k * P (Montgomery Ladder 방식)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">Point&lt;/span> &lt;span class="nf">scalarMultiply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">const&lt;/span> &lt;span class="n">Point&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">P&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cpp_int&lt;/span> &lt;span class="n">k&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">Point&lt;/span> &lt;span class="n">R0&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Point&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="n">Point&lt;/span> &lt;span class="n">R1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">P&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">// k 의 비트 길이를 가져옴 (secp256k1 이라면 256 비트)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="n">numBits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">256&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">int&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">numBits&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">i&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="n">i&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="c1">// i번째 비트의 값을 가져옴 (0 또는 1)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint8_t&lt;/span> &lt;span class="n">bit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">static_cast&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="kt">uint8_t&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">bit_test&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">?&lt;/span> &lt;span class="mi">1&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// bit == 1 이라면 R0 와 R1 을 스왑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">cswap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">R1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bit&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">// 항상 같은 연산을 실행 (Point Addition 과 Point Doubling)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">R1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pointAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">R1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">R0&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pointAdd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">R0&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">// bit == 1 이었던 경우, 상태를 원래대로 되돌리기 위해 다시 스왑
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">cswap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">R0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">R1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">bit&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="n">R0&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>이 구현 로직을 통해, 스칼라 $k$의 각 비트가 &lt;code>0&lt;/code>이든 &lt;code>1&lt;/code>이든, 각 루프 이터레이션 내에서 실행되는 연산(&lt;code>cswap&lt;/code> $\to$ &lt;code>pointAdd&lt;/code> $\to$ &lt;code>pointAdd&lt;/code> $\to$ &lt;code>cswap&lt;/code>)은 완전히 동일한 흐름이 되어, 타이밍이나 캐시 액세스 패턴의 차이를 통한 비밀 정보의 유출을 강력하게 방지할 수 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="9-요약">9. 요약
&lt;/h2>&lt;p>타원곡선 암호(ECC)는 처음 보았을 때는 &amp;ldquo;어째서 직선을 긋고 교점을 뒤집는 기하학적인 조작이 암호가 되는 걸까?&amp;rdquo; 하고 신기하게 느껴질 수도 있습니다. 하지만 유한체라는 이산적인 세계로 매핑함으로써 훌륭한 일방향 함수(이산대수 문제)를 구축할 수 있다는, 수학과 암호학의 기적적인 융합의 산물입니다.&lt;/p>
&lt;p>본 기사에서는 다음의 중요한 요점들을 해설했습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>RSA 대비 우위성&lt;/strong>: 매우 짧은 키 길이로 강력한 보안을 제공하며, 현대의 모바일·IoT 시대에 최적화되어 있습니다.&lt;/li>
&lt;li>&lt;strong>군론과 유한체의 기초&lt;/strong>: ECC의 토대가 되는 수학적 구조.&lt;/li>
&lt;li>&lt;strong>덧셈과 2배 연산의 공식&lt;/strong>: 바이어슈트라스 방정식을 이용한 대수적인 군 연산의 구현 방법.&lt;/li>
&lt;li>&lt;strong>부채널 공격의 위협&lt;/strong>: 개인키의 비트에 의존하는 조건 분기가 치명적인 취약점을 발생시킨다는 점.&lt;/li>
&lt;li>&lt;strong>Constant-Time 구현&lt;/strong>: Montgomery Ladder와 Conditional Swap을 이용해 하드웨어 수준의 동작을 균일화하여 공격을 방지하는 C++ 코딩 기법.&lt;/li>
&lt;/ol>
&lt;p>실제 프로덕션 환경에서 동작하는 암호 라이브러리를 직접 만드는 것은 보안상 위험이 매우 높기 때문에 권장되지 않습니다(&amp;ldquo;Don&amp;rsquo;t roll your own crypto&amp;rdquo;). 하지만 그 내부에서 동작하고 있는 알고리즘과 수학적 배경을 깊이 이해하는 것은 더 안전하고 퍼포먼스가 높은 시스템을 설계 및 운영하는 엔지니어에게 둘도 없이 강력한 무기가 될 것입니다.&lt;/p>
&lt;p>다음 기사에서는 이 타원곡선을 이용한 디지털 서명 알고리즘인 **ECDSA(Elliptic Curve Digital Signature Algorithm)**의 메커니즘이나, 비트코인에서 채택된 **Schnorr 서명(슈노르 서명)**에 대해 한층 더 깊이 파고들어 보고자 합니다.&lt;/p></description></item><item><title>GNFS(일반 수체 체)를 뛰어넘는 알고리즘은 존재하는가?</title><link>http://kenji.blog/ko/p/beyond-gnfs-integer-factorization-algorithms/</link><pubDate>Fri, 11 Sep 2026 09:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/beyond-gnfs-integer-factorization-algorithms/</guid><description>&lt;img src="http://kenji.blog/p/beyond-gnfs-integer-factorization-algorithms/img/eyecatch.jpg" alt="Featured image of post GNFS(일반 수체 체)를 뛰어넘는 알고리즘은 존재하는가?" />&lt;h2 id="1-서론-소인수분해와-현대-암호의-근간">1. 서론: 소인수분해와 현대 암호의 근간
&lt;/h2>&lt;p>현대 사회에서 인터넷 통신의 안전성은 공개키 암호인 RSA 암호의 안전성에 크게 의존하고 있습니다. 그리고 RSA 암호의 안전성은 &amp;lsquo;거대한 합성수의 소인수분해의 어려움&amp;rsquo;이라는 수학적 가정에 기반을 두고 있습니다. 만약 극도로 효율적인 소인수분해 알고리즘이 발견된다면, 전 세계의 통신 인프라는 뿌리째 흔들리게 될 것입니다.&lt;/p>
&lt;p>현재 고전 컴퓨터를 이용한 거대 정수의 소인수분해에서 가장 빠르고 강력한 알고리즘으로 군림하고 있는 것이 **일반 수체 체(GNFS: General Number Field Sieve)**입니다. GNFS는 1980년대 후반에 제안된 특수 수체 체(SNFS)를 확장하는 형태로 탄생했으며, 오늘날까지 RSA-768이나 RSA-250과 같은 거대한 합성수의 소인수분해 기록을 수립해 왔습니다.&lt;/p>
&lt;p>그러나 암호학자들과 수학자들은 항상 다음과 같은 의문을 품고 있습니다. &amp;ldquo;GNFS를 뛰어넘는 고전적 알고리즘은 존재하는가?&amp;rdquo;, &amp;ldquo;고전 컴퓨터의 한계는 어디까지인가?&amp;rdquo;, 그리고 &amp;ldquo;양자 컴퓨터는 어떻게 이 상황을 타파할 것인가?&amp;rdquo;&lt;/p>
&lt;p>본 기사에서는 GNFS 이면에 있는 심연의 수학적 구조를 철저하게 해부하고, 다항식 선택, 체질(Sieving) 처리, 블록 위더만(Block-Wiedemann) 방법을 통한 선형대수 단계 등 상세한 기술적 분석을 수행합니다. 나아가 Coppersmith의 개선 등을 통한 GNFS의 확장 기법에 대해 고찰하고, 고전적인 준지수 시간(Sub-exponential time) 알고리즘과 양자 다항식 시간 알고리즘의 결정적인 차이를 수리적 관점에서 비교 및 해설합니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-점근적-시간-복잡도와-l-표기법l-notation">2. 점근적 시간 복잡도와 L 표기법(L-notation)
&lt;/h2>&lt;p>소인수분해 알고리즘의 계산 복잡도를 평가할 때, 표준적인 다항식 시간 표기($O(n^k)$ 등)가 아니라 입력 $n$의 자릿수에 대한 준지수 시간을 표현하기 위해 **L 표기법(L-notation)**이 사용됩니다. L 표기법은 다음과 같이 정의됩니다.&lt;/p>
$$
L_n[\alpha, c] = \exp \left( (c + o(1)) (\ln n)^\alpha (\ln \ln n)^{1-\alpha} \right)
$$
&lt;p>여기서 $n$은 소인수분해의 대상이 되는 정수, $\ln n$은 자연로그이며 $n$의 비트 길이에 비례합니다.&lt;/p>
&lt;ul>
&lt;li>$\alpha = 0$인 경우: $L_n[0, c] = \exp(c \ln \ln n) = (\ln n)^c$가 되어, 비트 길이에 대한 **다항식 시간(Polynomial time)**을 나타냅니다.&lt;/li>
&lt;li>$\alpha = 1$인 경우: $L_n[1, c] = \exp(c \ln n) = n^c$가 되어, 비트 길이에 대한 **지수 시간(Exponential time)**을 나타냅니다.&lt;/li>
&lt;li>$0 &lt; \alpha &lt; 1$인 경우: 다항식 시간과 지수 시간의 중간에 위치하는 **준지수 시간(Sub-exponential time)**이 됩니다.&lt;/li>
&lt;/ul>
&lt;p>과거 소인수분해 알고리즘의 진화는 이 $\alpha$ 값을 서서히 줄여온 역사이기도 했습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>연분수법(CFRAC)이나 다중 다항식 이차 체(MPQS)&lt;/strong>: $\alpha = 1/2$ 클래스에 속하며, 계산 복잡도는 $L_n[1/2, 1]$ 정도입니다.&lt;/li>
&lt;li>&lt;strong>일반 수체 체(GNFS)&lt;/strong>: $\alpha = 1/3$을 달성하여, 현재 알려진 고전 알고리즘 중 가장 빠른 $L_n[1/3, (64/9)^{1/3}]$이라는 계산 복잡도를 자랑합니다.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="3-gnfs일반-수체-체의-알고리즘-전모와-수학적-구조">3. GNFS(일반 수체 체)의 알고리즘 전모와 수학적 구조
&lt;/h2>&lt;p>GNFS는 매우 복잡하고 고도화된 수학적 기반을 가지고 있습니다. 기본 아이디어는 페르마의 소정리나 이차 체(QS)의 연장선에 있으며, 합동식 $X^2 \equiv Y^2 \pmod n$을 만족하면서 $X \not\equiv \pm Y \pmod n$이 되는 자명하지 않은 쌍 $(X, Y)$를 찾음으로써 $n$의 인수 $\gcd(X-Y, n)$을 도출해내는 것입니다.&lt;/p>
&lt;p>하지만 GNFS의 진수는 이를 유리수체 $\mathbb{Q}$에서만 수행하는 것이 아니라, 대수체(Algebraic Number Field)라 불리는 확대체 $\mathbb{Q}(\alpha)$와 유리수체 양쪽에서 동시에 &amp;lsquo;매끄러운 수(Smooth numbers)&amp;lsquo;를 탐색하고, 준동형 사상을 통해 합동 관계를 구축한다는 점에 있습니다.&lt;/p>
&lt;p>GNFS의 과정은 크게 5개의 단계로 나뉩니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["소인수분해 문제 (입력 n)"] --> B["1. 다항식 선택 (Polynomial Selection)"]
B --> C["2. 체질 처리 (Sieving Phase)"]
C --> D["3. 필터링 (Filtering Phase)"]
D --> E["4. 선형대수 (Linear Algebra Phase)"]
E --> F["5. 제곱근 (Square Root Phase)"]
F --> G["소인수 p, q 출력"]&lt;/div>
&lt;h3 id="31-1단계-다항식-선택-polynomial-selection">3.1 1단계: 다항식 선택 (Polynomial Selection)
&lt;/h3>&lt;p>GNFS의 성공은 적절한 다항식의 선택에 크게 의존합니다. 목표는 공통 근 $m$을 가지는 두 개의 기약다항식 $f_1(x)$(유리수 측)와 $f_2(x)$(대수 측)를 찾는 것입니다. 즉,
$f_1(m) \equiv f_2(m) \equiv 0 \pmod n$
을 만족합니다.&lt;/p>
&lt;p>통상적으로 유리수 측의 다항식은 1차식 $f_1(x) = x - m$을 선택하고, 대수 측의 다항식 $f_2(x)$는 차수 $d$(일반적으로 5나 6)의 모닉(monic) 다항식을 선택합니다. 가장 고전적인 접근법은 &lt;strong>Base-$m$ 메서드&lt;/strong>입니다.
$n$의 $1/(d+1)$ 제곱에 가까운 정수 $m = \lfloor n^{1/(d+1)} \rfloor$을 선택하고, $n$을 $m$진수로 전개합니다.
$n = c_d m^d + c_{d-1} m^{d-1} + \dots + c_1 m + c_0$
이를 통해 다항식 $f_2(x) = c_d x^d + c_{d-1} x^{d-1} + \dots + c_0$을 얻습니다. 명백하게 $f_2(m) = n \equiv 0 \pmod n$이 됩니다.&lt;/p>
&lt;p>그러나 현대의 구현에서는 &lt;strong>Kleinjung의 알고리즘&lt;/strong>이 사용됩니다. 이는 다항식의 계수가 극단적으로 커지지 않게 하면서(Skewness 최적화), 대수적 성질(Murphy&amp;rsquo;s $E$ value나 $\alpha$-value)을 최적화하고, 체질(Sieving) 처리에서 매끄러운 수가 쉽게 생성될 수 있는 다항식을 탐색합니다. 이 단계에만 막대한 계산 자원이 투입됩니다.&lt;/p>
&lt;h3 id="32-2단계-체질-처리-sieving-phase">3.2 2단계: 체질 처리 (Sieving Phase)
&lt;/h3>&lt;p>다항식이 결정되면 알고리즘에서 가장 계산 부하가 큰 &amp;lsquo;체(Sieving)&amp;rsquo; 단계에 진입합니다. 여기서는 쌍 $(a, b)$를 탐색합니다. 이 쌍은 서로소이며, 다음 두 값이 동시에 &amp;lsquo;매끄러울(Smooth)&amp;rsquo; 것이 요구됩니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>유리수 측의 노름(Norm)&lt;/strong>: $F_1(a, b) = b \cdot f_1(a/b) = a - bm$&lt;/li>
&lt;li>&lt;strong>대수 측의 노름(Norm)&lt;/strong>: $F_2(a, b) = b^d \cdot f_2(a/b)$&lt;/li>
&lt;/ol>
&lt;p>&amp;lsquo;매끄럽다&amp;rsquo;는 것은 지정된 상한(Sieve bound) 이하의 소수로만 소인수분해 가능하다는 것을 의미합니다. 유리수 측의 소수 기반(Factor base)과 대수 측의 소수 기반을 준비하여, 거대한 탐색 공간 위에서 에라토스테네스의 체와 같은 요령으로 매끄러운 수를 효율적으로 찾아냅니다.
현재는 **격자 체(Lattice Sieving)**라 불리는 기법이 주류를 이루고 있으며, 특정 소수 $q$를 고정하여 유리수 측과 대수 측 모두가 $q$의 배수가 되는 부분 격자 상의 $(a, b)$ 쌍만을 체로 걸러냄으로써 극도로 높은 효율을 실현하고 있습니다.&lt;/p>
&lt;h3 id="33-3단계-필터링-filtering-phase">3.3 3단계: 필터링 (Filtering Phase)
&lt;/h3>&lt;p>체질 처리에서 발견된 매끄러운 관계식(Relation)의 수는 수억, 수십억 개에 달합니다. 하지만 이들은 쓸모없는 정보도 많이 포함하고 있습니다.
필터링의 목적은 거대한 희소 행렬(Sparse Matrix)을 구축하면서 그 차원을 가능한 한 작게 만드는 것입니다.&lt;/p>
&lt;p>구체적으로 다음과 같은 조작을 수행합니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Singleton removal&lt;/strong>: 1번만 나타나는 소인수를 포함하는 관계식을 삭제합니다.&lt;/li>
&lt;li>&lt;strong>Clique removal / Merging&lt;/strong>: 2번 이상 나타나는 소인수를 가진 관계식끼리 곱하여 변수를 소거하고, 밀도는 더 높으면서도 차원이 작은 방정식계로 축약합니다.&lt;/li>
&lt;/ul>
&lt;p>이를 통해 수십억 행의 행렬이 수천만 행 수준의 거대한 희소 행렬 $\mathbf{A}$(원소는 체 $\mathbb{F}_2$ 위의 0과 1)로 압축됩니다.&lt;/p>
&lt;h3 id="34-4단계-선형대수-linear-algebra-phase">3.4 4단계: 선형대수 (Linear Algebra Phase)
&lt;/h3>&lt;p>여기서는 방정식 $\mathbf{A} \mathbf{x} \equiv \mathbf{0} \pmod 2$의 자명하지 않은 해 벡터 $\mathbf{x}$를 찾습니다. 즉, 거대한 희소 행렬의 좌측 영공간(Left Nullspace)을 구하는 문제입니다.&lt;/p>
&lt;p>행렬의 크기가 극단적으로 크기 때문에 통상적인 가우스 소거법($O(N^3)$)으로는 도저히 계산이 불가능합니다. 그래서 크릴로프 부분공간법(Krylov subspace method)의 일종인 반복법이 사용됩니다. 역사적으로는 **블록 란초스법(Block Lanczos)**이 사용되어 왔지만, 현재의 분산 컴퓨팅 환경에서는 통신 오버헤드를 극적으로 줄일 수 있는 **블록 위더만법(Block Wiedemann Algorithm)**이 주류입니다.&lt;/p>
&lt;p>블록 위더만법은 행렬 $\mathbf{A}$와 벡터 수열로부터 최소 다항식을 계산하고, 벌리캠프-매시(Berlekamp-Massey) 알고리즘을 이용하여 영공간의 기저를 구성합니다. 이 단계는 병렬화가 매우 어려워 슈퍼컴퓨터나 대규모 클러스터의 밀결합 통신 네트워크를 요구하는, GNFS 최대의 병목 현상 중 하나입니다.&lt;/p>
&lt;h3 id="35-5단계-제곱근-square-root-phase">3.5 5단계: 제곱근 (Square Root Phase)
&lt;/h3>&lt;p>선형대수의 해로부터 유리수 측과 대수 측 각각에서 &amp;lsquo;완전제곱&amp;rsquo;이 되는 곱을 구성합니다.
유리수 측에서는 $\prod (a-bm)$이 어떤 정수 $X$의 제곱 $X^2$이 되고, 대수 측에서는 대응하는 이디얼(ideal)의 곱이 대수체 상에서 완전제곱 $\gamma^2$이 됩니다.
이 $\gamma$를 대수체 위에서 계산하고, 유리정수환으로의 준동형 사상 $\phi: \alpha \mapsto m \pmod n$을 적용함으로써 합동식:
$X^2 \equiv \phi(\gamma)^2 \equiv Y^2 \pmod n$
을 얻게 됩니다.&lt;/p>
&lt;p>대수체 상의 제곱근 계산에는 &lt;strong>몽고메리의 방법(Montgomery&amp;rsquo;s Method)&lt;/strong> 등의 복잡한 알고리즘이 사용되며, 대수적 정수론의 깊은 지식이 요구됩니다. 최종적으로 $\gcd(X-Y, n)$을 계산하여 자명하지 않은 인수를 얻게 되면 소인수분해가 완료됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="4-gnfs를-뛰어넘는-고전-알고리즘은-존재하는가">4. GNFS를 뛰어넘는 고전 알고리즘은 존재하는가?
&lt;/h2>&lt;p>현재까지 일반 정수에 대한 소인수분해에서 점근적 계산 복잡도가 $L_n[1/3, c]$를 밑도는 고전 알고리즘은 발견되지 않았습니다. 그러나 이론적, 실천적 한계를 돌파하기 위한 몇 가지 시도나 파생 알고리즘이 존재합니다.&lt;/p>
&lt;h3 id="41-다중-수체-체-mnfs-multiple-number-field-sieve">4.1 다중 수체 체 (MNFS: Multiple Number Field Sieve)
&lt;/h3>&lt;p>GNFS를 확장한 접근법으로 D. Coppersmith에 의한 **다중 수체 체(MNFS)**가 있습니다. GNFS에서는 2개의 다항식(유리수 측과 대수 측)을 사용하지만, MNFS에서는 1개의 유리수 측 다항식에 대해 여러 개의 다른 대수 측 다항식을 동시에 사용합니다.&lt;/p>
$$ f_1(x), f_{2,1}(x), f_{2,2}(x), \dots, f_{2,V}(x) $$
&lt;p>여러 개의 대수체를 이용함으로써 각 체질 단계에서 &amp;lsquo;어느 한 대수체에서 매끄러워질&amp;rsquo; 확률을 비약적으로 높일 수 있습니다. Coppersmith는 이 접근법을 통해 계산 복잡도 $L_n[1/3, c]$의 상수 $c$를 약간 감소시키는 데 성공했습니다.
구체적으로, GNFS의 상수가 $c = (64/9)^{1/3} \approx 1.923$인 반면, MNFS를 최적화하면 $c \approx 1.902$ 정도까지 계산량을 줄일 수 있음이 이론적으로 증명되었습니다.
그러나 실제 적용에 있어서는 여러 체를 관리하는 오버헤드가 크기 때문에, 실용적 규모의 RSA 모듈러스에 대한 결정적인 돌파구에는 이르지 못했습니다.&lt;/p>
&lt;h3 id="42-l_n14-클래스의-알고리즘은-가능한가">4.2 $L_n[1/4]$ 클래스의 알고리즘은 가능한가?
&lt;/h3>&lt;p>소인수분해의 고전적 알고리즘의 한계에 대해 수학자들 사이에서 오랜 세월 논의되어 온 주제가 &amp;ldquo;지수 $\alpha = 1/4$인 알고리즘은 존재하는가?&amp;ldquo;라는 질문입니다.
현재의 GNFS나 그 파생형은 체를 통한 &amp;lsquo;매끄러움의 탐색&amp;rsquo;이라는 틀에 강하게 얽매여 있으며, 이 패러다임 안에서는 $\alpha = 1/3$이 한계라고 널리 믿어지고 있습니다. 딕만 함수(Dickman function)를 이용한 매끄러운 정수의 분포 확률 해석에서도, 현재의 대수체 구성법과 체의 조합으로는 아무리 최적화해도 $O(L_n[1/3])$의 벽을 넘을 수 없다고 여겨집니다.&lt;/p>
&lt;p>만약 $L_n[1/4]$ 혹은 고전적인 다항식 시간 알고리즘이 존재한다고 가정한다면, 그것은 GNFS와 같은 &amp;lsquo;매끄러움 기반&amp;rsquo;의 접근법과는 전혀 다른, 현재 인류가 생각지도 못한 새로운 수학적 구조(예를 들어, 타원곡선 암호에 대한 Schoof의 알고리즘과 같은 더 고차원적인 대수기하학적 접근 등)에 의존해야 할 것입니다. 그러나 현재로서는 그러한 징후가 보이지 않습니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-양자-컴퓨터에-의한-돌파구-쇼어의-알고리즘">5. 양자 컴퓨터에 의한 돌파구: 쇼어의 알고리즘
&lt;/h2>&lt;p>고전 컴퓨터가 $L_n[1/3]$의 벽에 직면해 있는 반면, 계산 모델 자체를 근본적으로 바꿈으로써 이 벽을 산산조각 낸 것이 1994년 Peter Shor가 발표한 **쇼어의 알고리즘(Shor&amp;rsquo;s Algorithm)**입니다.&lt;/p>
&lt;h3 id="51-양자-다항식-시간의-충격">5.1 양자 다항식 시간의 충격
&lt;/h3>&lt;p>쇼어의 알고리즘은 소인수분해 문제를 &amp;lsquo;위수 발견 문제(Order Finding Problem)&amp;lsquo;로 귀결시킵니다. 어떤 정수 $a$에 대해, 함수 $f(x) = a^x \pmod n$의 주기(위수) $r$을 찾는 문제입니다.
고전 컴퓨터에서는 이 주기를 찾기 위해 지수 함수적인 시간을 요구하지만, 양자 컴퓨터 상의 **양자 위상 추정(QPE: Quantum Phase Estimation)**과 **양자 푸리에 변환(QFT: Quantum Fourier Transform)**을 이용하면 모든 상태의 중첩(Superposition)에 대해 병렬로 평가를 수행하여 높은 확률로 주기 $r$을 추출할 수 있습니다.&lt;/p>
&lt;p>계산 복잡도 측면에서 볼 때, 쇼어의 알고리즘의 실행 시간은 &lt;strong>양자 다항식 시간&lt;/strong>, 구체적으로 다음과 같습니다.
&lt;/p>
$$ O((\log n)^3) $$
&lt;p>
최근의 최적화된 회로 구현을 고려하면 $O((\log n)^2 \log \log n)$까지 단축 가능하다고 알려져 있습니다.&lt;/p>
&lt;div class="mermaid">graph LR
A["고전 알고리즘 (GNFS)"] -->|한계| B["준지수 시간 L_n[1/3]"]
C["양자 알고리즘 (Shor)"] -->|돌파구| D["다항식 시간 O((log n)^3)"]
B --> E["RSA 암호의 지속적 사용 (키 길이 증가)"]
D --> F["RSA 암호의 완전 붕괴"]&lt;/div>
&lt;h3 id="52-고전적-준지수-시간-vs-양자-다항식-시간">5.2 고전적 준지수 시간 vs 양자 다항식 시간
&lt;/h3>&lt;p>이 두 계산 복잡도 클래스의 차이는 현실 세계의 암호 보안에서 결정적인 의미를 갖습니다.&lt;/p>
&lt;p>예를 들어 RSA-2048(2048비트의 합성수)을 소인수분해하는 경우를 생각해 봅시다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>GNFS (고전)&lt;/strong>: $n \approx 2^{2048}$을 $L_n[1/3, 1.923]$에 대입하면 약 $2^{112}$번의 연산이 필요합니다. 이는 현재 지구상의 모든 계산 자원을 결집하더라도 우주의 수명 이상의 시간이 걸리는 천문학적인 계산량입니다.&lt;/li>
&lt;li>&lt;strong>Shor&amp;rsquo;s Algorithm (양자)&lt;/strong>: $O((\log n)^3)$ 알고리즘에서는 $2048^3 \approx 8.5 \times 10^9$번 정도의 논리 게이트 조작으로 끝납니다. 이는 적절한 하드웨어(수백만 개의 물리 양자 비트와 오류 정정 능력을 갖춘 범용 양자 컴퓨터)가 존재한다면 단 몇 시간에서 며칠 만에 계산이 완료됨을 의미합니다.&lt;/li>
&lt;/ul>
&lt;p>&amp;lsquo;지수 $\alpha=1/3$&amp;lsquo;의 준지수 시간에서 &amp;lsquo;다항식 시간&amp;rsquo;으로의 패러다임 전환은, 키 길이를 늘려 안전성을 담보한다는 기존 암호학의 전략을 무력화시킵니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-결론-차세대에-대한-전망">6. 결론: 차세대에 대한 전망
&lt;/h2>&lt;p>&amp;ldquo;GNFS를 뛰어넘는 고전 알고리즘은 존재하는가?&amp;ldquo;라는 질문에 대한 현재 과학계의 합의는 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>실용적인 개선은 계속되나 점근적인 비약은 없다&lt;/strong>: MNFS나 다항식 선택의 최적화, 블록 위더만법의 병렬화 등 GNFS의 상수항 $c$를 개선하려는 시도는 계속되고 있습니다. 그러나 $\alpha = 1/3$을 밑도는 고전적 알고리즘이 발견될 가능성은 극히 낮다고 여겨집니다.&lt;/li>
&lt;li>&lt;strong>고전 컴퓨터 상에서의 RSA 안전성은 여전히 견고하다&lt;/strong>: GNFS의 계산량은 여전히 막대하며, RSA-2048이나 RSA-4096은 고전 컴퓨터의 공격에 대해 앞으로 수십 년간 안전성을 유지할 것입니다.&lt;/li>
&lt;li>&lt;strong>진정한 위협은 양자 알고리즘이다&lt;/strong>: 계산 복잡도의 벽을 넘은 것은 양자역학의 원리에 기반한 쇼어의 알고리즘입니다. 이로 인해 세계는 양자 내성 암호(PQC: Post-Quantum Cryptography)로의 전환을 강요받고 있습니다. 격자 기반 암호나 해시 기반 암호 등 양자 컴퓨터로도 해독이 어려운(다항식 시간으로 풀 수 없는) 새로운 수학적 문제로의 전환이 현재 암호학의 최전선이 되고 있습니다.&lt;/li>
&lt;/ol>
&lt;p>일반 수체 체(GNFS)는 인류가 고전 수학과 알고리즘 설계의 극한까지 도전하여 도달한 &amp;lsquo;최고 도달점&amp;rsquo; 중 하나입니다. GNFS의 심오한 수학적 구조를 이해하는 것은 단순히 암호 해독의 역사를 배우는 것을 넘어, 계산 복잡도 이론이나 대수적 정수론의 아름다움을 접하는 지적 탐구의 여정이기도 합니다. 양자 컴퓨터가 실용화되는 그날까지 GNFS는 최강의 소인수분해 알고리즘으로서의 왕좌를 계속 지킬 것입니다.&lt;/p></description></item><item><title>쇼어의 알고리즘을 Python으로 시뮬레이션해 보았다</title><link>http://kenji.blog/ko/p/shors-algorithm-simulation-python/</link><pubDate>Fri, 11 Sep 2026 08:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/shors-algorithm-simulation-python/</guid><description>&lt;img src="http://kenji.blog/p/shors-algorithm-simulation-python/img/eyecatch.jpg" alt="Featured image of post 쇼어의 알고리즘을 Python으로 시뮬레이션해 보았다" />&lt;h1 id="1-서론-양자-컴퓨터가-가져올-암호의-위기">1. 서론: 양자 컴퓨터가 가져올 암호의 위기
&lt;/h1>&lt;p>현대 인터넷 사회에서 보안의 대부분은 &lt;strong>공개키 암호 방식&lt;/strong>(특히 RSA 암호)에 의존하고 있습니다. 우리가 온라인 쇼핑에서 신용카드 정보를 전송할 때나 기밀성이 높은 데이터를 주고받을 때, 그 통신 내용은 RSA 암호에 의해 강력하게 보호받고 있습니다.&lt;/p>
&lt;p>RSA 암호 안전성의 근거는 &amp;ldquo;&lt;strong>거대한 정수의 소인수분해는 고전 컴퓨터(우리가 평소 사용하는 PC나 슈퍼컴퓨터)로는 극히 어렵다&lt;/strong>&amp;ldquo;라는 수학적인 사실에 의존하고 있습니다. 그러나 1994년 피터 쇼어(Peter Shor)가 발표한 &amp;ldquo;&lt;strong>쇼어의 알고리즘(Shor&amp;rsquo;s Algorithm)&lt;/strong>&amp;ldquo;은 이 전제를 근본부터 뒤엎는 것이었습니다. 쇼어의 알고리즘을 대규모 양자 컴퓨터에서 실행하면 고전 컴퓨터로는 우주의 나이 이상의 시간이 걸리는 소인수분해를 불과 수 분에서 수 시간 만에 풀 수 있다는 것이 수학적으로 증명된 것입니다.&lt;/p>
&lt;p>본 문서에서는 이 쇼어의 알고리즘이 어떻게 소인수분해를 고속으로 수행하는지, 그 수학적인 구조부터 Python과 양자 계산 프레임워크인 &lt;strong>Qiskit&lt;/strong>을 이용한 구체적인 시뮬레이션 구현까지 철저하고 상세하게 해설해 나가겠습니다.&lt;/p>
&lt;hr>
&lt;h1 id="2-계산량의-극적인-변화-지수-함수에서-다항식-시간으로">2. 계산량의 극적인 변화: 지수 함수에서 다항식 시간으로
&lt;/h1>&lt;p>왜 소인수분해가 어려운 것일까요? 고전 컴퓨터에서 최고의 소인수분해 알고리즘으로 알려진 &amp;lsquo;일반 수체 체(General Number Field Sieve, GNFS)&amp;lsquo;를 사용하더라도 그 계산량은 준지수 함수적이 됩니다.&lt;/p>
&lt;p>자릿수가 $N$인 합성수를 소인수분해하는 데 걸리는 시간 계산량은 고전적인 기법에서는 다음과 같습니다.&lt;/p>
$$ O\left(\exp\left( c (\log N)^{1/3} (\log \log N)^{2/3} \right)\right) $$
&lt;p>이 때문에 키 길이를 늘리는(예를 들어 2048비트나 4096비트로 만드는) 것만으로도 고전 컴퓨터로 해독하는 데 수천 년, 수만 년이라는 비현실적인 시간이 걸리게 됩니다.&lt;/p>
&lt;p>그러나 양자 컴퓨터에서 &lt;strong>쇼어의 알고리즘&lt;/strong>을 사용하면 계산량은 입력 비트 수 $\log N$에 대해 다항식 시간으로 극적으로 단축됩니다.&lt;/p>
$$ O((\log N)^3) $$
&lt;p>이것은 비트 수를 2배로 늘렸을 때, 고전 컴퓨터에서는 계산 시간이 천문학적으로 증가하는 반면, 양자 컴퓨터에서는 계산 시간이 기껏해야 8배 정도밖에 늘어나지 않음을 의미합니다. 이 **지수 함수 시간에서 다항식 시간으로의 계산량 클래스 단축(BQP 클래스로의 포함)**이야말로 쇼어 알고리즘의 진정한 대단함입니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["입력 크기(비트 수) N의 증가"] --> B{"알고리즘의 선택"}
B -->|고전: 일반 수체 체| C["준지수 함수적 증가 O(exp(...))"]
B -->|양자: 쇼어의 알고리즘| D["다항식 시간 O((log N)^3)"]
C --> E["수천 년~수십억 년 (해독 불가능)"]
D --> F["수 분~수 시간 (현실적인 시간 내 해독)"]&lt;/div>
&lt;hr>
&lt;h1 id="3-알고리즘의-전체상과-수학적-배경">3. 알고리즘의 전체상과 수학적 배경
&lt;/h1>&lt;p>쇼어의 알고리즘은 사실 모든 것을 양자 컴퓨터로 하는 것은 아닙니다. 고전 컴퓨터를 통한 전처리·후처리와 양자 컴퓨터를 통한 핵심 부분(주기 발견 알고리즘)의 연계를 통해 이루어집니다.&lt;/p>
&lt;p>알고리즘의 전체적인 흐름은 다음과 같습니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["입력: 소인수분해하고 싶은 합성수 N"] --> B["a &lt; N 인 난수 a 를 선택"]
B --> C{"gcd(a, N) > 1 ?"}
C -- "Yes" --> D["자명한 인수 gcd(a, N) 을 출력하고 종료"]
C -- "No" --> E["양자 알고리즘으로 f(x) = a^x mod N 의 주기 r 을 찾음"]
E --> F{"r 이 짝수이고 a^(r/2) ≢ -1 mod N ?"}
F -- "No" --> B
F -- "Yes" --> G["인수 p = gcd(a^(r/2) - 1, N), q = gcd(a^(r/2) + 1, N) 을 계산"]
G --> H["출력: p, q"]&lt;/div>
&lt;h2 id="소인수분해에서-주기-발견-문제로의-귀착">소인수분해에서 주기 발견 문제로의 귀착
&lt;/h2>&lt;p>쇼어의 천재적인 번뜩임은 &amp;ldquo;&lt;strong>소인수분해 문제&lt;/strong>&amp;ldquo;를 &amp;ldquo;&lt;strong>주기 발견 문제(Order Finding Problem)&lt;/strong>&amp;ldquo;로 변환한 것에 있습니다.&lt;/p>
&lt;p>정수 $N$(소인수분해하고 싶은 수)과 서로소인 정수 $a$($1 &lt; a &lt; N$)를 생각해 봅시다. 다음과 같은 모듈러 지수 함수를 정의합니다.&lt;/p>
$$ f(x) = a^x \bmod N $$
&lt;p>이 함수는 어떤 주기 $r$을 가집니다. 즉, 임의의 $x$에 대하여 $f(x+r) = f(x)$가 성립합니다. 특히 $x=0$일 때,&lt;/p>
$$ a^r \equiv 1 \pmod N $$
&lt;p>이 되는 최소의 양의 정수 $r$을 &amp;ldquo;$a$의 $N$을 법으로 하는 위수(Order)&amp;ldquo;라고 부릅니다. 이 주기 $r$을 찾을 수 있다면, 다음과 같이 소인수를 도출해 낼 수 있습니다.&lt;/p>
&lt;p>식을 변형하면,
&lt;/p>
$$ a^r - 1 \equiv 0 \pmod N $$
&lt;p>
만약 $r$이 짝수라면, 합차 공식을 사용하여 인수분해할 수 있습니다.
&lt;/p>
$$ (a^{r/2} - 1)(a^{r/2} + 1) \equiv 0 \pmod N $$
&lt;p>이것은 $N$이 $(a^{r/2} - 1)$ 또는 $(a^{r/2} + 1)$ 중 하나와 공약수를 가짐을 의미합니다(단, $a^{r/2} \not\equiv -1 \pmod N$이라는 조건을 만족해야 합니다). 따라서 유클리드 호제법을 사용하여,&lt;/p>
$$ p = \gcd(a^{r/2} - 1, N) $$
$$ q = \gcd(a^{r/2} + 1, N) $$
&lt;p>을 계산하면, $N$의 비자명한 소인수 $p, q$를 찾을 수 있는 것입니다. 이 계산(최대공약수 계산이나 난수 생성)은 고전 컴퓨터에서 매우 고속으로 수행할 수 있습니다. 문제는 &lt;strong>주기 $r$을 어떻게 고속으로 찾을 것인가&lt;/strong> 하는 점으로 좁혀집니다. 고전 컴퓨터에서는 이 주기 $r$을 찾는 것 자체에 지수 함수적인 시간이 소요되어 버립니다. 여기서 양자 컴퓨터가 나설 차례가 됩니다.&lt;/p>
&lt;hr>
&lt;h1 id="4-양자-알고리즘-부분-주기-발견의-구조">4. 양자 알고리즘 부분: 주기 발견의 구조
&lt;/h1>&lt;p>양자 컴퓨터를 사용하여 주기 $r$을 찾기 위한 서브루틴은 다음의 4가지 단계로 구성됩니다.&lt;/p>
&lt;div class="mermaid">graph LR
subgraph "양자 상태의 전이"
S1["|0⟩|0⟩ (초기화)"] --> S2["H 게이트: 중첩 Σ|x⟩|0⟩"]
S2 --> S3["오라클 U: Σ|x⟩|a^x mod N⟩"]
S3 --> S4["QFT: 간섭에 의한 주기 추출"]
S4 --> S5["측정: 근사치 y 획득"]
end&lt;/div>
&lt;h2 id="1단계-양자-레지스터의-초기화와-중첩">1단계: 양자 레지스터의 초기화와 중첩
&lt;/h2>&lt;p>먼저, 2개의 양자 레지스터를 준비합니다. 제1 레지스터는 상태를 입력하기 위한 것이고, 제2 레지스터는 함수의 계산 결과를 저장하기 위한 것입니다.
초기 상태는 모두 $|0\rangle$입니다.&lt;/p>
$$ |\psi_0\rangle = |0\rangle_1 |0\rangle_2 $$
&lt;p>제1 레지스터의 모든 양자 비트에 아다마르 게이트(Hadamard Gate)를 적용하여, 생각할 수 있는 모든 입력 $x$ ($0$에서 $Q-1$까지, $Q=2^n$)에 대해 동일한 확률의 중첩 상태를 만들어 냅니다.&lt;/p>
$$ |\psi_1\rangle = \frac{1}{\sqrt{Q}} \sum_{x=0}^{Q-1} |x\rangle_1 |0\rangle_2 $$
&lt;p>이로 인해 양자 컴퓨터는 한 번의 연산으로 $Q$개의 모든 입력에 대한 상태를 동시에 유지하게 됩니다. 이것이 &lt;strong>양자 병렬성&lt;/strong>의 강력한 원천입니다.&lt;/p>
&lt;h2 id="2단계-오라클-함수모듈러-거듭제곱의-적용">2단계: 오라클 함수(모듈러 거듭제곱)의 적용
&lt;/h2>&lt;p>다음으로, 양자 연산 회로 $U_f$를 사용하여 함수 $f(x) = a^x \bmod N$을 계산하고 그 결과를 제2 레지스터에 저장합니다.&lt;/p>
$$ |\psi_2\rangle = \frac{1}{\sqrt{Q}} \sum_{x=0}^{Q-1} |x\rangle_1 |a^x \bmod N\rangle_2 $$
&lt;p>이 시점에서 제1 레지스터와 제2 레지스터는 &lt;strong>양자 얽힘(Entanglement)&lt;/strong> 상태에 있습니다. 만약 (가정하여) 제2 레지스터를 관측하여 특정 값 $k = a^{x_0} \bmod N$을 얻었다고 하면, 제1 레지스터의 상태는 그 값 $k$를 주는 $x$의 중첩 상태로 붕괴됩니다. 함수의 주기가 $r$이므로, 남는 상태는 $x_0, x_0+r, x_0+2r, \dots$ 이라는 $r$ 간격의 값이 됩니다.&lt;/p>
$$ |\psi_3\rangle = \sqrt{\frac{r}{Q}} \sum_{j=0}^{M-1} |x_0 + j r\rangle_1 |k\rangle_2 $$
&lt;p>그러나 우리는 $x_0$을 알고 싶은 것이 아니라 주기 $r$ 자체를 알고 싶어 합니다. 이 상태에서 $r$을 직접 관측하는 것은 불가능합니다. 그래서 양자 푸리에 변환을 사용합니다.&lt;/p>
&lt;h2 id="3단계-양자-푸리에-변환qft에-의한-위상-간섭">3단계: 양자 푸리에 변환(QFT)에 의한 위상 간섭
&lt;/h2>&lt;p>제1 레지스터에 대해 **양자 푸리에 변환(Quantum Fourier Transform, QFT)**을 적용합니다. QFT는 고전적인 이산 푸리에 변환의 양자 버전으로, 상태 벡터의 진폭을 변환합니다. 기저 상태 $|x\rangle$에 대한 QFT의 작용은 다음과 같이 정의됩니다.&lt;/p>
$$ QFT |x\rangle = \frac{1}{\sqrt{Q}} \sum_{y=0}^{Q-1} \omega^{xy} |y\rangle $$
&lt;p>여기서 $\omega = e^{2\pi i / Q}$입니다.&lt;/p>
&lt;p>QFT를 적용하면 상태의 진폭이 간섭을 일으킵니다. 수학적인 세부 사항은 생략하겠지만, 주기 $r$을 갖는 상태에 대해 QFT를 적용하면 파동이 **보강 간섭(Constructive Interference)**을 일으키는 것은 $y$가 $Q/r$의 정수배에 극히 가까운 값일 때뿐입니다. 그 이외의 상태는 **상쇄 간섭(Destructive Interference)**에 의해 확률 진폭이 상쇄되어 0에 가까워집니다.&lt;/p>
&lt;h2 id="4단계-측정과-연분수-전개">4단계: 측정과 연분수 전개
&lt;/h2>&lt;p>마지막으로 제1 레지스터를 측정합니다. 측정에 의해 얻어지는 값 $y$는 높은 확률로 다음 조건을 만족합니다.&lt;/p>
$$ y \approx c \frac{Q}{r} \implies \frac{y}{Q} \approx \frac{c}{r} $$
&lt;p>($c$는 $0 \le c &lt; r$인 미지의 정수입니다)&lt;/p>
&lt;p>얻어진 유리수 $y/Q$에 대해 고전 알고리즘인 **연분수 전개(Continued Fraction Expansion)**를 적용함으로써 근사 분수 $c/r$을 계산하고, 분모에서 주기 $r$을 추출합니다.&lt;/p>
&lt;hr>
&lt;h1 id="5-python과-qiskit을-이용한-시뮬레이션-구현">5. Python과 Qiskit을 이용한 시뮬레이션 구현
&lt;/h1>&lt;p>이론만으로는 실감이 나지 않으므로, 실제로 Python과 IBM의 양자 계산 프레임워크인 &lt;strong>Qiskit&lt;/strong>을 사용하여 쇼어의 알고리즘을 시뮬레이션해 봅시다.&lt;/p>
&lt;p>여기서는 가장 고전적이고 유명한 예인 **&amp;quot;$N=15$를 $a=7$을 사용하여 소인수분해하기&amp;rdquo;**라는 시나리오를 구현합니다.&lt;/p>
&lt;h2 id="실행-환경-준비">실행 환경 준비
&lt;/h2>&lt;p>미리 Qiskit을 설치해 둡니다.&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;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install qiskit qiskit-aer numpy
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="python-구현-코드의-전체상">Python 구현 코드의 전체상
&lt;/h2>&lt;p>다음 코드는 $N=15, a=7$에 특화된 쇼어의 알고리즘 구현 예입니다. 범용적인 모듈러 거듭제곱 회로를 구성하는 것은 현재의 시뮬레이터에서는 계산 비용이 너무 높기 때문에, 특정한 $a=7$인 경우의 게이트 동작을 하드코딩하고 있습니다.&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;span class="lnt"> 97
&lt;/span>&lt;span class="lnt"> 98
&lt;/span>&lt;span class="lnt"> 99
&lt;/span>&lt;span class="lnt">100
&lt;/span>&lt;span class="lnt">101
&lt;/span>&lt;span class="lnt">102
&lt;/span>&lt;span class="lnt">103
&lt;/span>&lt;span class="lnt">104
&lt;/span>&lt;span class="lnt">105
&lt;/span>&lt;span class="lnt">106
&lt;/span>&lt;span class="lnt">107
&lt;/span>&lt;span class="lnt">108
&lt;/span>&lt;span class="lnt">109
&lt;/span>&lt;span class="lnt">110
&lt;/span>&lt;span class="lnt">111
&lt;/span>&lt;span class="lnt">112
&lt;/span>&lt;span class="lnt">113
&lt;/span>&lt;span class="lnt">114
&lt;/span>&lt;span class="lnt">115
&lt;/span>&lt;span class="lnt">116
&lt;/span>&lt;span class="lnt">117
&lt;/span>&lt;span class="lnt">118
&lt;/span>&lt;span class="lnt">119
&lt;/span>&lt;span class="lnt">120
&lt;/span>&lt;span class="lnt">121
&lt;/span>&lt;span class="lnt">122
&lt;/span>&lt;span class="lnt">123
&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="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">qiskit&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">QuantumCircuit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">qiskit_aer&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">AerSimulator&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">qiskit.visualization&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">plot_histogram&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">fractions&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">Fraction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">math&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. 역 양자 푸리에 변환 (QFT†) 을 구성하는 함수&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">qft_dagger&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="s2">&amp;#34;&amp;#34;&amp;#34;n 양자 비트의 역 양자 푸리에 변환 회로를 생성한다&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">QuantumCircuit&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="c1"># 순서를 반전하기 위한 SWAP 게이트&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">qubit&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&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="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">qubit&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">qubit&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="c1"># 제어 위상 게이트와 H 게이트의 적용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">j&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&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">for&lt;/span> &lt;span class="n">m&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cp&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pi&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="nb">float&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">m&lt;/span>&lt;span class="p">)),&lt;/span> &lt;span class="n">m&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">j&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">j&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;QFT_dagger&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="n">qc&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. 7^x mod 15 의 제어 모듈러 거듭제곱 연산을 구성하는 함수&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">c_amod15&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">power&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;특정 a와 거듭제곱에 대한 제어 U 게이트를 생성한다(N=15 전용)&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">QuantumCircuit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">power&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># a=7인 경우의 7^x mod 15 의 하드코딩 논리&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">in&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&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">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&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="k">if&lt;/span> &lt;span class="n">a&lt;/span> &lt;span class="ow">in&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">8&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&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">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&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">3&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="n">a&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">11&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swap&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&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="n">a&lt;/span> &lt;span class="ow">in&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">11&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">13&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">to_gate&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">name&lt;/span> &lt;span class="o">=&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">a&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">^&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">power&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> mod 15&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c_U&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">U&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">control&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="n">c_U&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="k">def&lt;/span> &lt;span class="nf">shor_circuit&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">n_count&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_count: 제어 레지스터의 비트 수&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 타겟 레지스터는 0~15 를 표현하기 위해 4비트&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">QuantumCircuit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">n_count&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="k">for&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">h&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&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 레지스터(타겟 레지스터)를 |1&amp;gt; (0001) 로 초기화&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">n_count&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"># 제어 모듈러 거듭제곱 연산(오라클)의 적용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">q&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 2^q 승의 연산을 적용&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">c_amod15&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="n">q&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="n">q&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">+&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">n_count&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&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">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">qft_dagger&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&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">qc&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">measure&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="nb">range&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n_count&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="n">qc&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="k">if&lt;/span> &lt;span class="vm">__name__&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;__main__&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="n">N&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">15&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="mi">7&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n_count&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">8&lt;/span> &lt;span class="c1"># 제어 레지스터에 8 양자 비트를 사용 (Q=256)&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">, a=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">, 제어 양자 비트 수=&lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">n_count&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"># 회로의 생성&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">qc&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">shor_circuit&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">n_count&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"># 시뮬레이터에서의 실행&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">sim&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">AerSimulator&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 최신 Qiskit에서는 transpile을 권장&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kn">from&lt;/span> &lt;span class="nn">qiskit&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">transpile&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">compiled_circuit&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">transpile&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">qc&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">sim&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">job&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">sim&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">run&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">compiled_circuit&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">shots&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">1024&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">job&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">result&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">counts&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">result&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_counts&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="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&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 class="k">for&lt;/span> &lt;span class="n">bitstring&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">count&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">counts&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">items&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">bitstring&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2">: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">count&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"># 고전적 후처리: 연분수 전개에 의한 주기 r의 특정&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="s2">&amp;#34;&lt;/span>&lt;span class="se">\n&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 class="n">phases&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="k">for&lt;/span> &lt;span class="n">output&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">counts&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 비트열을 10진수로 변환&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">decimal&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 위상 = 측정값 / 2^n_count&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">phase&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">decimal&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="n">n_count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">phases&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">append&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">phase&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=15&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">frac&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Fraction&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">phase&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">limit_denominator&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">r&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">frac&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">denominator&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;관측값: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">decimal&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">3d&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> | 위상: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">phase&lt;/span>&lt;span class="si">:&lt;/span>&lt;span class="s2">.4f&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> | 연분수: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">frac&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> | 추정 주기 r = &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">r&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"># 주기 r이 짝수이고 유효한 결과를 가져오는지 확인&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">r&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">guess1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">math&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="mi">2&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">N&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">guess2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">math&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">gcd&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">r&lt;/span>&lt;span class="o">//&lt;/span>&lt;span class="mi">2&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">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="n">guess1&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">N&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="ow">or&lt;/span> &lt;span class="n">guess2&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&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; =&amp;gt; 성공! &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"> 의 소인수는 &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">guess1&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> 와(과) &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">guess2&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 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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; =&amp;gt; 자명한 인수뿐임. 다시 시도.&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="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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34; =&amp;gt; 주기가 홀수이므로 실패.&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;h2 id="코드의-해설과-실행-결과의-해석">코드의 해설과 실행 결과의 해석
&lt;/h2>&lt;p>위의 코드를 실행하면 제어 레지스터의 측정 결과로서 높은 확률로 특정 피크(관측값)를 얻을 수 있습니다. &lt;code>n_count=8&lt;/code>($Q=256$)인 경우, 이상적인 양자 컴퓨터(또는 시뮬레이터)라면 관측값으로 &lt;code>0&lt;/code>, &lt;code>64&lt;/code>, &lt;code>128&lt;/code>, &lt;code>192&lt;/code>와 같은 수치가 압도적인 확률로 출현합니다.&lt;/p>
&lt;p>이것들을 $Q=256$으로 나누면 위상 $y/Q$는 각각 $0.0$, $0.25$, $0.5$, $0.75$가 됩니다.
이 위상을 연분수 전개하면:&lt;/p>
&lt;ul>
&lt;li>$0.25 \to 1/4$ (추정 주기 $r=4$)&lt;/li>
&lt;li>$0.50 \to 1/2$ (추정 주기 $r=2$)&lt;/li>
&lt;li>$0.75 \to 3/4$ (추정 주기 $r=4$)&lt;/li>
&lt;/ul>
&lt;p>여기서 얻어진 주기 $r=4$를 사용하여 소인수를 계산합니다.
$a=7, r=4$이므로,
$p = \gcd(7^2 - 1, 15) = \gcd(48, 15) = 3$
$q = \gcd(7^2 + 1, 15) = \gcd(50, 15) = 5$&lt;/p>
&lt;p>훌륭하게 $15 = 3 \times 5$의 소인수분해에 성공했습니다.&lt;/p>
&lt;blockquote>
&lt;p>[!TIP]
측정값으로 $y=128$(위상 $0.5$)을 얻은 경우, 분모는 $2$가 되어 참값인 주기 $r=4$가 아니라 그 약수를 얻게 됩니다. 이런 경우에는 알고리즘을 여러 번 실행하거나 얻어진 $r$의 배수를 조사함으로써 진짜 주기에 도달할 수 있습니다.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h1 id="6-실용화를-향한-과제와-nisq-시대의-한계">6. 실용화를 향한 과제와 NISQ 시대의 한계
&lt;/h1>&lt;p>시뮬레이터 상에서 $N=15$를 소인수분해하는 것은 간단하게 할 수 있었지만, 실제 사회에서 사용되고 있는 RSA-2048(617자리의 10진수)을 소인수분해하려면 현실의 양자 컴퓨터에는 아직 수많은 장벽이 존재합니다.&lt;/p>
&lt;p>현재 우리가 살고 있는 시대는 &lt;strong>NISQ(Noisy Intermediate-Scale Quantum: 노이즈가 있는 중간 규모 양자) 시대&lt;/strong>라고 불립니다. 양자 비트는 외부 환경의 노이즈에 극히 취약하여, 계산 도중에 &amp;lsquo;결어긋남(데코히어런스)&amp;lsquo;을 일으켜 상태가 깨져 버립니다.&lt;/p>
&lt;p>쇼어의 알고리즘과 같이 깊은(게이트 수가 많은) 회로를 정확하게 실행하기 위해서는 노이즈를 정정하는 **양자 오류 정정(Quantum Error Correction)**이 필수적입니다. 하나의 노이즈 없는 &amp;lsquo;논리 양자 비트&amp;rsquo;를 만들어 내기 위해 수천 개의 &amp;lsquo;물리 양자 비트&amp;rsquo;를 표면 부호(Surface Code) 등으로 인코딩해야 합니다.&lt;/p>
&lt;p>2048비트의 RSA 암호를 깨기 위해서는 수천 개의 완벽한 논리 양자 비트가 필요하며, 이를 실현하려면 &lt;strong>수백만에서 수천만 개의 물리 양자 비트&lt;/strong>를 탑재한 결함 허용(오류 내성) 양자 컴퓨터가 필요할 것으로 추산되고 있습니다. 현재 최첨단 양자 프로세서도 수백~수천 물리 양자 비트 정도이기 때문에, 당장 전 세계의 암호가 깨지는 것은 아닙니다.&lt;/p>
&lt;blockquote>
&lt;p>[!WARNING]
그러나 &amp;ldquo;Store Now, Decrypt Later(지금 저장하고, 나중에 해독한다)&amp;ldquo;라는 위협 모델이 존재합니다. 공격자는 현재 암호화되어 있는 기밀 통신을 암호 데이터인 채로 대량으로 저장해 두고, 10~20년 후에 강력한 양자 컴퓨터가 완성되는 순간에 모든 것을 해독하려는 전략을 취할 가능성이 있습니다.&lt;/p>
&lt;/blockquote>
&lt;hr>
&lt;h1 id="7-양자-내성-암호pqc로의-전환">7. 양자 내성 암호(PQC)로의 전환
&lt;/h1>&lt;p>이러한 &amp;lsquo;Q-Day(양자 컴퓨터가 암호를 깨는 날)&amp;lsquo;의 도래에 대비하여, 미국 국립표준기술연구소(NIST)를 필두로 전 세계 암호학자들이 **양자 내성 암호(Post-Quantum Cryptography, PQC)**의 제정을 추진하고 있습니다.&lt;/p>
&lt;p>PQC는 쇼어의 알고리즘을 사용해도 (혹은 그로버의 알고리즘을 사용해도) 효율적으로 풀 수 없을 것으로 수학적으로 여겨지는 새로운 수학적 문제(격자 문제, 다변수 다항식 문제, 해시 함수 기반 등)를 기반으로 하고 있습니다. 이미 &amp;lsquo;CRYSTALS-Kyber&amp;rsquo;나 &amp;lsquo;CRYSTALS-Dilithium&amp;rsquo;과 같은 알고리즘이 표준 규격으로 선정되어, Apple의 iMessage나 각종 웹 브라우저의 통신 프로토콜에 도입이 서서히 시작되고 있습니다.&lt;/p>
&lt;p>IT 인프라를 관리하는 엔지니어에게 있어, 기존의 RSA나 타원곡선 암호에서 PQC로의 &amp;lsquo;크립토 어질리티(암호 민첩성: 빠르게 암호 방식을 전환할 수 있는 설계)&amp;lsquo;를 시스템에 통합하는 것이 향후 큰 과제가 될 것입니다.&lt;/p>
&lt;hr>
&lt;h1 id="8-맺음말">8. 맺음말
&lt;/h1>&lt;p>본 기사에서는 쇼어 알고리즘의 이론적인 수학적 배경에서 시작하여 양자 푸리에 변환을 사용한 주기 추출의 메커니즘, 그리고 Python과 Qiskit을 이용한 구체적인 시뮬레이션 코드까지 1만 자 규모의 분량으로 철저하게 해설했습니다.&lt;/p>
&lt;p>양자역학이라는 미시적 세계의 물리 법칙이 거시적 정보과학의 근간인 계산량 이론이나 암호 이론을 근본부터 뒤엎어 버린다는 사실은 과학의 역사에 있어 가장 흥미진진한 패러다임 전환 중 하나입니다. 현재 진행형으로 계속 발전하고 있는 양자 컴퓨팅 기술과 그에 맞서는 새로운 암호 기술의 공방전에서 앞으로도 눈을 뗄 수 없습니다.&lt;/p>
&lt;p>꼭 이번에 소개한 Python 코드를 자신의 환경에서 실행해 보시고, 양자 상태의 중첩과 간섭이 만들어 내는 &amp;lsquo;계산의 마법&amp;rsquo;을 체감해 보시기 바랍니다.&lt;/p>
&lt;hr>
&lt;p>&lt;strong>참고 문헌&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Shor, P. W. (1994). &amp;ldquo;Algorithms for quantum computation: discrete logarithms and factoring&amp;rdquo;. Proceedings 35th Annual Symposium on Foundations of Computer Science.&lt;/li>
&lt;li>Nielsen, M. A., &amp;amp; Chuang, I. L. (2010). &amp;ldquo;Quantum Computation and Quantum Information&amp;rdquo;. Cambridge University Press.&lt;/li>
&lt;li>Qiskit Documentation: &lt;a class="link" href="https://qiskit.org/documentation/" target="_blank" rel="noopener"
>https://qiskit.org/documentation/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>【도해 PQC】 양자 내성 암호의 주요 알고리즘 비교</title><link>http://kenji.blog/ko/p/post-quantum-cryptography-algorithms-comparison/</link><pubDate>Fri, 11 Sep 2026 07:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/post-quantum-cryptography-algorithms-comparison/</guid><description>&lt;img src="http://kenji.blog/p/post-quantum-cryptography-algorithms-comparison/img/eyecatch.jpg" alt="Featured image of post 【도해 PQC】 양자 내성 암호의 주요 알고리즘 비교" />&lt;h2 id="1-시작하며-양자-컴퓨터가-가져올-암호의-위기">1. 시작하며: 양자 컴퓨터가 가져올 &amp;lsquo;암호의 위기&amp;rsquo;
&lt;/h2>&lt;p>현대 인터넷 사회에서 통신의 기밀성과 데이터의 무결성을 보호하기 위해 공개키 암호 기술은 인프라로서 필수 불가결합니다. 현재 널리 사용되고 있는 RSA 암호나 타원 곡선 암호(ECC)는 각각 &amp;lsquo;거대한 합성수의 소인수분해의 어려움&amp;rsquo;이나 &amp;lsquo;타원 곡선 상의 이산대수 문제의 어려움&amp;rsquo;이라는 수학적인 장벽에 의존하여 안전성을 담보하고 있습니다. 고전적인 컴퓨터(슈퍼컴퓨터를 포함하여 현재 우리가 사용하고 있는 컴퓨터)에서는 이러한 수학적 문제를 풀기 위해서는 우주의 나이보다 더 오랜 시간이 걸린다고 증명되어 있으며, 그것이 안전성의 근거가 되어 왔습니다.&lt;/p>
&lt;p>그러나 이 견고한 전제는 &lt;strong>양자 컴퓨터&lt;/strong>의 이론과 실용화의 진전으로 인해 근본적으로 뒤집히려 하고 있습니다. 1994년에 암호학자 피터 쇼어(Peter Shor)가 발표한 &amp;lsquo;&lt;strong>쇼어의 알고리즘(Shor&amp;rsquo;s Algorithm)&lt;/strong>&amp;lsquo;은 충분한 성능을 가진 오류 내성 범용 양자 컴퓨터(CRQC: Cryptographically Relevant Quantum Computer)에서 실행함으로써 소인수분해 문제나 이산대수 문제를 &amp;lsquo;다항식 시간&amp;rsquo;에 해독할 수 있음을 이론적으로 증명했습니다. 이는 현재 사용되고 있는 공개키 암호가 모두 무력화됨을 의미합니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["대규모 양자 컴퓨터 (CRQC)"] -->|실행| B["쇼어의 알고리즘"]
B -->|다항식 시간에 해독| C["소인수분해 문제 (RSA)"]
B -->|다항식 시간에 해독| D["이산대수 문제 (ECC / ECDSA)"]
C --> E["암호 통신의 도청·데이터 위조·스푸핑"]
D --> E
F["Store Now, Decrypt Later (SNDL)"] --> E&lt;/div>
&lt;p>&amp;ldquo;양자 컴퓨터의 본격적인 완성은 아직 수십 년 후의 일이므로 문제없다&amp;quot;라고 생각하는 것은 매우 위험합니다. 왜냐하면, &lt;strong>Store Now, Decrypt Later (SNDL: 지금 저장하고 나중에 복호화한다)&lt;/strong> 라고 불리는 공격 기법이 이미 현실적인 위협이 되고 있기 때문입니다. 이는 악의적인 국가나 해커 조직이 현재 암호화된 통신 데이터(TLS 트래픽 등)를 대량으로 스토리지에 저장해 두었다가, 장래에 강력한 양자 컴퓨터를 이용할 수 있게 되는 순간 그것들을 모두 복호화하는 공격입니다. 국가 기밀, 인프라 정보, 장기간 보호해야 할 의료 데이터 등은 이미 이러한 위협에 노출되어 있습니다.&lt;/p>
&lt;p>또한 대칭키 암호(AES 등)나 해시 함수(SHA-256 등)에 대해서도 1996년에 발견된 &lt;strong>그로버의 알고리즘(Grover&amp;rsquo;s Algorithm)&lt;/strong> 이 존재합니다. 이로 인해 무차별 대입 공격(브루트 포스)의 계산량이 제곱근으로 감소합니다. 즉, AES-128의 보안 수준은 실질적으로 2의 64승으로 반감되기 때문에, 양자 시대에는 AES-256이나 SHA-384와 같은 더 긴 키나 해시 길이를 사용하는 것이 권장됩니다.&lt;/p>
&lt;p>이 전례 없는 암호 위기에 대항하기 위해 탄생한 것이 바로 양자 컴퓨터를 사용해도 해독이 어려운 새로운 수학적 문제에 기반한 &lt;strong>양자 내성 암호(Post-Quantum Cryptography: PQC)&lt;/strong> 입니다. 본 기사에서는 미국 국립표준기술연구소(NIST)가 주도하여 진행해 온 PQC 표준화 프로세스의 결과에 바탕을 두고, 주요 PQC 알고리즘에 대해 그 수학적 배경부터 작동 원리, 아키텍처 비교까지 매우 상세하게 해설합니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-nist의-pqc-표준화-프로젝트의-전모와-역사">2. NIST의 PQC 표준화 프로젝트의 전모와 역사
&lt;/h2>&lt;p>암호 기술의 전환에는 프로토콜의 재설계, 시스템 업데이트, 하드웨어 교체 등을 포함하여 수년에서 수십 년의 시간이 걸립니다. 그렇기 때문에 전 세계의 암호학자들은 초기부터 PQC 연구를 진행해 왔습니다. 그 중심적인 역할을 맡아온 곳이 미국의 NIST(국립표준기술연구소)입니다. NIST는 2016년에 PQC의 표준화 프로세스를 공모하여 전 세계의 암호 커뮤니티로부터 전혀 새로운 암호 알고리즘의 제안을 받았습니다.&lt;/p>
&lt;p>표준화 대상이 된 것은 다음 두 가지 주요 카테고리입니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>공개키 암호 / 키 캡슐화 메커니즘 (KEM: Key Encapsulation Mechanism)&lt;/strong>: TLS 연결 등에서 통신 경로를 암호화하기 위한 대칭키를 안전하게 공유(분배)하기 위한 구조.&lt;/li>
&lt;li>&lt;strong>디지털 서명 (Digital Signatures)&lt;/strong>: 소프트웨어 업데이트나 전자 인증서에서 데이터가 위조되지 않았음과 송신자의 스푸핑이 없음(진정성)을 증명하기 위한 구조.&lt;/li>
&lt;/ol>
&lt;p>약 6년에 걸친 매우 치열한 평가·분석 및 암호 해독 경쟁(Round 1~Round 3)을 거쳐, 일부 알고리즘에 대해서는 Round 4의 추가 평가가 이루어졌습니다. 그 결과, 2024년에 다음 알고리즘들이 정식 연방 정보 처리 표준(FIPS)으로 발행되어 향후 세계 표준으로 확정되었습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>FIPS 203 (ML-KEM)&lt;/strong>: CRYSTALS-Kyber 기반 KEM&lt;/li>
&lt;li>&lt;strong>FIPS 204 (ML-DSA)&lt;/strong>: CRYSTALS-Dilithium 기반 디지털 서명&lt;/li>
&lt;li>&lt;strong>FIPS 205 (SLH-DSA)&lt;/strong>: SPHINCS+ 기반 상태 비저장(Stateless) 해시 기반 서명&lt;/li>
&lt;li>&lt;strong>(향후 책정 예정) FN-DSA&lt;/strong>: FALCON 기반 디지털 서명&lt;/li>
&lt;/ul>
&lt;p>선정된 이 알고리즘들은 의존하는 수학적인 &amp;lsquo;어려움 문제&amp;rsquo;가 각각 달라서, 어느 한 알고리즘에서 장래 치명적인 취약점이 발견되더라도 시스템 전체가 붕괴하지 않도록 다양성(Crypto Agility)이 확보되어 있습니다. 표준화 과정에서는 주로 성능 면에서 격자 기반 암호(Lattice-based cryptography)가 주역이 되었지만, 해시 기반 암호나 코드 기반 암호가 강력한 백업으로 채택되었습니다.&lt;/p>
&lt;hr>
&lt;h2 id="3-pqc의-주요-수학적-접근-방식-분류">3. PQC의 주요 수학적 접근 방식 분류
&lt;/h2>&lt;p>PQC 알고리즘은 그 안전성의 근거가 되는 수학적 문제에 따라 주로 다음 5가지 카테고리로 크게 나뉩니다. 본 기사에서는 특히 상위 3가지에 대해 자세히 파고듭니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>격자 기반 암호 (Lattice-based Cryptography)&lt;/strong>:
다차원 격자 공간에서의 최단 벡터 문제(SVP)나 최근접 벡터 문제(CVP), 그리고 여기서 파생된 LWE 문제에 기반합니다. NIST 표준화의 중심이며, Kyber, Dilithium, FALCON이 이에 해당합니다. 처리 속도, 공개키 크기, 암호문 크기의 균형이 가장 뛰어나 범용적인 사용에 적합합니다.&lt;/li>
&lt;li>&lt;strong>해시 기반 암호 (Hash-based Cryptography)&lt;/strong>:
암호학적 해시 함수(SHA-2, SHAKE 등)의 &amp;lsquo;충돌 저항성&amp;rsquo;과 &amp;lsquo;일방향성&amp;rsquo;에만 안전성의 근거를 둡니다. 디지털 서명(SPHINCS+ 등)에만 적용 가능하지만, 안전성 증명이 가장 확고하며 미지의 수학적 공격에 대한 내성이 극히 높은 것이 특징입니다.&lt;/li>
&lt;li>&lt;strong>코드 기반 암호 (Code-based Cryptography)&lt;/strong>:
오류 정정 코드 이론에 기반하여 신드롬 복호화 문제(Syndrome Decoding Problem)의 어려움에 의존합니다. 1970년대에 제안된 Classic McEliece가 대표적으로, 매우 긴 역사와 입증된 안전성을 가지는 반면 공개키의 크기가 메가바이트 단위로 극단적으로 큽니다.&lt;/li>
&lt;li>&lt;strong>다변수 다항식 암호 (Multivariate Polynomial Cryptography)&lt;/strong>:
유한체 상의 다변수 연립 이차방정식의 해를 구하는 것(MQ 문제)의 어려움에 기반합니다. 주로 디지털 서명(Rainbow 등)으로 제안되었으나, NIST의 최종 라운드 진행 중에 PC 1대로 며칠 만에 해독되는 강력한 공격 기법이 발견되어 많은 알고리즘이 표준화에서 탈락했습니다.&lt;/li>
&lt;li>&lt;strong>동종 사상 암호 (Isogeny-based Cryptography)&lt;/strong>:
타원 곡선의 동종 사상(Isogeny) 그래프 상에서의 경로 탐색 문제에 기반합니다. 키 크기가 매우 작아 ECC의 정당한 후계자로 기대되었으나, 최종 후보였던 &amp;lsquo;SIKE&amp;rsquo;가 2022년에 고전적인 수학(Castryck-Decru 공격 등)을 이용하여 일반 PC에서 불과 몇 시간 만에 완전히 해독되어 PQC 설계의 어려움과 두려움을 상징하는 극적인 결말을 맞았습니다.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="4-격자-암호의-심연-lwe-문제와-module-lwe의-수학적-기초">4. 격자 암호의 심연: LWE 문제와 Module-LWE의 수학적 기초
&lt;/h2>&lt;p>현재 가장 유력하게 여겨지며 표준화의 중심이 된 &lt;strong>격자 기반 암호&lt;/strong>. 그 안전성의 근저에 있는 것이 &lt;strong>LWE 문제 (Learning with Errors: 오차가 있는 학습 문제)&lt;/strong> 입니다. 2005년에 Oded Regev가 제창하였고, 이 획기적인 공적으로 그는 괴델상(Gödel Prize)을 수상했습니다. LWE 문제를 이해하지 않고서는 현대의 PQC를 논할 수 없습니다.&lt;/p>
&lt;h3 id="41-lwe-문제learning-with-errors란-무엇인가">4.1. LWE 문제(Learning with Errors)란 무엇인가
&lt;/h3>&lt;p>먼저 간단한 연립 일차 방정식을 생각해 봅시다. 어떤 법 $q$ (모듈로 $q$) 하에서 알려진 임의의 행렬 $A$와 알려지지 않은 비밀 벡터 $\vec{s}$가 있고 그 곱인 $\vec{b}$가 주어졌다고 가정합시다.&lt;/p>
$$ \vec{b} = A\vec{s} \pmod q $$
&lt;p>이때 공개 정보인 $A$와 $\vec{b}$로부터 미지의 $\vec{s}$를 구하는 것은 간단합니다. 고전적인 알고리즘인 &amp;lsquo;가우스 소거법&amp;rsquo;을 사용하면 다항식 시간에 쉽게 $\vec{s}$를 계산할 수 있습니다.&lt;/p>
&lt;p>그러나 이 식에 &amp;lsquo;작고 의도적인 오차(노이즈)&amp;lsquo;를 더하면 문제의 난이도가 극적으로 상승합니다. 이것이 바로 &lt;strong>LWE 문제&lt;/strong>입니다.&lt;/p>
&lt;p>미지의 비밀 벡터 $\vec{s} \in \mathbb{Z}_q^n$과 임의로 선택된 행렬 $A \in \mathbb{Z}_q^{m \times n}$를 준비합니다. 나아가 정규분포나 이항분포 등에 따라 선택된 &amp;lsquo;요소의 값이 충분히 작은&amp;rsquo; 오차 벡터 $\vec{e} \in \mathbb{Z}_q^m$를 준비하고 다음과 같이 $\vec{b}$를 계산합니다.&lt;/p>
$$ \vec{b} = A\vec{s} + \vec{e} \pmod q $$
&lt;p>&lt;strong>Search LWE 문제&lt;/strong>란 &amp;ldquo;공개 정보 $(A, \vec{b})$로부터 비밀 정보 $\vec{s}$를 구하라&amp;quot;는 문제입니다. 이 오차 $\vec{e}$가 존재함으로써 가우스 소거법 등 대수학적 해법을 시도하려 하면, 방정식을 더하고 빼는 과정에서 오차 $\vec{e}$가 눈덩이처럼 증폭되어 최종적으로 무작위 값과 구별할 수 없게 되어 파탄 나고 맙니다.&lt;/p>
&lt;p>LWE 문제의 대단한 점은 &amp;lsquo;격자 상의 최악 경우 계산 복잡도 문제(Worst-case hardness)&amp;lsquo;인 GapSVP(판정 최단 벡터 문제)나 SIVP(최단 독립 벡터 문제)를 풀 수 있는 양자 알고리즘이 존재하지 않는 한, LWE 문제도 평균적(Average-case)으로 풀 수 없다는 강력한 이론적 증명(환원)이 존재한다는 것입니다. 즉, 무작위로 생성된 암호키라 하더라도 이론적인 한계에 의해 뒷받침되는 확고한 안전성을 가짐이 보장되어 있습니다.&lt;/p>
&lt;h3 id="42-ring-lwe와-module-lwe를-통한-극적인-효율화">4.2. Ring-LWE와 Module-LWE를 통한 극적인 효율화
&lt;/h3>&lt;p>일반적인 LWE 문제(Standard LWE)는 안전성의 근거가 매우 명확하지만, 행렬 $A$의 크기가 매우 커져 키 크기가 메가바이트 급이 되므로 실용적이지 않습니다. 그래서 제안된 것이 다항식 환(Polynomial Rings)을 이용하여 대수적인 구조를 갖게 하는 접근 방식입니다.&lt;/p>
&lt;p>&lt;strong>Ring-LWE 문제&lt;/strong>에서는 단순한 벡터나 행렬 대신, 어떤 다항식 환 $R_q$의 원소(다항식)를 사용합니다. NIST 표준에서 일반적으로 사용되는 것은 다음과 같은 원분 다항식 환(Cyclotomic Polynomial Ring)입니다.&lt;/p>
$$ R_q = \mathbb{Z}_q[X]/(X^n + 1) $$
&lt;p>여기서 $n$은 2의 거듭제곱(예: 256), $q$는 적당한 소수입니다. 이 환 위에서 요소 $a, s, e \in R_q$를 이용하여 $b = a \cdot s + e \pmod q$를 계산합니다. 하나의 다항식 $a$가 $n$개의 계수를 갖기 때문에 데이터를 대폭 압축할 수 있으며, 나아가 &lt;strong>NTT (Number Theoretic Transform: 수론 변환)&lt;/strong> 라는 고속 푸리에 변환(FFT)의 유한체 버전을 사용함으로써 $O(n \log n)$의 계산량으로 초고속으로 다항식 곱셈이 가능해집니다.&lt;/p>
&lt;p>하지만 Ring-LWE에는 &amp;lsquo;환의 특수한 대수 구조로 인한 미지의 취약점이 존재할지도 모른다&amp;rsquo;는 우려가 있었습니다. 또한 보안 수준(AES-128, 192, 256 상당 등)을 변경할 때 다항식의 차수 $n$ 자체를 변경해야 하며, 이에 따라 NTT 알고리즘 등 구현 전체를 다시 작성해야 한다는 공학적인 과제가 있었습니다.&lt;/p>
&lt;p>그래서 표준화 알고리즘인 Kyber나 Dilithium이 채택한 것이 바로 &lt;strong>Module-LWE (M-LWE) 문제&lt;/strong>입니다. Module-LWE는 구조가 없는 Standard LWE와 너무 많은 구조를 가진 Ring-LWE의 딱 중간에 위치하는 절충안이며, 다항식 환 $R_q$의 요소를 성분으로 하는 $k \times k$의 행렬(모듈)을 사용합니다.&lt;/p>
$$ \vec{b} = A\vec{s} + \vec{e} \pmod{R_q} \quad (A \in R_q^{k \times k}, \vec{s}, \vec{e} \in R_q^k) $$
&lt;p>Module-LWE의 가장 큰 이점은 다항식의 차수 $n$(NIST 표준에서는 $n=256$)을 고정한 채 행렬의 차원 $k$만 변경하여 쉽게 보안 수준을 확장(Scale)할 수 있다는 점입니다.
예를 들어 Kyber의 경우 다음과 같이 차원 $k$를 조정합니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Kyber512 (Level 1)&lt;/strong>: $k = 2$ (AES-128 상당)&lt;/li>
&lt;li>&lt;strong>Kyber768 (Level 3)&lt;/strong>: $k = 3$ (AES-192 상당)&lt;/li>
&lt;li>&lt;strong>Kyber1024 (Level 5)&lt;/strong>: $k = 4$ (AES-256 상당)&lt;/li>
&lt;/ul>
&lt;p>이로 인해 기반이 되는 NTT의 코드나 다항식 연산의 하드웨어 회로를 모든 보안 수준에서 100% 재사용할 수 있게 되어 구현의 안전성과 효율성이 극적으로 향상되었습니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-crystals-kyber-ml-kem-차세대-키-캡슐화-메커니즘">5. CRYSTALS-Kyber (ML-KEM): 차세대 키 캡슐화 메커니즘
&lt;/h2>&lt;p>&lt;strong>FIPS 203 (ML-KEM)&lt;/strong> 으로 공식 표준화된 CRYSTALS-Kyber는 앞서 언급한 Module-LWE 문제에 기반한 키 캡슐화 메커니즘(KEM)입니다. 앞으로 TLS 1.3이나 SSH 등에서 세션 키를 안전하게 공유하기 위한 사실상의 세계 표준이 될 것입니다.&lt;/p>
&lt;h3 id="51-kem-key-encapsulation-mechanism-아키텍처">5.1. KEM (Key Encapsulation Mechanism) 아키텍처
&lt;/h3>&lt;p>PQC의 시대에서는 RSA처럼 &amp;lsquo;클라이언트가 대칭키를 만들어 서버의 공개키로 암호화해서 보낸다&amp;rsquo;는 직접적인 접근 방식이 아니라, KEM이라는 캡슐화의 틀이 표준이 됩니다.&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant Client as "클라이언트 (Alice)"
participant Server as "서버 (Bob)"
Note over Client: "ML-KEM KeyGen()"
Client->>Client: "비밀키 (sk) 와 공개키 (pk) 생성"
Client->>Server: "공개키 (pk) 전송"
Note over Server: "ML-KEM Encaps()"
Server->>Server: "무작위 대칭키 (K) 생성"
Server->>Server: "K를 pk로 캡슐화하여 암호문 (c) 생성"
Server->>Client: "암호문 (c) 전송"
Note over Client: "ML-KEM Decaps()"
Client->>Client: "비밀키 (sk) 를 사용하여 암호문 (c) 복호화"
Client->>Client: "캡슐화를 해제하고 대칭키 (K) 추출"
Note over Client, Server: "공유한 대칭키 (K) 를 사용하여 AES 등으로 암호화 통신 시작"&lt;/div>
&lt;h3 id="52-kyber-내부-알고리즘의-원리와-후지사키-오카모토-변환fujisaki-okamoto-transform">5.2. Kyber 내부 알고리즘의 원리와 후지사키-오카모토 변환(Fujisaki-Okamoto Transform)
&lt;/h3>&lt;p>Kyber의 설계는 매우 세련되었습니다. 먼저 CPA(선택 평문 공격)에 대해서만 안전한 공개키 암호 방식(Kyber.CPAPKE)을 구축하고, 여기에 &lt;strong>후지사키-오카모토 변환 (Fujisaki-Okamoto Transform)&lt;/strong> 이라고 불리는 암호학적으로 매우 강력 기법을 적용함으로써 CCA(적응적 선택 암호문 공격)에 대해서도 안전한 완전한 KEM으로 업그레이드하는 설계를 채택했습니다.&lt;/p>
&lt;p>CPAPKE의 핵심이 되는 암호화와 복호화의 메커니즘은 다음과 같습니다.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>키 생성 (Key Generation)&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>임의의 시드 값으로부터 NTT 도메인 상의 행렬 $A \in R_q^{k \times k}$를 생성합니다. 법 $q$는 $3329$가 사용됩니다.&lt;/li>
&lt;li>중심 이항 분포(CBD)로부터 작은 계수를 갖는 비밀 벡터 $\vec{s}$와 오차 벡터 $\vec{e}$를 샘플링합니다.&lt;/li>
&lt;li>$\vec{t} = A\vec{s} + \vec{e}$를 계산합니다. 공개키는 $(A, \vec{t})$, 비밀키는 $\vec{s}$입니다. (실제로는 $A$는 시드 값으로서 공개되어 대역폭을 절약합니다).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>암호화 (Encryption)&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>공유하려는 32바이트 메시지(대칭키의 재료) $m$을 다항식으로 인코딩합니다.&lt;/li>
&lt;li>새로운 임의의 벡터 $\vec{r}$과 작은 오차 $\vec{e_1}, e_2$를 생성합니다.&lt;/li>
&lt;li>$\vec{u} = A^T\vec{r} + \vec{e_1}$&lt;/li>
&lt;li>$v = \vec{t}^T\vec{r} + e_2 + \lfloor q/2 \rceil \cdot m$&lt;/li>
&lt;li>암호문은 $(\vec{u}, v)$가 됩니다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>복호화 (Decryption)&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>수신자는 $v - \vec{s}^T\vec{u}$를 계산합니다.&lt;/li>
&lt;li>이 식을 수식으로 전개하면 다음과 같이 됩니다.
$v - \vec{s}^T\vec{u} = (\vec{t}^T\vec{r} + e_2 + \lfloor q/2 \rceil \cdot m) - \vec{s}^T(A^T\vec{r} + \vec{e_1})$&lt;/li>
&lt;li>여기서 $\vec{t} = A\vec{s} + \vec{e}$를 대입하면 주항인 $\vec{s}^TA^T\vec{r}$가 상쇄됩니다.&lt;/li>
&lt;li>남는 것은 $\lfloor q/2 \rceil \cdot m + (\vec{e}^T\vec{r} + e_2 - \vec{s}^T\vec{e_1})$이 됩니다.&lt;/li>
&lt;li>괄호 안의 항은 &amp;lsquo;작은 오차끼리의 곱이나 합&amp;rsquo;이므로 전체적으로도 충분히 작은 값(노이즈)에 머무릅니다. 따라서 각 계수가 $0$에 가까운지 $q/2$에 가까운지를 임계값 판정함으로써 원래 메시지 $m$의 비트(0 또는 1)를 완전히 오류 없이 복원할 수 있습니다.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>Kyber의 가장 큰 강점은 그 압도적인 &lt;strong>처리 속도&lt;/strong>와 &lt;strong>적절한 키 크기&lt;/strong>에 있습니다. Kyber768의 경우 공개키 크기는 1,184바이트, 암호문 크기는 1,088바이트이며, RSA-3072(키 크기 약 384바이트) 등과 비교하면 크지만 현대 인터넷 통신의 MTU(Maximum Transmission Unit) 내에 패킷 분할 없이 담는 것이 가능하여 네트워크 지연(Latency)에 거의 악영향을 주지 않습니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-crystals-dilithium-ml-dsa-격자-기반-범용-디지털-서명">6. CRYSTALS-Dilithium (ML-DSA): 격자 기반 범용 디지털 서명
&lt;/h2>&lt;p>디지털 서명의 표준화에 있어서는, 같은 격자 암호 접근 방식 중에서도 다른 설계 사상을 가진 알고리즘들이 경쟁했습니다. 그중에서 범용적인 디지털 서명으로서 &lt;strong>FIPS 204 (ML-DSA)&lt;/strong> 로 선정된 것이 &lt;strong>CRYSTALS-Dilithium&lt;/strong>입니다.&lt;/p>
&lt;h3 id="61-fiat-shamir-with-aborts-패러다임">6.1. Fiat-Shamir with Aborts 패러다임
&lt;/h3>&lt;p>Dilithium은 Kyber와 마찬가지로 Module-LWE(및 Module-SIS 문제)에 기반한 디지털 서명 체계입니다. 설계의 기본 바탕에는 &amp;lsquo;&lt;strong>Fiat-Shamir with Aborts (중도 종료를 동반한 피아트-샤미르 변환)&lt;/strong>&amp;lsquo;라는 매우 중요한 패러다임이 사용되었습니다.&lt;/p>
&lt;p>피아트-샤미르 변환 자체는 대화형 영지식 증명 프로토콜을 비대화형 디지털 서명으로 변환하기 위한 표준적인 기법입니다. 증명자(서명자)가 약속(Commitment) $y$를 생성하고, $w = Ay$를 계산하여 해시 함수에 통과시킴으로써 임의의 챌린지(Challenge) $c$를 얻고 응답 $z = y + cs$를 계산합니다.&lt;/p>
&lt;p>하지만 격자 암호에서 이를 단순히 적용하면 응답 $z$의 분포가 비밀키 $s$의 값에 의존하여 왜곡되어 버리고, 다수의 서명을 관측한 공격자에게 비밀키 $s$의 정보가 조금씩 유출되어 버리는 치명적인 문제(부채널 방식의 수학적 유출)가 있었습니다.&lt;/p>
&lt;p>Dilithium의 설계팀(Lyubashevsky 등)은 서명의 계산 결과 $z$의 계수가 미리 설정된 안전한 임계값 범위 내에 들어가지 않을 경우 해당 서명 프로세스 전체를 파기(Abort)하고, 새로운 난수 $y$를 사용하여 처음부터 계산을 다시 하는 &amp;lsquo;&lt;strong>기각 샘플링(Rejection Sampling)&lt;/strong>&amp;lsquo;이라는 기법을 도입했습니다.&lt;/p>
&lt;p>이를 통해 최종적으로 출력되는 서명 $z$는 비밀키에 전혀 의존하지 않는 완전한 균등 분포가 되어, 정보 유출을 수학적으로 완전히 막는 데 성공했습니다.&lt;/p>
&lt;h3 id="62-dilithium의-이점과-구현의-용이성">6.2. Dilithium의 이점과 구현의 용이성
&lt;/h3>&lt;p>Dilithium 설계상의 큰 이점은 서명 생성 프로세스에서 복잡한 &amp;lsquo;가우스 분포로부터의 샘플링&amp;rsquo;이나 &amp;lsquo;부동소수점 연산&amp;rsquo;을 &lt;strong>전혀 사용하지 않는다&lt;/strong>는 점입니다. 균등 분포로부터의 샘플링과 단순한 정수 모듈로 연산, NTT 및 해시 함수(SHAKE)만으로 구현할 수 있기 때문에 임베디드 마이크로컨트롤러에서 클라우드 서버에 이르기까지 폭넓은 환경에서 안전하고 상수 시간(Constant-time)에 구현하기가 용이합니다. 이로 인해 타이밍 공격 등 물리적인 부채널 공격에 대해서도 강력한 내성을 가집니다.&lt;/p>
&lt;hr>
&lt;h2 id="7-falcon-fn-dsa-궁극적으로-콤팩트한-격자-서명">7. FALCON (FN-DSA): 궁극적으로 콤팩트한 격자 서명
&lt;/h2>&lt;p>NIST는 Dilithium과는 다른 특성을 가진 또 다른 격자 기반 서명으로서 &lt;strong>FALCON (Fast-Fourier Lattice-based Compact Signatures over NTRU)&lt;/strong> 을 표준화 후보로 선정했습니다(현재 FN-DSA로 초안 작성 중).&lt;/p>
&lt;h3 id="71-ntru-격자와-가우스-샘플링">7.1. NTRU 격자와 가우스 샘플링
&lt;/h3>&lt;p>FALCON의 가장 큰 특징은 LWE 문제가 아니라 1996년부터 존재하는 역사가 오래된 &lt;strong>NTRU (N-th degree Truncated polynomial Ring Units) 격자&lt;/strong>를 사용한다는 점입니다. 더 나아가 GPV (Gentry-Peikert-Vaikuntanathan) 프레임워크에 기반한 &amp;lsquo;&lt;strong>해시 앤 사인 (Hash-and-Sign)&lt;/strong>&amp;rsquo; 패러다임을 채택하고 있습니다.&lt;/p>
&lt;p>해시 앤 사인(Hash-and-Sign)에서는 메시지의 해시 값을 공간 내의 타깃 점으로 삼고, 그 점에 가장 가까운 격자 상의 점(최근접 벡터 문제의 근사해)을 찾는 것으로 서명을 합니다. 여기에는 비밀키인 &amp;lsquo;품질이 좋은 짧은 기저(Basis)&amp;lsquo;를 사용하여 이산 가우스 분포에 따라 점을 샘플링할 필요가 있습니다.&lt;/p>
&lt;p>FALCON은 이 무거운 계산을 &amp;lsquo;&lt;strong>고속 푸리에 직교화 (Fast Fourier Orthogonalization: FFO)&lt;/strong>&amp;lsquo;라는 기법을 사용하여 극적으로 고속화했습니다.&lt;/p>
&lt;h3 id="72-falcon의-장점과-단점">7.2. FALCON의 장점과 단점
&lt;/h3>&lt;p>FALCON의 압도적인 장점은 그 &lt;strong>서명 크기와 공개키 크기가 극히 작다(콤팩트하다)&lt;/strong> 는 것입니다. Dilithium3의 서명 크기가 약 3,309바이트인 것에 반해, FALCON-512의 서명 크기는 불과 약 666바이트입니다. 공개키 역시 897바이트로 매우 작아 통신 대역폭이 극단적으로 제한되는 환경이나 IoT 기기, 특정 네트워크 프로토콜에서는 구세주가 됩니다.&lt;/p>
&lt;p>그러나 중대한 단점이 존재합니다. 서명 생성 시에 복잡한 &lt;strong>부동소수점 연산(64-bit IEEE 754)&lt;/strong> 을 동반하는 이산 가우스 샘플링이 필수이기 때문에 타이밍 정보 유출을 방지하는 상수 시간 구현(Constant-time implementation)이 매우 어렵고 코드도 거대해집니다. 이 때문에 FALCON은 범용적인 이용(Dilithium)에 대비되는 특정 용도 지향의 강력한 특화형 알고리즘이라는 위치를 갖습니다.&lt;/p>
&lt;div class="mermaid">graph LR
A["디지털 서명의 요구사항"] --> B{"가장 우선시하는 제약은?"}
B -->|"구현의 단순함·범용성·상수 시간 구현의 용이성"| C["Dilithium (ML-DSA)"]
B -->|"통신 대역폭의 최소화·데이터 크기의 콤팩트함"| D["FALCON (FN-DSA)"]
C --> E["범용적인 TLS 인증서, 소프트웨어의 전자 서명"]
D --> F["패킷 크기 제한이 엄격한 프로토콜, 특수 환경"]&lt;/div>
&lt;hr>
&lt;h2 id="8-sphincs-slh-dsa-최강의-안전성을-자랑하는-해시-기반-서명">8. SPHINCS+ (SLH-DSA): 최강의 안전성을 자랑하는 해시 기반 서명
&lt;/h2>&lt;p>격자 암호의 안전성이 장래에 천재적인 수학자의 돌파구에 의해 무너진다는 최악의 시나리오(만약의 사태)에 대비하여, NIST는 격자 암호와는 전혀 다른 접근 방식을 가진 표준으로서 &lt;strong>FIPS 205 (SLH-DSA)&lt;/strong>, 즉 **SPHINCS+**를 책정했습니다.&lt;/p>
&lt;p>SPHINCS+는 &lt;strong>해시 기반 서명&lt;/strong>으로 분류됩니다. 그 안전성의 근거는 &amp;ldquo;사용하는 암호학적 해시 함수(SHA-2나 SHAKE256 등)가 충돌 저항성과 일방향성을 갖는다는 것&amp;quot;이라는 단 1점에만 의존하고 있습니다. LWE나 소인수분해와 같이 특정 대수 구조를 가진 수학적 문제에 의존하지 않기 때문에 장래에 어떤 강력한 양자 알고리즘이 등장하더라도 해시 함수의 출력 길이를 단순하게 늘리는 것만으로 대항할 수 있다는, 극히 확고한 안전성(가장 보수적인 보안)을 자랑합니다.&lt;/p>
&lt;h3 id="81-wots-와-fors에-의한-상태-비저장stateless-아키텍처">8.1. WOTS+ 와 FORS에 의한 상태 비저장(Stateless) 아키텍처
&lt;/h3>&lt;p>해시 기반 서명의 역사는 오래되어 1970년대의 램포트 서명(Lamport signature)이나 윈터니츠 일회용 서명(WOTS)으로 거슬러 올라갑니다. 이것들은 &amp;ldquo;단 1번만 안전하게 서명할 수 있다&amp;quot;는 일회용 키였습니다. 이를 여러 번 사용할 수 있도록 하기 위해 머클 트리(Merkle Tree)를 조합하여 무수한 일회용 키를 하나의 루트 해시로 관리하는 XMSS(eXtended Merkle Signature Scheme)나 LMS와 같은 알고리즘이 개발되었습니다.&lt;/p>
&lt;p>그러나 XMSS나 LMS에는 &amp;lsquo;&lt;strong>상태 저장형(Stateful)&lt;/strong>&amp;lsquo;이라는 중대한 결점이 있었습니다. 서명할 때마다 &amp;ldquo;몇 번째 일회용 키를 사용했는가&amp;quot;라는 인덱스 상태를 비휘발성 메모리에 엄밀하게 계속 기록해야 하며, 만약 가상 머신의 스냅샷 복원 등으로 상태가 되돌아가 동일한 일회용 키를 두 번 사용해 버리면 비밀키가 즉시 유출되어 시스템이 붕괴하고 맙니다.&lt;/p>
&lt;p>SPHINCS+는 이러한 상태 관리의 번거로움을 해결한 &amp;lsquo;&lt;strong>상태 비저장형(Stateless)&lt;/strong>&amp;rsquo; 해시 기반 서명입니다.
그 핵심 기술은 다음의 조합입니다.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>WOTS+ (Winternitz One-Time Signature Plus)&lt;/strong>: 기본적인 일회용 서명.&lt;/li>
&lt;li>&lt;strong>FORS (Forest of Random Subsets)&lt;/strong>: 소수 회 서명(Few-Time Signature) 기술. 같은 키를 몇 번 정도는 재사용해도 안전성을 유지함.&lt;/li>
&lt;li>&lt;strong>Hyper-Tree (거대한 트리 구조)&lt;/strong>: 머클 트리를 다층적으로 겹겹이 쌓아 올린 거대한 구조.&lt;/li>
&lt;/ol>
&lt;p>SPHINCS+에서는 서명을 할 때 상태를 관리하는 대신, Hyper-Tree 밑바닥에 있는 방대한 수의 FORS 키 중에서 의사 난수를 사용하여 임의로 하나를 골라내어 서명합니다. 트리의 잎사귀 수가 천문학적으로 많기 때문에 같은 키를 우연히 두 번 고를 확률(충돌)이 무시할 수 있을 만큼 작아져 결과적으로 상태 비저장(Stateless)을 실현하고 있습니다.&lt;/p>
&lt;p>SPHINCS+의 유일하고도 가장 큰 약점은 &lt;strong>서명 크기가 극히 크다&lt;/strong>는 것입니다. 파라미터에 따라 다르지만 서명 크기는 17킬로바이트~49킬로바이트에 달하며, 서명 생성 속도도 격자 암호에 비해 압도적으로 느려집니다. 그 때문에 일상적인 웹 브라우징에서의 사용보다는 소프트웨어 업데이트 서명이나 루트 인증기관(CA)의 인증서 등 자주 서명을 하지 않으면서도 장기간의 절대적인 안전성이 강하게 요구되는 용도에서의 사용이 상정되어 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="9-코드-기반-암호-classic-mceliece라는-좋고-오래된-거인">9. 코드 기반 암호: Classic McEliece라는 좋고 오래된 거인
&lt;/h2>&lt;p>NIST 표준화 프로세스에서 Round 4의 최종 후보로서 현재도 계속 평가받고 있는 중요한 접근 방식이 &lt;strong>코드 기반 암호&lt;/strong>인 &lt;strong>Classic McEliece&lt;/strong>입니다.&lt;/p>
&lt;p>1978년 Robert McEliece가 제안한 이 알고리즘은 공개키 암호 역사 중에서도 RSA와 더불어 가장 오래된 것 중 하나입니다. &amp;lsquo;Goppa 코드(고파 부호)&amp;lsquo;라고 불리는 대수 기하 코드를 이용하여 메시지에 의도적으로 오류(노이즈 벡터)를 더해 암호화하고, 비밀키로서 Goppa 코드의 패리티 검사 행렬을 가진 자만이 강력한 오류 정정 능력을 사용해 오류를 제거하고 원래의 메시지를 복호화할 수 있다는 &amp;lsquo;&lt;strong>신드롬 복호화 문제 (Syndrome Decoding Problem)&lt;/strong>&amp;lsquo;에 기반하고 있습니다.&lt;/p>
$$ \vec{c} = \vec{m} G + \vec{e} $$
&lt;p>
($G$는 공개키인 스크램블된 생성 행렬, $\vec{e}$는 가중치 $t$의 오류 벡터)&lt;/p>
&lt;p>Classic McEliece의 놀라운 점은 &lt;strong>제안된 지 40년 이상 지났고 전 세계의 암호학자에 의한 치열한 해독 연구에 노출되어 왔음에도 불구하고, 그 본질적인 취약점이 한 번도 발견되지 않았다&lt;/strong>는 압도적인 실적에 있습니다. PQC 중에서 가장 &amp;lsquo;시간이 증명한 확고한 안전성&amp;rsquo;을 가지고 있습니다.&lt;/p>
&lt;p>게다가 암호문의 크기가 매우 작다(불과 100~200바이트 정도)는 이점이 있습니다. 하지만 &lt;strong>공개키 크기가 메가바이트(MB) 단위가 된다&lt;/strong>는 치명적인 결점이 존재합니다. 가장 낮은 보안 수준(AES-128 상당)에서도 공개키가 약 250KB, 높은 수준에서는 1MB를 넘습니다.&lt;/p>
&lt;p>이 때문에 TLS 핸드셰이크처럼 통신할 때마다 공개키를 네트워크 상에 전송하는 용도에는 전혀 적용할 수 없습니다. 하지만 VPN의 사전 공유 키 교환, 펌웨어에 공개키를 하드코딩하는 경우, 또는 위성 통신 등 공개키를 시스템에 사전에 배치할 수 있는 특수한 유스케이스에서는 그 확고한 안전성 덕분에 매우 유망한 선택지로 계속 검토되고 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="10-각-pqc-알고리즘의-성능-비교-및-트레이드오프">10. 각 PQC 알고리즘의 성능 비교 및 트레이드오프
&lt;/h2>&lt;p>지금까지 해설해 온 주요 알고리즘에 대해 일반적인 보안 수준(NIST Level 2&lt;del>3 상당, AES-128&lt;/del>192 레벨)에서의 성능 특성을 아래 표로 정리했습니다.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">알고리즘 (표준명)&lt;/th>
&lt;th style="text-align:left">카테고리&lt;/th>
&lt;th style="text-align:left">수학적 기반&lt;/th>
&lt;th style="text-align:left">공개키 크기&lt;/th>
&lt;th style="text-align:left">비밀키 크기&lt;/th>
&lt;th style="text-align:left">암호문/서명 크기&lt;/th>
&lt;th style="text-align:left">처리 속도 경향&lt;/th>
&lt;th style="text-align:left">주요 특징 및 용도&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Kyber768&lt;/strong>&lt;br>(ML-KEM)&lt;/td>
&lt;td style="text-align:left">KEM&lt;/td>
&lt;td style="text-align:left">Module-LWE&lt;/td>
&lt;td style="text-align:left">1,184 Bytes&lt;/td>
&lt;td style="text-align:left">2,400 Bytes&lt;/td>
&lt;td style="text-align:left">1,088 Bytes&lt;/td>
&lt;td style="text-align:left">매우 빠름&lt;/td>
&lt;td style="text-align:left">키 크기·속도의 밸런스가 최고. TLS 1.3 등 범용 KEM 표준.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Dilithium3&lt;/strong>&lt;br>(ML-DSA)&lt;/td>
&lt;td style="text-align:left">서명&lt;/td>
&lt;td style="text-align:left">Module-LWE&lt;/td>
&lt;td style="text-align:left">1,952 Bytes&lt;/td>
&lt;td style="text-align:left">4,032 Bytes&lt;/td>
&lt;td style="text-align:left">3,309 Bytes&lt;/td>
&lt;td style="text-align:left">생성·검증 모두 빠름&lt;/td>
&lt;td style="text-align:left">구현이 단순. 범용적인 디지털 서명 표준.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>FALCON-512&lt;/strong>&lt;br>(FN-DSA)&lt;/td>
&lt;td style="text-align:left">서명&lt;/td>
&lt;td style="text-align:left">NTRU 격자&lt;/td>
&lt;td style="text-align:left">897 Bytes&lt;/td>
&lt;td style="text-align:left">1,281 Bytes&lt;/td>
&lt;td style="text-align:left">666 Bytes&lt;/td>
&lt;td style="text-align:left">서명 생성은 느린 편, 검증은 초고속&lt;/td>
&lt;td style="text-align:left">서명 크기가 극소. 단 부동소수점 연산이 필요. 임베디드·IoT용.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>SPHINCS+&lt;/strong>&lt;br>(SLH-DSA)&lt;/td>
&lt;td style="text-align:left">서명&lt;/td>
&lt;td style="text-align:left">해시 함수&lt;/td>
&lt;td style="text-align:left">32 Bytes&lt;/td>
&lt;td style="text-align:left">64 Bytes&lt;/td>
&lt;td style="text-align:left">약 17,000 Bytes&lt;/td>
&lt;td style="text-align:left">생성이 매우 느림&lt;/td>
&lt;td style="text-align:left">수학적 파탄 위험이 거의 제로. 루트 인증서 등 고보안 용도.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>Classic McEliece&lt;/strong>&lt;/td>
&lt;td style="text-align:left">KEM&lt;/td>
&lt;td style="text-align:left">Goppa 코드&lt;/td>
&lt;td style="text-align:left">&lt;strong>약 1.04 MB&lt;/strong>&lt;/td>
&lt;td style="text-align:left">13,568 Bytes&lt;/td>
&lt;td style="text-align:left">&lt;strong>188 Bytes&lt;/strong>&lt;/td>
&lt;td style="text-align:left">캡슐화는 빠름&lt;/td>
&lt;td style="text-align:left">40년의 안전성 실적. 공개키가 거대. 하드코딩 가능한 환경용.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="트레이드오프에-대한-이해">트레이드오프에 대한 이해
&lt;/h3>&lt;p>PQC의 세계에서는 &amp;ldquo;크기가 작고, 속도가 빠르며, 수학적 보장도 완벽한&amp;rdquo; 마법과 같은 단일 알고리즘은 존재하지 않습니다.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>인터넷의 표준 (Kyber / Dilithium)&lt;/strong>: 성능 밸런스가 가장 좋으며, 현재의 RSA/ECC를 드롭인 리플레이스먼트(그대로 교체하는 것) 하기에 가장 적합합니다.&lt;/li>
&lt;li>&lt;strong>궁극의 보수성 (SPHINCS+)&lt;/strong>: 데이터 크기나 처리 속도를 희생해서라도 장래의 수학적 돌파구에 대한 절대적인 보험을 원할 경우에 선택됩니다.&lt;/li>
&lt;li>&lt;strong>특수 환경용 (FALCON / Classic McEliece)&lt;/strong>: 통신 대역폭이 극단적으로 좁거나 혹은 사전 배포가 가능한 등, 환경 제약에 맞춰 선택되는 특화형 무기입니다.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="11-실용화를-향한-과제와-하이브리드-암호라는-현실적-해답">11. 실용화를 향한 과제와 &amp;lsquo;하이브리드 암호&amp;rsquo;라는 현실적 해답
&lt;/h2>&lt;p>NIST의 표준화가 완료되고 FIPS 규격이 정식으로 발행됨에 따라 전 세계 IT 인프라의 PQC 전환(&lt;strong>PQC 마이그레이션&lt;/strong>)이 본격적으로 시작되었습니다. Google의 Chrome 브라우저, Apple의 iMessage(PQ3 프로토콜), Cloudflare와 같은 네트워크 제공자들은 이미 프로토콜에 PQC 지원을 구현하여 실제 운용을 시작하고 있습니다.&lt;/p>
&lt;p>하지만 새로운 암호 알고리즘으로 갑자기 완전히 전환하는 데에는 매우 높은 위험이 따릅니다. 만약 Kyber와 같은 격자 암호에 대해 몇 년 뒤 어떤 천재 수학자가 치명적인 공격 기법(고전 컴퓨터로도 풀 수 있는 수학적 결함)을 발견할 경우, 그에 의존하고 있는 시스템 전체가 순식간에 발가벗겨지게 됩니다.&lt;/p>
&lt;p>이러한 불확실성 위험을 경감하기 위한 현실적이고 권장되는 접근 방식이 &amp;lsquo;&lt;strong>하이브리드 암호(Hybrid Cryptography)&lt;/strong>&amp;lsquo;입니다.&lt;/p>
&lt;p>하이브리드 암호에서는 다년간의 실적이 있는 현행 고전적 암호(예: X25519 등의 타원 곡선 암호)와 새로운 PQC(예: Kyber768)를 모두 동시에 사용하여 키 교환을 수행합니다. 각각의 알고리즘에서 개별적으로 대칭키 성분을 생성하고 마지막으로 안전한 키 도출 함수(KDF)를 이용하여 두 성분을 혼합해 최종적인 마스터 시크릿을 생성합니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["클라이언트"] -->|① X25519의 공개키 + Kyber의 공개키 전송| B["서버"]
B -->|② X25519의 대칭키 + Kyber의 캡슐화 암호문 반환| A
A --> C{"마스터 시크릿 도출 (KDF)"}
B --> C
C -->|입력: (X25519의 대칭키) || (Kyber의 대칭키)| D["안전한 통신키 (AES-256 / ChaCha20)"]
D -->|"양자의 위협 &amp; 고전적인 취약성 양쪽에 내성"| E["안전한 하이브리드 암호 통신 (TLS 1.3)"]&lt;/div>
&lt;p>이를 통해 &amp;ldquo;만일 양자 컴퓨터가 실현되어 ECC가 깨지더라도 Kyber가 통신을 지키고&amp;rdquo;, 반대로 &amp;ldquo;만일 Kyber에 미지의 수학적 결함이 발견되더라도 ECC가 통신을 지킨다&amp;quot;는 견고한 이중 안전성을 실현할 수 있습니다. 대표적인 예로 IETF에서 표준화가 진행 중인 &lt;strong>X25519MLKEM768 (구 X25519Kyber768)&lt;/strong> 초안이 있으며, 현재 웹 브라우저와 최첨단 서버 간의 통신은 바로 이 하이브리드 방식을 이용하여 이루어지고 있습니다.&lt;/p>
&lt;p>또한 시스템 설계에 있어서 &amp;ldquo;특정 암호 알고리즘에 과도하게 의존하지 않고 알고리즘이 파탄 났을 때 신속하게 다른 알고리즘(예: Kyber에서 McEliece로, Dilithium에서 SPHINCS+로)으로 전환할 수 있는 아키텍처&amp;quot;를 구축하는 &lt;strong>Crypto Agility(암호 민첩성)&lt;/strong> 의 개념이 향후 시스템 개발에 있어 필수 요건이 됩니다.&lt;/p>
&lt;hr>
&lt;h2 id="12-맺음말-암호-기술의-새로운-지평선">12. 맺음말: 암호 기술의 새로운 지평선
&lt;/h2>&lt;p>양자 컴퓨터라는 인류의 꿈의 기술은 아이러니하게도 우리가 오랫동안 신뢰해 온 &amp;lsquo;소인수분해&amp;rsquo;나 &amp;lsquo;이산대수 문제&amp;rsquo;라는 수학적 방벽을 무너뜨리는 최대의 위협이 되었습니다. 그러나 전 세계의 암호학자들은 이에 굴하지 않고 격자 이론, 해시 함수 트리, 오류 정정 코드와 같은 보다 복잡하고 심오한 다차원 수학적 영역을 개척하여 양자 내성 암호(PQC)라는 새로운 방벽을 구축했습니다.&lt;/p>
&lt;p>NIST의 FIPS 203 (ML-KEM), FIPS 204 (ML-DSA), FIPS 205 (SLH-DSA) 표준화 완료는 끝이 아닙니다. 앞으로 수십 년간 이어질 PQC 마이그레이션이라는 장대한 여정의 첫걸음에 불과합니다. 소프트웨어 엔지니어나 시스템 아키텍트에게 있어서, 이 새로운 알고리즘들이 가져오는 &amp;lsquo;키 크기 증가&amp;rsquo;나 &amp;lsquo;계산 비용 변화&amp;rsquo;를 네트워크 프로토콜과 시스템에 어떻게 최적으로 적응시켜 나갈지가 앞으로의 큰 기술적 과제가 될 것입니다.&lt;/p>
&lt;p>양자 컴퓨터와 암호의 싸움은 인류의 수학적 탐구와 기술의 진화가 가장 치열하게 교차하는 흥미진진한 영역입니다. 본 기사를 통해 PQC의 배후에 있는 아름다운 수학 이론과 사이버 보안의 미래를 형성할 각 알고리즘의 놀라운 메커니즘에 대해 깊이 이해하셨기를 바랍니다.&lt;/p>
&lt;hr>
&lt;p>&lt;em>References:&lt;/em>&lt;/p>
&lt;ul>
&lt;li>&lt;em>NIST Post-Quantum Cryptography Standardization Program&lt;/em>&lt;/li>
&lt;li>&lt;em>FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard&lt;/em>&lt;/li>
&lt;li>&lt;em>FIPS 204: Module-Lattice-Based Digital Signature Standard&lt;/em>&lt;/li>
&lt;li>&lt;em>FIPS 205: Stateless Hash-Based Digital Signature Standard&lt;/em>&lt;/li>
&lt;/ul></description></item><item><title>양자 컴퓨터가 실용화되는 날: 2026년의 현재 위치</title><link>http://kenji.blog/ko/p/quantum-computing-2026-current-status/</link><pubDate>Fri, 11 Sep 2026 06:00:00 +0900</pubDate><guid>http://kenji.blog/ko/p/quantum-computing-2026-current-status/</guid><description>&lt;img src="http://kenji.blog/p/quantum-computing-2026-current-status/img/eyecatch.jpg" alt="Featured image of post 양자 컴퓨터가 실용화되는 날: 2026년의 현재 위치" />&lt;h2 id="1-시작하며-2026년-양자-컴퓨터는-어디까지-왔는가">1. 시작하며: 2026년, 양자 컴퓨터는 어디까지 왔는가
&lt;/h2>&lt;p>2026년 현재, 양자 컴퓨팅은 과거의 &amp;lsquo;이론상의 꿈&amp;rsquo;에서 &amp;lsquo;공학적인 현실&amp;rsquo;로 결정적인 전환을 이루었습니다. 수년 전까지 주류였던 &lt;strong>NISQ(Noisy Intermediate-Scale Quantum)&lt;/strong> 디바이스의 한계가 명확해짐에 따라, 전 세계의 연구 기관과 거대 기술 기업들은 &amp;lsquo;오류 내성 양자 컴퓨팅(FTQC: Fault-Tolerant Quantum Computing)&amp;lsquo;의 실현으로 방향을 틀었습니다.&lt;/p>
&lt;p>이 글에서는 2026년의 최신 돌파구를 교차하며, 양자 컴퓨터의 현재 위치를 깊이 파고듭니다. 특히, 양자 오류 정정(표면 부호), 물리 큐비트와 논리 큐비트의 차이, 위상 양자 컴퓨팅의 진전, 그리고 초전도·이온 트랩 방식의 최전선에 대해 자세히 설명합니다.&lt;/p>
&lt;hr>
&lt;h2 id="2-양자-상태의-기초와-피델리티충실도">2. 양자 상태의 기초와 피델리티(충실도)
&lt;/h2>&lt;p>양자 컴퓨터의 기본 단위인 양자 비트(Qubit)는 고전적인 비트(0 또는 1)와는 달리, 0과 1의 중첩 상태(Superposition)를 취할 수 있습니다. 단일 큐비트의 상태는 힐베르트 공간 상의 벡터로서 다음과 같이 표현됩니다.&lt;/p>
$$
|\psi\rangle = \alpha|0\rangle + \beta|1\rangle
$$
&lt;p>여기서 $\alpha$와 $\beta$는 복소수인 확률 진폭이며, 다음의 정규화 조건을 만족합니다.&lt;/p>
$$
|\alpha|^2 + |\beta|^2 = 1
$$
&lt;p>양자 컴퓨팅의 성능을 측정하는 데 있어 매우 중요한 지표가 **피델리티(Fidelity)**입니다. 이상적인 양자 상태 $|\psi\rangle$와 노이즈에 의해 열화되어 혼합 상태가 된 실제 밀도 행렬 $\rho$ 간의 피델리티 $F$는 다음과 같이 정의됩니다.&lt;/p>
$$
F(\rho, |\psi\rangle) = \langle \psi | \rho | \psi \rangle
$$
&lt;p>2026년 현재, 2큐비트 게이트(예: CNOT 게이트나 CZ 게이트)의 피델리티는 초전도 방식에서 **99.99%**의 장벽(이른바 &amp;lsquo;4나인&amp;rsquo;)을 안정적으로 넘어서게 되었습니다. 이는 표면 부호에 의한 오류 정정 임계값(약 99%)을 크게 웃도는 수치이며, 상용화를 향한 가장 큰 돌파구 중 하나입니다.&lt;/p>
&lt;hr>
&lt;h2 id="3-nisq-시대의-한계와-ftqc로의-패러다임-전환">3. NISQ 시대의 한계와 FTQC로의 패러다임 전환
&lt;/h2>&lt;p>2010년대 후반부터 2020년대 전반에 걸쳐서는 오류 정정을 가지지 않는 수십~수백 큐비트의 디바이스인 NISQ(Noisy Intermediate-Scale Quantum)의 시대였습니다. 하지만, NISQ에는 명확한 한계가 있었습니다.&lt;/p>
&lt;p>회로의 깊이(Depth)가 증가함에 따라 오류가 지수 함수적으로 축적되어, 의미 있는 계산 결과를 얻는 것이 불가능해집니다. 회로의 깊이 $D$에서의 전체 성공 확률 $P_{success}$는 단일 게이트의 피델리티 $f$와 게이트 수 $N$에 대해 다음과 같이 감쇠합니다.&lt;/p>
$$
P_{success} \approx f^N
$$
&lt;p>만약 $f = 0.99$에서 1000개의 게이트를 적용한 경우, $0.99^{1000} \approx 4.3 \times 10^{-5}$가 되어 결과는 거의 랜덤 노이즈에 묻혀버리게 됩니다. 이로 인해 2026년에는 NISQ 알고리즘(VQE나 QAOA 등)의 직접적인 스케일업보다는 **논리 큐비트(Logical Qubit)**의 생성에 자원이 집중되고 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="4-양자-오류-정정과-논리-큐비트-표면-부호의-최전선">4. 양자 오류 정정과 논리 큐비트: 표면 부호의 최전선
&lt;/h2>&lt;p>양자 오류 정정(QEC: Quantum Error Correction)은 여러 &amp;lsquo;물리 큐비트&amp;rsquo;를 인코딩하여 하나의 &amp;lsquo;논리 큐비트&amp;rsquo;를 만들어내어, 오류를 감지하고 정정하는 기술입니다. 현재 가장 유망하게 여겨지는 것이 **표면 부호(Surface Code)**입니다.&lt;/p>
&lt;h3 id="41-표면-부호surface-code의-구조">4.1 표면 부호(Surface Code)의 구조
&lt;/h3>&lt;p>표면 부호에서는 큐비트를 2차원 격자 형태로 배치합니다. 데이터 큐비트(실제 정보를 유지)와 측정 큐비트(신드롬 측정용)가 체크무늬처럼 늘어섭니다.&lt;/p>
&lt;div class="mermaid">graph TD
A["데이터 큐비트 (D1)"] --- B["측정 큐비트 (M1)"]
B --- C["데이터 큐비트 (D2)"]
C --- D["측정 큐비트 (M2)"]
D --- E["데이터 큐비트 (D3)"]
B --- F["데이터 큐비트 (D4)"]
D --- G["데이터 큐비트 (D5)"]
style A fill:#e1f5fe,stroke:#039be5
style C fill:#e1f5fe,stroke:#039be5
style E fill:#e1f5fe,stroke:#039be5
style F fill:#e1f5fe,stroke:#039be5
style G fill:#e1f5fe,stroke:#039be5
style B fill:#fff3e0,stroke:#fb8c00
style D fill:#fff3e0,stroke:#fb8c00&lt;/div>
&lt;p>안정자(Stabilizer) 연산자 $S_x$와 $S_z$를 사용하여, 비트 반전(X 오류)과 위상 반전(Z 오류)을 끊임없이 감시합니다.&lt;/p>
$$
S_x = \prod_{i \in \text{star}} X_i, \quad S_z = \prod_{j \in \text{plaquette}} Z_j
$$
&lt;p>2026년의 중대한 진전은 &amp;lsquo;손익분기점(Break-even point)&amp;lsquo;을 완전히 돌파했다는 것입니다. 즉, 오류 정정을 수행하기 위한 여분의 회로가 일으키는 노이즈보다 오류 정정에 의해 제거되는 노이즈가 더 커지게 되어, 논리 큐비트의 수명이 물리 큐비트의 수명을 몇 자릿수나 웃돌게 되었습니다.&lt;/p>
&lt;h3 id="42-양자-오류-정정의-사이클">4.2 양자 오류 정정의 사이클
&lt;/h3>&lt;p>오류 정정은 지속적인 피드백 루프로 기능합니다.&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant D as "데이터 큐비트"
participant M as "보조/측정 큐비트"
participant C as "고전 컨트롤러"
loop "신드롬 추출 사이클 (약 1 마이크로초)"
D->>M: "얽힘 (CNOT/CZ)"
M->>C: "상태 측정 (신드롬)"
C->>C: "신드롬 디코딩 (예: 최소 가중치 완벽 매칭)"
C-->>D: "파울리 정정 적용 (필요시)"
end&lt;/div>
&lt;p>현재는 이러한 고전적인 디코딩 처리(신드롬 해석)를 FPGA나 전용 ASIC에서 나노초 단위로 실행하는 기술이 확립되어, 실시간 오류 정정이 실용화 단계에 접어들었습니다.&lt;/p>
&lt;hr>
&lt;h2 id="5-하드웨어-아키텍처의-진화-2026년판">5. 하드웨어 아키텍처의 진화 (2026년판)
&lt;/h2>&lt;p>2026년의 양자 하드웨어는 주로 &amp;lsquo;초전도 방식&amp;rsquo;, &amp;lsquo;이온 트랩 방식&amp;rsquo;, &amp;lsquo;위상(Topological) 방식&amp;rsquo;의 세 가지 축으로 진화하고 있습니다.&lt;/p>
&lt;h3 id="51-초전도-큐비트의-집적화">5.1 초전도 큐비트의 집적화
&lt;/h3>&lt;p>초전도 방식은 IBM이나 Google이 이끌고 있는 분야이며, 조셉슨 접합을 이용한 트랜즈몬(Transmon) 큐비트가 주류입니다. 2026년에는 단일 칩 상에 수천에서 일만 개의 물리 큐비트를 집적하는 메가 칩이 실현되었습니다.&lt;/p>
&lt;p>특기할 만한 점은 **모듈 간 양자 통신(Quantum Interconnects)**의 확립입니다. 마이크로파 광자를 이용한 칩 간의 양자 원격 이동(Quantum Teleportation)이 상용 수준에서 구현되어, 단일 희석 냉동기의 크기 제한을 우회할 수 있게 되었습니다.&lt;/p>
&lt;h3 id="52-이온-트랩의-2차원-스케일링과-광-인터커넥트">5.2 이온 트랩의 2차원 스케일링과 광 인터커넥트
&lt;/h3>&lt;p>이온 트랩 방식(Quantinuum이나 IonQ 등이 주도)에서는 진공 중에 부유하는 이온의 내부 에너지 상태를 큐비트로 사용합니다. 초전도 방식과 비교하여 T1/T2 결맞음(Coherence) 시간이 극히 길고, 전결합(All-to-All Connectivity)이 가능하다는 장점이 있습니다.&lt;/p>
&lt;p>2026년의 돌파구는 QCCD(Quantum Charge Coupled Device) 아키텍처의 2차원화와 광 인터커넥트(Photonic Interconnect)를 이용한 다중 트랩 간의 고속 얽힘 생성입니다. 이로 인해 이온 트랩의 약점이었던 &amp;lsquo;느린 게이트 속도&amp;rsquo;와 &amp;lsquo;확장성&amp;rsquo;이 극적으로 개선되었습니다.&lt;/p>
&lt;h3 id="53-위상-양자-컴퓨팅-애니온의-제어">5.3 위상 양자 컴퓨팅: 애니온의 제어
&lt;/h3>&lt;p>오랫동안 이론상의 존재로 여겨져 왔던 &lt;strong>위상 양자 컴퓨팅&lt;/strong>이 2026년에 마침내 실험적인 실증 단계에 들어섰습니다. Microsoft 등이 추진하는 이 방식은 &amp;lsquo;마요라나 제로 모드(Majorana Zero Modes)&amp;lsquo;라고 불리는 비가환 애니온(Non-Abelian Anyons)을 사용합니다.&lt;/p>
&lt;p>애니온 입자의 위치를 맞바꾸는 &amp;lsquo;브레이딩(Braiding, 땋기)&amp;lsquo;이라는 조작을 통해 양자 게이트를 실행합니다.&lt;/p>
$$
|\psi_{final}\rangle = B_{ij} |\psi_{initial}\rangle
$$
&lt;p>여기서 $B_{ij}$는 브레이딩 연산자입니다. 위상 방식은 정보의 저장이 입자의 국소적인 상태가 아니라 전체의 &amp;lsquo;매듭&amp;rsquo; 위상(Topology)에 의존하기 때문에, 환경 노이즈에 대해 본질적으로 내성(하드웨어 수준에서의 오류 내성)을 가지고 있습니다. 2026년에 세계 최초로 고충실도의 위상 논리 큐비트 생성이 확인되어, FTQC를 향한 강력한 지름길로 주목받고 있습니다.&lt;/p>
&lt;hr>
&lt;h2 id="6-실용화를-향한-로드맵과-전망">6. 실용화를 향한 로드맵과 전망
&lt;/h2>&lt;p>양자 컴퓨터가 &amp;lsquo;화학 계산&amp;rsquo;, &amp;lsquo;재료 과학&amp;rsquo;, &amp;lsquo;금융 모델링&amp;rsquo; 등에서 고전 컴퓨터(슈퍼 컴퓨터)를 압도하는 **양자 우위(Quantum Advantage)**를 진정으로 발휘하기 위해서는 수천 개의 논리 큐비트가 필요합니다.&lt;/p>
&lt;div class="mermaid">gantt
title "양자 컴퓨팅 로드맵 (2026년 개정)"
dateFormat YYYY
axisFormat %Y
section "NISQ 시대"
"노이즈 큐비트 (&lt;1000)" :done, 2018, 2024
section "초기 FTQC"
"손익분기점 실증" :done, 2024, 2026
"수백 개의 논리 큐비트" :active, 2026, 2028
section "본격적인 FTQC"
"1000개 이상의 논리 큐비트 (상용 앱)" : 2028, 2030
"범용 오류 내성 양자 컴퓨터" : 2030, 2035&lt;/div>
&lt;h3 id="61-현재-직면한-과제와-미래">6.1 현재 직면한 과제와 미래
&lt;/h3>&lt;p>2026년 시점에서의 최대 과제는 극저온을 유지하기 위한 거대한 크라이오스탯(희석 냉동기)의 냉각 능력과, 실온의 제어 장치와 극저온의 양자 칩을 연결하는 배선(I/O 병목 현상)입니다. 이에 대응하여 극저온 환경에서 동작하는 CMOS(Cryo-CMOS) 컨트롤러 칩의 개발이 빠른 속도로 진행되고 있습니다.&lt;/p>
&lt;h3 id="결론">결론
&lt;/h3>&lt;p>2026년은 양자 컴퓨터의 역사에 있어 &amp;lsquo;논리 큐비트 스케일업 원년&amp;rsquo;으로 기록될 것입니다. 오류 정정 알고리즘의 실증, 하드웨어의 모듈화, 그리고 위상 접근법의 급진전에 의해 &amp;lsquo;실용화되는 날&amp;rsquo;은 더 이상 먼 미래의 이야기가 아니라, 향후 몇 년 이내의 구체적인 이정표로 내다볼 수 있게 되었습니다. 양자 알고리즘 개발자나 기업들에게 있어, 지금이야말로 양자 네이티브한 문제 해결에 본격적으로 투자해야 할 적기라고 할 수 있습니다.&lt;/p>
&lt;hr>
&lt;p>&lt;em>이 글은 2026년 시점의 최신 양자 컴퓨팅 연구 논문 및 업계 동향을 바탕으로 작성되었습니다.&lt;/em>&lt;/p></description></item></channel></rss>