<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cryptography on kenji.blog</title><link>http://kenji.blog/categories/cryptography/</link><description>Recent content in Cryptography on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ja</language><copyright>kenjinote</copyright><lastBuildDate>Fri, 11 Sep 2026 22:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/categories/cryptography/index.xml" rel="self" type="application/rss+xml"/><item><title>フェルマーの小定理を利用した暗号化の基礎と実装</title><link>http://kenji.blog/p/fermats-little-theorem-cryptography-implementation/</link><pubDate>Fri, 11 Sep 2026 22:00:00 +0900</pubDate><guid>http://kenji.blog/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>現代のデジタル社会、とりわけインターネットを介した通信において、「暗号化」は不可欠な基盤技術となっています。私たちがウェブブラウザでHTTPS経由で安全にウェブサイトを閲覧し、オンラインバンキングで金融取引を行い、メッセージングアプリでプライベートなやり取りができるのは、高度な数学的理論に裏打ちされた暗号プロトコルが背後で機能しているからです。その中でも特に重要な役割を担っているのが「公開鍵暗号方式」であり、その代表格が &lt;strong>RSA暗号&lt;/strong> です。&lt;/p>
&lt;p>RSA暗号をはじめとする多くの暗号アルゴリズムの安全性や正当性は、17世紀のフランスの数学者ピエール・ド・フェルマー（Pierre de Fermat）が発見した非常に美しく、かつ強力な定理に大きく依存しています。それが &lt;strong>フェルマーの小定理（Fermat&amp;rsquo;s Little Theorem）&lt;/strong> です。さらに、これを一般化したレオンハルト・オイラー（Leonhard Euler）の定理も、暗号理論において決定的な役割を果たしています。&lt;/p>
&lt;p>本記事では、フェルマーの小定理という純粋数学の発見が、いかにして現代の実用的な暗号技術、特に「素数判定」や「RSA暗号」へと応用されているのかを、基礎から徹底的に解説します。数学的な証明、暗号化・復号のメカニズム、そして C++ と Python を用いた具体的なアルゴリズムの実装までを網羅する、非常に詳細な技術ガイドとなります。&lt;/p>
&lt;hr>
&lt;h2 id="2-合同式とモジュラ演算の基礎">2. 合同式とモジュラ演算の基礎
&lt;/h2>&lt;p>フェルマーの小定理を理解するためには、まず「モジュラ演算（合同式）」という数学の概念に慣れ親しむ必要があります。モジュラ演算とは、ある決まった数（法、モジュラスと呼ばれる）で割った「余り」に着目した計算体系のことです。時計の文字盤（12時間で1周する）のような計算であるため、「時計の数学」とも呼ばれます。&lt;/p>
&lt;p>整数 $a$ と $b$ を正の整数 $n$ で割った余りが等しいとき、数学的には以下のように記述します。&lt;/p>
$$
a \equiv b \pmod n
$$
&lt;p>これは「$n$ を法として $a$ と $b$ は合同である」と読みます。例えば、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）である場合に限られます。この「モジュラ逆元」の概念は、後述する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>また、条件「$a$ が $p$ の倍数でない」を外し、すべての整数 $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$ を法として合同になる2つの異なる要素は存在しません。もし $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>フェルマーの小定理は「素数 $p$」に関する定理ですが、これを「任意の正の整数 $n$」に一般化したのが、レオンハルト・オイラーです。RSA暗号を理解するには、この拡張が不可欠です。&lt;/p>
&lt;h3 id="41-オイラーのトーティエント関数-phin">4.1 オイラーのトーティエント関数 $\phi(n)$
&lt;/h3>&lt;p>オイラーのトーティエント関数（あるいはオイラーの $\phi$ 関数） $\phi(n)$ は、「$1$ から $n$ までの整数のうち、$n$ と互いに素であるものの個数」を表す関数です。&lt;/p>
&lt;ul>
&lt;li>素数 $p$ の場合、$1$ から $p-1$ までのすべての整数が $p$ と互いに素なので、$\phi(p) = p - 1$ となります。&lt;/li>
&lt;li>2つの異なる素数 $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鍵交換など）においては、数百桁にも及ぶ「巨大な素数」を高速に見つけ出す必要があります。しかし、巨大な数 $N$ が素数かどうかを判定するために、$2$ から $\sqrt{N}$ までのすべての数で割り切れるか試す「試し割り法」では、宇宙の寿命ほどの時間がかかってしまいます。&lt;/p>
&lt;p>そこで登場するのが、フェルマーの小定理を逆手に取った「確率的素数判定法」である &lt;strong>フェルマーテスト（Fermat Primality Test）&lt;/strong> です。&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$ が必ず成立します。
この対偶をとると、「ある $a$ について $a^{p-1} \not\equiv 1 \pmod p$ となった場合、$p$ は&lt;strong>絶対に素数ではない（合成数である）&lt;/strong>」ということが言えます。&lt;/p>
&lt;p>したがって、$N$ が素数かどうかを判定したい場合、ランダムにいくつか $a$ を選び、$a^{N-1} \pmod N$ を計算して $1$ になるかを確認します。もし1回でも $1$ 以外の答えが出れば、$N$ は合成数であると確定します。何度試しても $1$ になる場合、$N$ は「おそらく素数である」と高い確率で判断できます。&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 -- "Yes" --> RandomA["1 &lt; a &lt; p-1 の範囲でランダムな整数 a を選択"]
RandomA --> Calc["モジュラべき乗 a^(p-1) mod p を計算"]
Calc --> CheckPrime{"結果は 1 か？"}
CheckPrime -- "No" --> ReturnComposite["p は合成数である（確定）"]
CheckPrime -- "Yes" --> Increment["i をインクリメント"]
Increment --> Condition
Condition -- "No" --> 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$ を満たしてしまう悪魔のような数が存在することです。これを &lt;strong>カーマイケル数 (Carmichael numbers)&lt;/strong> と呼びます。最も小さいカーマイケル数は $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など）が必要ですが、ここではアルゴリズムを理解するために64bit整数（&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">// オーバーフロー防止のため128bit拡張
&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の3人によって開発された &lt;strong>RSA暗号&lt;/strong> です。
RSA暗号は「公開鍵暗号」という画期的なシステムであり、暗号化するための鍵（公開鍵）は全世界に公開しておきながら、復号するための鍵（秘密鍵）は受信者本人だけが知っているという仕組みを実現しています。&lt;/p>
&lt;p>この非対称性は、「巨大な合成数の素因数分解は極めて困難である」という計算量的安全性に基づいています。&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>2つの巨大な素数 $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>、&lt;strong>秘密鍵は $(N, d)$&lt;/strong> となります。（$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暗号の鍵生成・暗号化・復号のプロセスを実装してみましょう。これは教育用の「トイ（おもちゃ）実装」ですが、使われている数学は本物と全く同じです。&lt;/p>
&lt;p>モジュラ逆元 $d$ を求めるための「拡張ユークリッドの互除法」も実装に含めます。&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"># 12bitの素数を使用&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>ピエール・ド・フェルマーがこの「小定理」を発見した17世紀当時、これが何かの役に立つと考えた人は皆無でした。フェルマー自身も純粋な数学的探求心から数論の研究を行っていました。&lt;/p>
&lt;p>しかし、約300年後の1970年代、コンピュータネットワークの黎明期において、安全な通信プロトコルを確立するために不可欠な暗号技術として、フェルマーの定理は劇的な復活を遂げました。フェルマーの小定理に基づく素数判定技術と、オイラーの定理に基づくRSA暗号は、現代のインターネットインフラを文字通り支えています。&lt;/p>
&lt;p>私たちが日々何気なく送っているLINEのメッセージも、Amazonでの買い物も、すべてはこの $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/p/lattice-based-cryptography-math-intuition/</link><pubDate>Fri, 11 Sep 2026 21:00:00 +0900</pubDate><guid>http://kenji.blog/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）をはじめとする公開鍵暗号技術です。これらの暗号方式は、「素因数分解問題」や「離散対数問題」といった、従来の古典コンピュータでは効率的に解くことができない（指数関数的な時間を要する）と信じられている数学的な困難性に安全性の根拠を置いています。&lt;/p>
&lt;p>しかし、1994年にピーター・ショア（Peter Shor）によって発表された「Shorのアルゴリズム」は、暗号界に激震を走らせました。このアルゴリズムは、大規模な量子コンピュータが実現した暁には、素因数分解問題や離散対数問題を多項式時間で解いてしまうことを数学的に証明したのです。これはつまり、現在広く利用されている公開鍵暗号が、将来的に完全に解読可能になってしまうということを意味しています。&lt;/p>
&lt;p>このような「量子コンピュータの脅威（Quantum Threat）」に対抗するため、量子コンピュータを用いても解読が困難な新しい暗号方式の研究が急務となりました。これが「ポスト量子暗号（Post-Quantum Cryptography: PQC）」または「耐量子計算機暗号」と呼ばれる分野です。&lt;/p>
&lt;p>PQCにはいくつかの有力な候補が存在します。ハッシュベース暗号、符号ベース暗号、多変数多項式暗号、同種写像暗号などが挙げられますが、その中でも現在最も注目を集め、NIST（米国国立標準技術研究所）によるPQC標準化プロセスの中心となっているのが「格子暗号（Lattice-based cryptography）」です。格子暗号は、他の方式と比較して、暗号化・復号の処理速度が非常に高速であり、また「最悪時計算量（Worst-case complexity）」から「平均時計算量（Average-case complexity）」への帰着という、暗号理論において極めて強力な安全性の証明を持つという際立った特徴を持っています。&lt;/p>
&lt;p>本記事では、この格子暗号の基礎となる「格子（Lattice）」の数学的定義から出発し、格子上の困難な問題であるSVP（最短ベクトル問題）やCVP（最近接ベクトル問題）、そして現代の格子暗号の心臓部とも言える「LWE問題（Learning With Errors）」について、数式と幾何学的な直観、そして具体的な数値例を交えて、徹底的に深く解説していきます。&lt;/p>
&lt;h1 id="2-格子latticeの数学的定義と幾何学的直観">2. 格子（Lattice）の数学的定義と幾何学的直観
&lt;/h1>&lt;h2 id="21-ベクトル空間と格子">2.1 ベクトル空間と格子
&lt;/h2>&lt;p>数学において、「格子（Lattice）」とは、$n$次元実ベクトル空間 $\mathbb{R}^n$ 内に規則正しく並んだ離散的な点の集合のことです。線形代数学で学ぶベクトル空間（Vector Space）と似ていますが、決定的な違いがあります。ベクトル空間が基底ベクトルの「実数係数」での線形結合で表される連続的な空間であるのに対し、格子は基底ベクトルの「整数係数」での線形結合で表される離散的な空間です。&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$ を格子の「基底（Basis）」と呼びます。&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}$ に限定されているという点です。これにより、空間内に無数に存在する連続的な点ではなく、等間隔に配置された交差点のような「離散的な点の集合」が形成されます。&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$ の集合となります。これは最も単純な「正方格子」です。&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>格子暗号の安全性の根幹に関わる重要な性質があります。それは、「同一の格子を生成する基底は無数に存在する」ということです。&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$ を「ユニモジュラ行列（Unimodular matrix）」と呼びます。&lt;/p>
&lt;p>暗号への応用における基本的なアイデアは、「良い基底（直交に近く、短いベクトルからなる基底）」を秘密鍵とし、「悪い基底（互いに極端に斜交し、非常に長いベクトルからなる基底）」を公開鍵として用いることです。悪い基底から良い基底を計算で求めることは、次元が高くなると非常に困難になります。これが格子暗号の基本的な直観です。&lt;/p>
&lt;h1 id="3-格子における計算困難な問題">3. 格子における計算困難な問題
&lt;/h1>&lt;p>格子暗号の安全性は、格子上の特定の数学的問題を解くことの困難性に依存しています。ここでは、最も基本的かつ有名な2つの問題を紹介します。&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)$ に属する非ゼロベクトルの中で、ユークリッドノルム（長さ）が最小となるベクトル $\mathbf{v}$ を見つけよ。&lt;/p>
&lt;p>数式で表すと、$\min_{\mathbf{v} \in \mathcal{L}(B) \setminus \{\mathbf{0}\}} \| \mathbf{v} \|$ となる $\mathbf{v}$ を求める問題です。この最小の長さを $\lambda_1(\mathcal{L})$ と書き、「格子の第一連続最小値（First successive minimum）」と呼びます。&lt;/p>
&lt;p>2次元や3次元の低い次元であれば、図を描けば目で見て一番短いベクトルを見つけることができます。あるいは、ガウスの格子簡約アルゴリズムなどを使って効率的に解くことができます。しかし、次元 $n$ が数百〜数千といった高次元になると、SVPを厳密に解くことはNP困難であることが知られています。&lt;/p>
&lt;p>現実の暗号では、厳密な最短ベクトルではなく、「近似的に短いベクトル」を見つける近似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困難です。暗号への応用という観点では、後述する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アルゴリズムは多項式時間で動作し、格子基底をある程度「良い基底」に簡約（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によって提唱された「LWE問題（Learning With Errors）」をベースにしています。LWE問題の美しさは、その定式化のシンプルさと、「最悪時計算量から平均時計算量への帰着」という強力な数学的証明を持っている点にあります。&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)$ のペアを与えられた場合、私たちは線形代数における「ガウスの消去法（Gaussian elimination）」を用いることで、容易に秘密ベクトル $\mathbf{s}$ を復元することができます。これは多項式時間で簡単に解ける問題です。&lt;/p>
&lt;h2 id="42-lwe問題の定義ノイズを加える">4.2 LWE問題の定義：ノイズを加える
&lt;/h2>&lt;p>では、この問題にわずかな「ノイズ（誤差）」を加えたらどうなるでしょうか？
これが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}$ を求めるのが「探索LWE問題（Search LWE problem）」です。&lt;/p>
&lt;p>誤差 $e_i$ が入っているため、ガウスの消去法を使おうとすると、方程式を足し引きする過程で誤差が指数関数的に増幅してしまい、正しい答えに辿り着くことができなくなります。一見すると単純な連立一次方程式に見えますが、この小さなノイズが加わるだけで、問題の難易度がNP困難なレベルへと跳ね上がるのです。&lt;/p>
&lt;h2 id="43-決定lwe問題decision-lwe">4.3 決定LWE問題（Decision LWE）
&lt;/h2>&lt;p>暗号理論の証明において頻繁に用いられるのは、探索LWE問題のバリエーションである「決定LWE問題（Decision LWE problem）」です。&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分布から得られたペアは、完全にランダムなデータのペアと「計算量的に識別不可能（Computationally Indistinguishable）」になります。この性質が、LWEベースの暗号が「乱数と区別がつかない暗号文」を生成できる根拠となっています。&lt;/p>
&lt;h2 id="44-最悪時計算量から平均時計算量への帰着regevの定理">4.4 最悪時計算量から平均時計算量への帰着（Regevの定理）
&lt;/h2>&lt;p>Oded Regevの最大の功績は、このLWE問題の難しさを、前述の格子問題（SVPやCVP）の難しさに数学的に結びつけたことです。&lt;/p>
&lt;p>彼は量子還元（Quantum reduction）を用いて、「もしLWE問題を平均的に（ランダムに選ばれた $A$ と $\mathbf{e}$ に対して）解くことができる多項式時間のアルゴリズムが存在するならば、任意の格子の最悪ケース（最も難しいケース）のGap-SVPを解くことができる多項式時間の量子アルゴリズムが存在する」ということを証明しました。（後に、Peikertらによって古典的な還元も示されています）。&lt;/p>
&lt;p>これは暗号理論において夢のような性質です。なぜなら、「暗号が破られるのは、我々がたまたま弱い鍵（平均的なケースの一部）を選んでしまったからかもしれない」という懸念を払拭し、「平均的なLWEが解けるなら、格子の全ての難しい問題が解けてしまう（だからLWEは絶対に難しい）」という強力な保証を与えてくれるからです。&lt;/p>
&lt;div class="mermaid">graph TD
A["Worst-case Lattice Problems (Gap-SVP, SIVP)"] -->|Quantum/Classical Reduction| B["Average-case LWE Problem"]
B -->|Cryptographic Construction| C["LWE-based Cryptosystems (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を用いた公開鍵暗号方式regev暗号の構築">5. LWEを用いた公開鍵暗号方式（Regev暗号）の構築
&lt;/h1>&lt;p>LWE問題の困難性を理解したところで、それを使ってどのように暗号化と復号を行うのか、Oded Regevが提案した基本的な公開鍵暗号方式を見ていきましょう。ここでは、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>公開鍵はまさに「LWE問題のインスタンス」そのものです。公開鍵 $(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}$ に対して、「ランダムな部分集合の和」をとることです。決定LWE問題の困難性により、この暗号文 $(\mathbf{u}, v)$ は、完全にランダムなベクトルと一様乱数と区別がつかないように見えます（意味的安全性：Semantic Security）。&lt;/p>
&lt;div class="mermaid">flowchart LR
M["Message M in {0,1}"] --> Enc
PK["Public Key (A, b)"] --> Enc
r["Random binary vector r"] --> Enc
subgraph Enc ["Encryption Process"]
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["Ciphertext (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["Ciphertext (u, v)"] --> Dec
SK["Secret Key s"] --> Dec
subgraph Dec ["Decryption Process"]
direction TB
calc["Compute D = v - s^T * u mod q"]
check["Check if D is closer to 0 or q/2"]
end
calc --> check
Dec --> M_out["Recovered Message 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）は、非常に強力な安全性証明を持っていますが、実用上において致命的な弱点があります。それは「鍵のサイズが巨大になること」と「計算コストが高いこと」です。&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）という代数的な構造を格子に組み込んだ「Ring-LWE（RLWE）」や「Module-LWE（MLWE）」です。&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）に類似した「数論変換（Number Theoretic Transform: NTT）」を用いることで、計算量を $\mathcal{O}(n \log n)$ にまで劇的に削減できます。さらに、公開鍵のサイズも行列から単一の多項式へと小さくなるため、データサイズが $\mathcal{O}(n)$ に削減されます。これは通信帯域において圧倒的な優位性をもたらします。&lt;/p>
&lt;p>数学的に見ると、Ring-LWEは一般的な格子ではなく、「イデアル格子（Ideal Lattice）」と呼ばれる特殊な対称性を持った格子上の問題に帰着します。&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の効率性の「いいとこ取り」をしたのが「Module-LWE（MLWE）」です。&lt;/p>
&lt;p>Module-LWEでは、多項式を要素とする小さな行列とベクトルを考えます。つまり、環上のモジュール（加群）を扱います。
現在、NISTがPQCの鍵共有アルゴリズム（KEM）の標準として選定した「CRYSTALS-Kyber」（標準化名称：ML-KEM）は、まさにこのModule-LWE問題の困難性に基づいて構築されています。&lt;/p>
&lt;h1 id="8-なぜ量子コンピュータに対して安全なのか">8. なぜ量子コンピュータに対して安全なのか？
&lt;/h1>&lt;p>最後に、「なぜ格子暗号は量子コンピュータを用いても解読されないと考えられているのか？」という核心部分に触れておきます。&lt;/p>
&lt;p>量子コンピュータがRSA暗号や楕円曲線暗号を破るShorのアルゴリズムは、本質的には「隠れ部分群問題（Hidden Subgroup Problem: HSP）」を解くアルゴリズムです。RSAやECCの背景にある数学的構造（有限アーベル群）は周期性を持っており、量子フーリエ変換（QFT）という量子アルゴリズム特Actions:特有の操作を用いることで、この周期（隠れた部分群）を一気に抽出することができます。&lt;/p>
&lt;p>しかし、格子問題は根本的に異なります。格子にも周期性はありますが、SVPやCVPで求められているのは「最短の距離」や「ノイズの除去」という幾何学的な非線形な性質です。Shorのアルゴリズムのような「アーベル群上の量子フーリエ変換」をそのまま適用しても、格子問題の解答となる有用な情報を効率的に抽出することができません。現在までに、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> は、基底ベクトルの整数係数線形結合で表される離散的な空間であり、高次元においては直交に近い「良い基底」を見つけること（SVP）が困難になります。&lt;/li>
&lt;li>&lt;strong>LWE問題（Learning With Errors）&lt;/strong> は、ノイズ付きの連立一次方程式を解く問題であり、これが格子の最悪ケース問題の困難性に結びついているため、強力な安全性の根拠を提供します。&lt;/li>
&lt;li>LWE問題を利用することで、ノイズを意図的に加えたり消去したりする巧妙な仕組みにより、暗号化と復号（&lt;strong>Regev暗号&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>量子コンピュータという未曾有の計算パラダイムシフトが迫る中、古典的な線形代数と整数論の深淵から生まれた「格子暗号」が、未来のインターネットセキュリティの基盤を担うというのは非常にロマンのある話です。格子暗号の基礎となる数学は決して難解すぎるものではなく、線形代数と確率の基礎知識があれば十分にその美しい構造を理解することができます。本記事が、PQCの核となる格子暗号の理解への一助となれば幸いです。&lt;/p></description></item><item><title>ゼロ知識証明（ZKP）の仕組みと最新のWeb3・セキュリティへの応用</title><link>http://kenji.blog/p/zero-knowledge-proofs-zkp-web3-security/</link><pubDate>Fri, 11 Sep 2026 19:00:00 +0900</pubDate><guid>http://kenji.blog/p/zero-knowledge-proofs-zkp-web3-security/</guid><description>&lt;img src="http://kenji.blog/p/zero-knowledge-proofs-zkp-web3-security/img/eyecatch.jpg" alt="Featured image of post ゼロ知識証明（ZKP）の仕組みと最新のWeb3・セキュリティへの応用" />&lt;h2 id="はじめに">はじめに
&lt;/h2>&lt;p>現代のデジタル社会において、データプライバシーとスケーラビリティは最も重要な課題の2つとなっています。個人情報の漏洩や不正利用のリスクが高まる中、「自分に関する情報を相手に明かすことなく、自分がその情報を持っていることを証明する」技術が強く求められています。これを実現するのが**ゼロ知識証明（Zero-Knowledge Proof: ZKP）**です。&lt;/p>
&lt;p>ゼロ知識証明は、1980年代にShafi Goldwasser、Silvio Micali、Charles Rackoffによって初めて提唱された暗号理論の概念ですが、長らく理論的な研究にとどまっていました。しかし、ブロックチェーン技術とWeb3の台頭により、状況は一変しました。Ethereumなどのパブリックブロックチェーンが直面するスケーラビリティ問題（処理能力の限界）とプライバシー問題（すべてのトランザクションが公開されること）を同時に解決する「魔法の杖」として、ZKPは一躍脚光を浴びることとなったのです。&lt;/p>
&lt;p>本記事では、ゼロ知識証明の基本的な概念から、現在主流となっている&lt;strong>zk-SNARKs&lt;/strong>および&lt;strong>zk-STARKs&lt;/strong>の深淵なる数学的・暗号学的メカニズム、そしてZK-Rollupsや分散型アイデンティティ（DID）といった最新のWeb3・セキュリティへの応用例に至るまで、極めて詳細かつ技術的に深く掘り下げて解説します。&lt;/p>
&lt;hr>
&lt;h2 id="ゼロ知識証明zkpとは何か">ゼロ知識証明（ZKP）とは何か？
&lt;/h2>&lt;p>ゼロ知識証明（ZKP）とは、ある命題が真であることを、証明者（Prover）が検証者（Verifier）に対して証明する際に、「その命題が真であること以外のいかなる情報も伝達しない」ようなプロトコルのことを指します。&lt;/p>
&lt;h3 id="zkpが満たすべき3つの要件">ZKPが満たすべき3つの要件
&lt;/h3>&lt;p>ZKPとして成立するためには、以下の3つの特性を厳密に満たす必要があります。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>完全性（Completeness）&lt;/strong>
命題が真であり、かつ証明者と検証者の双方が正しくプロトコルに従うならば、検証者は圧倒的な確率でその証明を受理（Accept）しなければなりません。&lt;/li>
&lt;li>&lt;strong>健全性（Soundness）&lt;/strong>
命題が偽であるならば、いかに計算能力が高く、悪意のある証明者であったとしても、検証者を騙して証明を受理させることは（無視できるほど小さな確率を除いて）不可能です。&lt;/li>
&lt;li>&lt;strong>ゼロ知識性（Zero-Knowledge）&lt;/strong>
命題が真である場合、検証者は「命題が真である」という事実以外のいかなる情報も、証明プロセスから得ることはできません。検証者の視点から見れば、証明プロセスをシミュレートすることが可能である（シミュレータが存在する）という数学的定義によって証明されます。&lt;/li>
&lt;/ol>
&lt;h3 id="インタラクティブ証明と非インタラクティブ証明">インタラクティブ証明と非インタラクティブ証明
&lt;/h3>&lt;p>ZKPには、証明者と検証者が複数回の通信を行う&lt;strong>インタラクティブ（対話型）証明&lt;/strong>と、証明者が一度だけ証明データを送信して終わる&lt;strong>非インタラクティブ（非対話型）証明&lt;/strong>の2種類が存在します。&lt;/p>
&lt;h4 id="インタラクティブ証明interactive-zkp">インタラクティブ証明（Interactive ZKP）
&lt;/h4>&lt;p>初期のZKPは対話型プロトコルとして設計されました。有名な「アリババの洞窟」の例え話がこれに該当します。一般的なプロトコルの流れは以下のようになります。&lt;/p>
&lt;div class="mermaid">sequenceDiagram
participant Prover as "Prover (証明者)"
participant Verifier as "Verifier (検証者)"
Note over Prover, Verifier: "インタラクティブ証明プロトコルの基本フロー"
Prover->>Verifier: "1. コミットメントを送信 (Commitment)"
Verifier->>Prover: "2. ランダムなチャレンジを送信 (Challenge)"
Prover->>Verifier: "3. レスポンスを計算し送信 (Response)"
Note over Verifier: "レスポンスを検証 (Verification)"
Verifier-->>Prover: "4. 受理または拒否 (Accept / Reject)"
Note over Prover, Verifier: "※確度を高めるため、これを数十回繰り返す"&lt;/div>
&lt;p>この方法は強力ですが、検証者がオンラインでなければならず、ブロックチェーンのような非同期的な分散システムに適用するには不便です。ブロックチェーンでは、誰もがいつでも過去の証明を検証できなければなりません。&lt;/p>
&lt;h4 id="フィアットシャミア変換fiat-shamir-heuristicと非対話化">フィアット・シャミア変換（Fiat-Shamir Heuristic）と非対話化
&lt;/h4>&lt;p>インタラクティブ証明を非インタラクティブ証明（Non-Interactive Zero-Knowledge Proof: NIZK）に変換するための画期的な手法が&lt;strong>フィアット・シャミア変換&lt;/strong>です。&lt;/p>
&lt;p>検証者が送信する「ランダムなチャレンジ」の代わりに、証明者が自分自身のコミットメントと公開情報のハッシュ値を用いて「擬似ランダムなチャレンジ」を自己生成します。暗号学的ハッシュ関数（例えばSHA-256やKeccakなど）がランダムオラクルとして機能することを前提とすれば、証明者はチャレンジを事前に予測・操作することができず、インタラクティブ証明と同等のセキュリティを保ったまま、1回のメッセージ送信で証明を完了させることができます。&lt;/p>
&lt;hr>
&lt;h2 id="zk-snarksの技術的詳細">zk-SNARKsの技術的詳細
&lt;/h2>&lt;p>現在、ZKPの中で最も広く利用されているのが&lt;strong>zk-SNARKs&lt;/strong>（Zero-Knowledge Succinct Non-Interactive Argument of Knowledge）です。名前の通り、ゼロ知識性（zk）を持ち、証明サイズが非常に小さく検証が高速（Succinct）で、非対話型（Non-Interactive）である知識の議論（Argument of Knowledge）です。&lt;/p>
&lt;p>zk-SNARKsの基盤となるのは、高度な代数幾何学と暗号理論です。プログラムの実行や計算を、特定の多項式の方程式の検証へと変換します。&lt;/p>
&lt;h3 id="1-算術回路とr1csrank-1-constraint-systemへの変換">1. 算術回路とR1CS（Rank-1 Constraint System）への変換
&lt;/h3>&lt;p>まず、証明したい任意の計算（アルゴリズムやスマートコントラクトのロジック）を、加算ゲートと乗算ゲートからなる**算術回路（Arithmetic Circuit）**に変換します。&lt;/p>
&lt;p>次に、この算術回路を**R1CS（Rank-1 Constraint System）**という行列方程式の集合に変換します。R1CSは、変数ベクトル $x$ に対して、次のような制約を満たす行列 $A, B, C$ を見つける問題です。&lt;/p>
$$ (A \cdot x) \circ (B \cdot x) = C \cdot x $$
&lt;p>ここで、$\circ$ はアダマール積（要素ごとの積）を表します。この制約は、回路内のすべての論理ゲート（特に乗算ゲート）が正しく計算されていることを保証します。&lt;/p>
&lt;h3 id="2-qapquadratic-arithmetic-programへの変換">2. QAP（Quadratic Arithmetic Program）への変換
&lt;/h3>&lt;p>R1CSの行列制約は無数に存在するため、これらを個別に検証するのは非常に非効率です。そこで、ラグランジュ補間を用いて、これらの制約を単一の多項式方程式に圧縮します。これが**QAP（Quadratic Arithmetic Program）**です。&lt;/p>
&lt;p>QAPへの変換により、証明すべき問題は「特定の多項式 $P(x)$ が、別の既知の多項式 $Z(x)$ で割り切れるか？」という問題に帰着します。&lt;/p>
$$ P(x) = L(x) \cdot R(x) - O(x) $$
&lt;p>ここで、$L(x), R(x), O(x)$ はそれぞれ行列 $A, B, C$ の各行に対応する多項式を組み合わせたものです。もし証明者が正しい解（Witness）を知っていれば、$P(x)$ の各根（評価点）で値が0になるため、$P(x)$ はターゲット多項式 $Z(x)$ を因数として持つことになります。すなわち、ある多項式 $H(x)$ が存在して、次式が成り立ちます。&lt;/p>
$$ P(x) = H(x) \cdot Z(x) $$
&lt;p>検証者は、あるランダムな秘密の点 $s$ において、この方程式 $P(s) = H(s) \cdot Z(s)$ が成立するかどうかをチェックするだけで、計算全体が正しく行われたことを瞬時に検証できるのです。これが「Succinct（簡潔性）」の秘密です。&lt;/p>
&lt;h3 id="3-楕円曲線暗号とペアリングbilinear-pairings">3. 楕円曲線暗号とペアリング（Bilinear Pairings）
&lt;/h3>&lt;p>しかし、検証者が秘密の点 $s$ を知っていては、証明者が偽の多項式を捏造して方程式を満たすことが可能になってしまいます（健全性の崩壊）。そこで、$s$ を誰にも知られないように暗号化（準同型暗号を利用）したまま計算を行う必要があります。&lt;/p>
&lt;p>これを実現するのが**楕円曲線ペアリング（Bilinear Pairings）**です。
ペアリング $e$ は、2つの暗号化された値から、それらの積の暗号化に相当する値を計算できる特殊な関数です。&lt;/p>
$$ e(g_1^a, g_2^b) = e(g_1, g_2)^{ab} $$
&lt;p>証明者は、$s$ 自体を知らなくても、$s$ の累乗の暗号化された値（これをCRS: Common Reference String と呼びます）を用いて、多項式 $P(s)$ や $H(s)$ の暗号化された値を計算します。検証者は、ペアリング関数を用いて暗号化された値のまま $P(s) = H(s) \cdot Z(s)$ の関係が成立しているかを検証します。&lt;/p>
&lt;h3 id="4-トラステッドセットアップtrusted-setup">4. トラステッド・セットアップ（Trusted Setup）
&lt;/h3>&lt;p>zk-SNARKs（特に初期のGroth16など）の最大の弱点は、秘密の点 $s$ を生成するプロセス、いわゆる&lt;strong>トラステッド・セットアップ&lt;/strong>が必要なことです。もし、$s$ の生成者がその値を破棄せずに保持していれば、任意の偽の証明を生成できてしまいます（Toxic Waste問題）。&lt;/p>
&lt;p>これを防ぐため、Multi-Party Computation（MPC）を用いた「Ceremony」と呼ばれる儀式が実施されます。多数の参加者が協力してランダムネスを提供し、少なくとも1人の参加者が正直に自身のランダムな値を破棄すれば、システム全体のセキュリティが保たれる仕組みです。しかし、この依存関係を排除するための研究が長年続けられてきました。&lt;/p>
&lt;hr>
&lt;h2 id="zk-starksの技術的詳細">zk-STARKsの技術的詳細
&lt;/h2>&lt;p>トラステッド・セットアップへの依存と、量子コンピュータによる楕円曲線暗号の解読リスクに対する回答として登場したのが&lt;strong>zk-STARKs&lt;/strong>（Zero-Knowledge Scalable Transparent Argument of Knowledge）です。&lt;/p>
&lt;p>Eli Ben-Sassonらによって開発されたSTARKsは、「Transparent（透明性）」の名の通りトラステッド・セットアップを一切必要とせず、「Scalable（スケーラビリティ）」の名の通り、計算量が増えても証明サイズと検証時間が効率的に保たれるという特徴を持っています。&lt;/p>
&lt;h3 id="1-多項式コミットメントとfriプロトコル">1. 多項式コミットメントとFRIプロトコル
&lt;/h3>&lt;p>zk-STARKsは楕円曲線暗号ではなく、&lt;strong>ハッシュ関数のみ&lt;/strong>にセキュリティの根拠を置いています。そのため、耐量子計算機暗号（Post-Quantum Cryptography）としての性質を持ちます。&lt;/p>
&lt;p>計算の検証は、AIR（Algebraic Intermediate Representation）と呼ばれる形式に変換された後、一次元または多次元の多項式の性質を利用して行われます。STARKsの核心は、**FRI（Fast Reed-Solomon Interactive Oracle Proof of Proximity）**プロトコルにあります。&lt;/p>
&lt;p>FRIプロトコルは、「ある関数が特定の次数の多項式に十分近いか（Proximity）」を検証する技術です。証明者は、多項式の値をマークルツリー（Merkle Tree）のリーフとしてコミット（多項式コミットメント）します。&lt;/p>
&lt;div class="mermaid">graph TD
Root["Merkle Root (コミットメント)"] --> Node0["Node 0"]
Root --> Node1["Node 1"]
Node0 --> Leaf0["P(x_0)"]
Node0 --> Leaf1["P(x_1)"]
Node1 --> Leaf2["P(x_2)"]
Node1 --> Leaf3["P(x_3)"]&lt;/div>
&lt;p>検証者は、ランダムな数点を開示するように要求し、マークルプルーフを用いてそれらがコミットメントに含まれていることを確認します。これを再帰的に繰り返すことで、元の多項式の次数が実際に低いことを圧倒的な確率で保証します。&lt;/p>
&lt;h3 id="zk-snarksとzk-starksの比較">zk-SNARKsとzk-STARKsの比較
&lt;/h3>&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:left">特徴&lt;/th>
&lt;th style="text-align:left">zk-SNARKs&lt;/th>
&lt;th style="text-align:left">zk-STARKs&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>暗号学的仮定&lt;/strong>&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>トラステッド・セットアップ&lt;/strong>&lt;/td>
&lt;td style="text-align:left">必要（Plonkなどはユニバーサル）&lt;/td>
&lt;td style="text-align:left">不要（Transparent）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>耐量子性&lt;/strong>&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>証明サイズ&lt;/strong>&lt;/td>
&lt;td style="text-align:left">非常に小さい（~200 Byte）&lt;/td>
&lt;td style="text-align:left">やや大きい（数十 KB）&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>証明生成の計算コスト&lt;/strong>&lt;/td>
&lt;td style="text-align:left">高い&lt;/td>
&lt;td style="text-align:left">SNARKsより比較的低い&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:left">&lt;strong>検証コスト（Gas代）&lt;/strong>&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;p>近年では、PlonkやHalo2のように「トラステッド・セットアップが不要、あるいは一度だけで済むSNARKs」が登場し、SNARKsとSTARKsの境界は徐々に曖昧になりつつありますが、基本的な数学的アプローチの違いは重要です。&lt;/p>
&lt;hr>
&lt;h2 id="ゼロ知識証明のweb3とセキュリティへの最新の応用">ゼロ知識証明のWeb3とセキュリティへの最新の応用
&lt;/h2>&lt;p>理論から実践へと移行したZKPは、現在Web3やサイバーセキュリティの最前線で革命を起こしています。&lt;/p>
&lt;h3 id="1-zk-rollupsによるethereumの究極的スケーリング">1. ZK-RollupsによるEthereumの究極的スケーリング
&lt;/h3>&lt;p>EthereumのようなL1（レイヤー1）ブロックチェーンは、分散性とセキュリティを重視するあまり、スケーラビリティに大きな制約（トリレンマ）を抱えています。これを解決するL2（レイヤー2）ソリューションの決定版が&lt;strong>ZK-Rollups&lt;/strong>です。&lt;/p>
&lt;p>ZK-Rollupでは、何千ものトランザクションをオフチェーン（L2）で実行・処理し、それらがすべて正しく実行されたことを示す「1つのZKP（Validity Proof）」を生成します。L1チェーン上のスマートコントラクトは、この証明を検証するだけで済みます。&lt;/p>
&lt;div class="mermaid">flowchart LR
Users["Users (Tx送信)"] --> Sequencer["Sequencer (Tx収集・実行)"]
Sequencer --> Prover["Prover (ZKP生成)"]
Sequencer --> L1Contract["L1 Smart Contract (Txデータ公開)"]
Prover --> L1Contract["ZKP (証明)の提出"]
L1Contract --> Verify["検証 &amp; 状態更新"]&lt;/div>
&lt;p>ZK-Rollupsの最大の利点は、Optimistic Rollups（ArbitrumやOptimismなど）とは異なり、不正証明（Fraud Proof）のためのチャレンジ期間（通常7日間）が不要である点です。暗号学的に正しさが保証されているため、証明が検証された瞬間にL1への資金の引き出し（Finality）が完了します。現在、zkSync、Starknet、Scroll、Polygon zkEVMなどのプロジェクトが熾烈な開発競争を繰り広げており、EVM（Ethereum Virtual Machine）と互換性を持つ&lt;strong>zkEVM&lt;/strong>の実現がエコシステムを急成長させています。&lt;/p>
&lt;h3 id="2-プライバシー保護アイデンティティzkp-for-identity">2. プライバシー保護アイデンティティ（ZKP for Identity）
&lt;/h3>&lt;p>デジタル世界における個人認証のあり方もZKPによって根本から変わります。
例えば、「あなたは18歳以上ですか？」という質問に対して、従来のシステムでは運転免許証やパスポートを提示し、氏名や住所といった不要な個人情報まで相手に渡してしまっていました。&lt;/p>
&lt;p>ZKPを用いれば、公的機関が発行したデジタル証明書（Verifiable Credential）を元に、「私の生年月日から計算すると、現在の日付において18歳以上である」という&lt;strong>事実だけを数学的に証明&lt;/strong>することが可能になります。検証者は証明書の署名とZKPを検証するだけでよく、ユーザーの生年月日や身元を知ることはできません。&lt;/p>
&lt;p>WorldcoinのようなProof of Personhood（人間性の証明）プロジェクトでも、虹彩データを直接保存・共有するのではなく、ZKPを用いて「一意の人間であること」だけを証明する仕組みが取り入れられています。&lt;/p>
&lt;h3 id="3-機密スマートコントラクトとエンタープライズ利用">3. 機密スマートコントラクトとエンタープライズ利用
&lt;/h3>&lt;p>パブリックブロックチェーンの「すべてのデータが公開される」という性質は、企業が機密の取引やサプライチェーン情報をブロックチェーン上で扱う際の大きな障壁でした。&lt;/p>
&lt;p>ZKP技術（例えばAleoやAztecなどのプライバシー特化型ネットワーク）を用いれば、トランザクションの入力値、出力値、さらには実行されるスマートコントラクトのロジック自体を暗号化したまま、状態の更新の正当性だけをパブリックチェーンに刻むことができます。これにより、DeFi（分散型金融）におけるフロントランニング（MEV）の防止や、企業間での機密コンソーシアムネットワークの構築が、パブリックチェーンの高いセキュリティを享受しながら実現可能となります。&lt;/p>
&lt;hr>
&lt;h2 id="zkpの今後の課題と展望">ZKPの今後の課題と展望
&lt;/h2>&lt;p>ZKPは間違いなく次世代の基盤技術ですが、いくつかの課題も残されています。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>証明生成の計算コストとハードウェアアクセラレーション&lt;/strong>
ZKPの生成には、膨大な多項式演算やFFT（高速フーリエ変換）、MSM（マルチスカラー乗算）が必要です。現在、この証明生成を高速化するための専用ハードウェア（FPGAやASIC）の開発、いわゆる&lt;strong>ZKPマイニング&lt;/strong>（Prover Network）の研究が急速に進んでいます。&lt;/li>
&lt;li>&lt;strong>標準化と開発者体験（DX）の向上&lt;/strong>
Circom、Cairo、Noir、Leoなど、ZKP回路を記述するための専用言語が乱立しています。これらを統一する標準規格や、既存のRustやC++から自動的にZKP回路を生成するコンパイラの成熟が、一般的なソフトウェアエンジニアによるZKP導入の鍵となるでしょう。&lt;/li>
&lt;/ol>
&lt;h2 id="おわりに">おわりに
&lt;/h2>&lt;p>ゼロ知識証明（ZKP）は、単なる「暗号通貨の匿名性を高める技術」から、「インターネット全体のトラスト（信用）を再定義する汎用技術」へと進化を遂げました。数式と暗号理論の奥深くで計算された小さな証明が、ブロックチェーンのスケーラビリティを無限に拡張し、私たちのプライバシーを強固に守る盾となります。&lt;/p>
&lt;p>Web3の真のマスアダプション、そしてセキュアでプライベートな次世代インターネットの構築に向けて、ゼロ知識証明は最も重要なピースとして機能し続けるでしょう。今後のZKP技術の進化から目が離せません。&lt;/p>
&lt;hr>
&lt;p>&lt;em>参考文献・関連リンク&lt;/em>&lt;/p>
&lt;ul>
&lt;li>Groth, J. (2016). &amp;ldquo;On the Size of Pairing-based Non-interactive Arguments&amp;rdquo;&lt;/li>
&lt;li>Ben-Sasson, E., et al. (2018). &amp;ldquo;Scalable, transparent, and post-quantum secure computational integrity&amp;rdquo;&lt;/li>
&lt;li>Vitalik Buterin&amp;rsquo;s blog on zk-SNARKs and zk-STARKs&lt;/li>
&lt;/ul></description></item><item><title>リーマン予想と素数の分布：現代暗号技術との深い関係</title><link>http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/</link><pubDate>Fri, 11 Sep 2026 16:00:00 +0900</pubDate><guid>http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/</guid><description>&lt;img src="http://kenji.blog/p/riemann-hypothesis-prime-distribution-cryptography/img/eyecatch.jpg" alt="Featured image of post リーマン予想と素数の分布：現代暗号技術との深い関係" />&lt;h1 id="1-はじめに素数という宇宙の神秘とリーマン予想">1. はじめに：素数という宇宙の神秘とリーマン予想
&lt;/h1>&lt;p>「素数（Prime Numbers）」は、1と自分自身でしか割り切れない自然数であり、数学の世界における「原子」とも呼ばれます。2, 3, 5, 7, 11, 13&amp;hellip; と続くこの数列は、一見すると無秩序でランダムに現れるように見えます。古代ギリシャの数学者ユークリッドが「素数が無限に存在すること」を証明して以来、数え切れないほどの数学者たちがこの素数の並びに潜む規則性を解き明かそうと挑んできました。&lt;/p>
&lt;p>その素数の謎に最も肉薄したのが、1859年にドイツの数学者ベルンハルト・リーマン（Bernhard Riemann）が提唱した**「リーマン予想（Riemann Hypothesis）」**です。リーマン予想は、現代数学において最も重要かつ未解決の難問の一つであり、クレイ数学研究所が定めるミレニアム懸賞問題の一つとして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>素数がどのように分布しているのかを理解するために、数学者たちは「ある数 $x$ 以下の素数がいくつ存在するか」を表す&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)$ の間には常に「誤差」すなわち「揺らぎ」が存在します。この揺らぎの正体こそが、リーマン予想が解き明かそうとしている最大のミステリーなのです。&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$ でしか収束しませんが、リーマンは「解析接続（Analytic Continuation）」という手法を用いて、$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$ を「零点」と呼びます。
関数等式から、$s$ が負の偶数（$-2, -4, -6, \dots$）のとき、$\sin(\pi s / 2)$ が 0 になるため、$\zeta(s) = 0$ となります。これらは**自明な零点（Trivial zeros）**と呼ばれます。&lt;/p>
&lt;p>しかし、素数の分布において重要なのは、それ以外の零点、すなわち $0 \le \sigma \le 1$ の「臨界領域（Critical strip）」に存在する**非自明な零点（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の直線を「臨界線（Critical line）」と呼びます。&lt;/p>
&lt;div class="mermaid">graph TD
A["リーマンゼータ関数 ζ(s)"] --> B["解析接続による複素数平面への拡張"]
B --> C["自明な零点 (s = -2, -4, -6 ...)"]
B --> D["非自明な零点 (0 &lt;= Re(s) &lt;= 1)"]
D --> E["リーマン予想"]
E --> F["すべての非自明な零点は Re(s) = 1/2 上にある"]
F --> G["素数分布の誤差項の極限の証明へ"]&lt;/div>
&lt;p>なぜリーマン予想がそれほど重要なのでしょうか？それは、ゼータ関数の零点が素数の分布を&lt;strong>完全に&lt;/strong>決定しているからです。&lt;/p>
&lt;p>リーマンと後の数学者フォン・マンゴルトは、素数の分布を正確に記述する「明示公式（Explicit formula）」を導きました。チェビシェフ関数 $\psi(x)$ を用いると、以下のように表されます。&lt;/p>
$$ \psi(x) = x - \sum_{\rho} \frac{x^\rho}{\rho} - \ln(2\pi) - \frac{1}{2}\ln(1 - x^{-2}) $$
&lt;p>ここで $\rho$ はゼータ関数の非自明な零点をすべてわたる和です。
主項は $x$ （これは素数定理に対応）であり、そこから零点 $\rho$ に依存する波のような項を足し引きすることで、素数の階段状の正確な分布が復元されるのです。非自明な零点は、素数の分布の「周波数（波）」を表していると言えます。&lt;/p>
&lt;p>もしリーマン予想が正しく、すべての非自明な零点 $\rho$ の実部がちょうど $1/2$ であるならば、素数定理の誤差項は理論上考えうる最小の範囲に収まることになります。&lt;/p>
$$ |\pi(x) - \text{Li}(x)| \le \frac{1}{8\pi} \sqrt{x} \ln x \quad \text{for} \quad x \ge 2657 $$
&lt;p>つまり、&lt;strong>リーマン予想が真であれば、素数は私たちが想像し得る限り最も「規則正しく、美しく」分布している&lt;/strong>ことが証明されるのです。&lt;/p>
&lt;hr>
&lt;h1 id="6-現代暗号技術と素数の不可分な関係">6. 現代暗号技術と素数の不可分な関係
&lt;/h1>&lt;p>ここまでは深遠な純粋数学の世界でしたが、この素数の性質は現代のデジタル社会を根底から支えています。その代表が&lt;strong>RSA暗号&lt;/strong>をはじめとする公開鍵暗号方式です。&lt;/p>
&lt;p>インターネットでのクレジットカード決済、パスワードの送信、ブロックチェーンの電子署名など、あらゆる通信の安全性は「素数」に依存しています。&lt;/p>
&lt;h3 id="rsa暗号の仕組み">RSA暗号の仕組み
&lt;/h3>&lt;p>RSA暗号の安全性は、「桁数の大きい合成数の素因数分解は非常に困難である」という数学的事実（素因数分解問題）に基づいています。&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>鍵生成&lt;/strong>:
巨大な素数 $p$ と $q$ （例えばそれぞれ2048ビット）をランダムに選びます。
それらを掛け合わせて $N = p \times q$ を計算します。この $N$ が公開鍵の一部となります。
オイラーのトーティエント関数 $\phi(N) = (p-1)(q-1)$ を用いて、秘密鍵 $d$ を生成します。&lt;/p>
$$ e \times d \equiv 1 \pmod{\phi(N)} $$
&lt;/li>
&lt;li>
&lt;p>&lt;strong>暗号化と復号&lt;/strong>:
平文 $M$ は、公開鍵 $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>では、純粋数学の頂点にある「リーマン予想」と「暗号技術」はどのように交差するのでしょうか？&lt;/p>
&lt;h3 id="71-素数生成アルゴリズム素数判定と拡張リーマン予想grh">7.1. 素数生成アルゴリズム（素数判定）と拡張リーマン予想（GRH）
&lt;/h3>&lt;p>RSA暗号を運用するためには、最初に巨大な素数 $p$ と $q$ を生成する必要があります。しかし、「ある数が素数かどうか」を確実かつ高速に判定するのは簡単ではありません。&lt;/p>
&lt;p>現在、実用的に使われているのは**ミラー・ラビン素数判定法（Miller-Rabin primality test）**という確率的アルゴリズムです。このアルゴリズムは高速ですが、極めて低い確率で合成数を素数と誤判定する「擬素数」のリスクがあります。&lt;/p>
&lt;p>しかし、リーマン予想をディリクレのL関数に拡張した**「拡張リーマン予想（Generalized Riemann Hypothesis, GRH）」&lt;strong>が真であると仮定すると、話は劇的に変わります。
GRHが真であれば、ミラー・ラビン判定法におけるテスト回数の上限が数学的に保証され、確率的アルゴリズムから&lt;/strong>「決定性多項式時間アルゴリズム」へと昇華**するのです（これは、AKS素数判定法が発見される以前から知られていた重大な事実でした）。&lt;/p>
&lt;p>つまり、リーマン予想（およびその拡張）は、「巨大な素数を絶対の自信を持って高速に生成できるか」という暗号の基盤生成に直接的なお墨付きを与える役割を持っています。&lt;/p>
&lt;h3 id="72-素因数分解アルゴリズムとの関係">7.2. 素因数分解アルゴリズムとの関係
&lt;/h3>&lt;p>暗号を解読する側のアルゴリズム（一般数体篩法など）の計算量を評価する際にも、素数の分布に関する知識が不可欠です。素因数分解アルゴリズムの多くは「滑らかな数（Smooth numbers：小さな素因数しか持たない数）」の分布に依存しています。&lt;/p>
&lt;p>滑らかな数がどの程度の頻度で現れるかを厳密に評価するためには、素数の分布に関する深い理解が必要であり、ここでもゼータ関数やリーマン予想に直結する解析的整数論のテクニックが駆使されています。リーマン予想が証明され、素数分布の誤差が完全に決定されれば、素因数分解アルゴリズムの性能限界もより正確に見極めることが可能になります。&lt;/p>
&lt;hr>
&lt;h1 id="8-もしリーマン予想が証明されたら暗号は破られるのか">8. もしリーマン予想が証明されたら、暗号は破られるのか？
&lt;/h1>&lt;p>都市伝説のように「リーマン予想が解けたらRSA暗号は一瞬で崩壊する」と語られることがありますが、&lt;strong>これは数学的には不正確&lt;/strong>です。&lt;/p>
&lt;p>リーマン予想の証明自体が、直ちに素因数分解を劇的に高速化する魔法のアルゴリズムを生み出すわけではありません。リーマン予想はあくまで素数の「巨視的な分布の規則性」についての定理であり、個別の数 $N$ がどの素数で割り切れるか（局所的な性質）を直接教えてくれるものではないからです。&lt;/p>
&lt;p>しかし、影響はゼロではありません。
リーマン予想が証明される過程で、&lt;strong>「新たな数学的ツール」や「未知の解析手法」が発見される可能性&lt;/strong>が極めて高いからです。歴史を見ても、フェルマーの最終定理やポアンカレ予想が証明された際、その過程で開発された新しい理論が数学全体を大きく飛躍させました。&lt;/p>
&lt;p>もしリーマンゼータ関数の零点の性質を完全に操作できる未知の代数幾何学的手法や、非可換幾何の手法が確立されれば、それが結果として素因数分解の画期的なアルゴリズム（例えば、計算量を多項式時間に落とし込むような古典アルゴリズム）の発見に繋がる可能性は否定できません。その意味で、暗号学者はリーマン予想の動向から決して目を離すことができないのです。&lt;/p>
&lt;h3 id="量子コンピュータとショアのアルゴリズム">量子コンピュータとショアのアルゴリズム
&lt;/h3>&lt;p>暗号技術にとってより直接的で現実的な脅威は、リーマン予想の証明ではなく&lt;strong>量子コンピュータ&lt;/strong>です。1994年にピーター・ショア（Peter Shor）が発表した「ショアのアルゴリズム」は、十分な性能を持つ量子コンピュータがあれば、素因数分解を多項式時間で解けることを証明しました。これにより、RSA暗号や楕円曲線暗号は根本的に破られることになります。&lt;/p>
&lt;p>現在、世界中で量子コンピュータでも解読できない「耐量子計算機暗号（Post-Quantum Cryptography, PQC）」への移行（格子暗号など）が進められています。素数に依存した暗号技術は、ある意味で黄金期を終えようとしているのかもしれませんが、素数そのものの数学的価値が失われることは永遠にありません。&lt;/p>
&lt;hr>
&lt;h1 id="9-結び数学の抽象性と現実社会の交差点">9. 結び：数学の抽象性と現実社会の交差点
&lt;/h1>&lt;div class="mermaid">graph TD
A["純粋数学の探求"] --> B["リーマン予想の解明"]
B --> C["素数分布の完全な理解"]
C --> D["数論・代数幾何学の飛躍的発展"]
D -.-> E["新しい素因数分解アルゴリズムの可能性"]
E -.-> F["暗号技術の安全性評価の更新"]
A --> G["応用数学・計算機科学"]
G --> H["素数判定・暗号生成の効率化"]
H --> F&lt;/div>
&lt;p>古代ギリシャから続く素数への飽くなき探求は、リーマンという天才によって複素数平面上の美しいシンフォニー（ゼータ関数の零点）へと昇華されました。そして驚くべきことに、その純粋無垢な数学の結晶が、数世紀の時を経てインターネット社会の安全を担保する最強の盾として応用されています。&lt;/p>
&lt;p>リーマン予想は、数学が持つ「抽象的な美しさ」と「物理世界・現実社会への驚異的な適用力」を同時に象徴する存在です。&lt;/p>
&lt;p>未だ誰も頂上に到達していないこの巨大な数学の山がいつの日か征服されたとき、私たちは素数という宇宙の真理を完全に理解するとともに、情報化社会の基盤に対して新たな視点を持つことになるでしょう。暗号技術を学ぶことは、そのまま人類の英知の歴史を辿る旅でもあるのです。&lt;/p></description></item><item><title>暗号技術の歴史：シーザー暗号から耐量子暗号（PQC）まで</title><link>http://kenji.blog/p/history-of-cryptography-caesar-to-pqc/</link><pubDate>Fri, 11 Sep 2026 15:00:00 +0900</pubDate><guid>http://kenji.blog/p/history-of-cryptography-caesar-to-pqc/</guid><description>&lt;img src="http://kenji.blog/p/history-of-cryptography-caesar-to-pqc/img/eyecatch.jpg" alt="Featured image of post 暗号技術の歴史：シーザー暗号から耐量子暗号（PQC）まで" />&lt;h1 id="1-はじめに暗号技術とは何か">1. はじめに：暗号技術とは何か？
&lt;/h1>&lt;p>暗号技術（Cryptography）は、情報の秘匿性を保つための技術であり、人類の歴史とともに進化してきました。古代の戦争における秘密司令の伝達から、現代のインターネットにおけるクレジットカード情報の保護に至るまで、暗号の目的は一貫しています。それは「意図された受信者だけが情報を理解でき、第三者には解読できないようにする」ことです。&lt;/p>
&lt;p>現代の情報セキュリティにおいて、暗号技術は単なる「情報の秘匿（機密性: Confidentiality）」にとどまらず、データの「完全性（Integrity）」、「認証（Authentication）」、「否認防止（Non-repudiation）」といった重要な役割を担っています。&lt;/p>
&lt;p>本記事では、古代の単純な換字式暗号から始まり、機械式暗号、現代の共通鍵・公開鍵暗号、そして量子コンピュータの実用化によって到来する「耐量子暗号（PQC）」の時代まで、暗号技術の進化の歴史を技術的・数学的な観点から詳細に紐解いていきます。&lt;/p>
&lt;hr>
&lt;h1 id="2-古典暗号の時代文字の置き換えと並べ替え">2. 古典暗号の時代：文字の置き換えと並べ替え
&lt;/h1>&lt;p>暗号の起源は紀元前にまで遡ります。初期の暗号は主に「転置（並べ替え）」と「換字（置き換え）」の2つのアプローチで構成されていました。&lt;/p>
&lt;h2 id="スキュタレー暗号転置式暗号">スキュタレー暗号（転置式暗号）
&lt;/h2>&lt;p>紀元前5世紀の古代ギリシャ・スパルタで使用された「スキュタレー（Scytale）」は、最古の暗号器具の一つです。特定の太さの木の棒に細長い羊皮紙を巻き付け、そこに横書きでメッセージを記します。紙を解くと文字が意味不明な順序に並びますが、同じ太さの棒を持つ受信者が再び紙を巻き付けることで、元のメッセージが読めるという仕組みでした。&lt;/p>
&lt;h2 id="シーザー暗号単一換字式暗号">シーザー暗号（単一換字式暗号）
&lt;/h2>&lt;p>紀元前1世紀、古代ローマの英雄ユリウス・カエサル（シーザー）が使用したとされるのが「シーザー暗号」です。これはアルファベットを一定数（通常は3文字）シフトさせる単一換字式暗号（Monoalphabetic substitution）です。&lt;/p>
&lt;p>数学的には、文字を $0$ から $25$ までの数値として扱い、シフト数を $K$ とすると、平文 $P$ から暗号文 $C$ への変換は以下の合同式で表されます。&lt;/p>
$$C \equiv P + K \pmod{26}$$
&lt;p>復号は逆の操作を行います。&lt;/p>
$$P \equiv C - K \pmod{26}$$
&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;/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="c1"># シーザー暗号のシンプルなPython実装例&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">caesar_cipher&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">shift&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mode&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;encrypt&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">result&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&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">mode&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="s2">&amp;#34;decrypt&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">shift&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="n">shift&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">char&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">text&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">char&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isalpha&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="nb">ord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;A&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">char&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isupper&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="nb">ord&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;a&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="c1"># シフト計算&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="nb">chr&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="o">-&lt;/span> &lt;span class="n">base&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">shift&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">%&lt;/span> &lt;span class="mi">26&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">base&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">result&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">char&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>&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">plaintext&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;HELLO WORLD&amp;#34;&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="n">caesar_cipher&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">plaintext&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;encrypt&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;暗号文: &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 class="c1"># KHOOR ZRUOG&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>単一換字式暗号は、9世紀のアラブの学者アル・キンディーによって考案された「頻度分析（Frequency Analysis）」によって容易に解読されるようになりました。英語であれば「E」や「T」が頻出するという言語の統計的特性を利用したものです。&lt;/p>
&lt;p>これに対抗するため、16世紀に考案されたのが「ヴィジュネル暗号（Vigenère cipher）」です。これは複数のシフト（鍵）を周期的に切り替えて使用する多表式換字暗号（Polyalphabetic substitution）であり、約300年もの間「解読不能な暗号（Le Chiffre Indéchiffrable）」と呼ばれていました。&lt;/p>
&lt;p>数学的には、平文の $i$ 番目の文字 $P_i$ と、繰り返される鍵の $i$ 番目の文字 $K_i$ を用いて次のように暗号化します。&lt;/p>
$$C_i \equiv P_i + K_i \pmod{26}$$
&lt;p>この暗号も、19世紀に入りチャールズ・バベッジやフリードリヒ・カシスキーによって、暗号文中の繰り返しのパターンから鍵の長さを割り出す「カシスキー触媒（Kasiski examination）」が発見され、解読されることになります。&lt;/p>
&lt;div class="mermaid">graph TD
subgraph "古典暗号の分類"
A["古典暗号"] --> B["転置式暗号"]
A --> C["換字式暗号"]
B --> D["スキュタレー暗号"]
C --> E["単一換字式"]
C --> F["多表式換字式"]
E --> G["シーザー暗号"]
F --> H["ヴィジュネル暗号"]
end&lt;/div>
&lt;hr>
&lt;h1 id="3-機械式暗号と世界大戦エニグマとその解読">3. 機械式暗号と世界大戦：エニグマとその解読
&lt;/h1>&lt;p>20世紀に入ると、通信手段は手紙から電信や無線へと移行し、暗号化の速度と複雑さが求められるようになりました。ここで登場したのが、ローター（回転盤）を組み合わせた「機械式暗号」です。&lt;/p>
&lt;h2 id="エニグマenigmaの脅威">エニグマ（Enigma）の脅威
&lt;/h2>&lt;p>第二次世界大戦中、ナチス・ドイツが使用した「エニグマ」は、暗号技術史において最も有名な暗号機です。エニグマは複数のローター（通常3〜4個）と、文字の配線を入れ替えるプラグボード（Steckerbrett）、そしてリフレクター（反転ローター）で構成されていました。&lt;/p>
&lt;p>キーボードで1文字打ち込むたびにローターが回転するため、同じ文字を連続して打っても異なる暗号文字が出力されます（多表式暗号の極地）。その鍵空間（設定の組み合わせ）は約 $1.58 \times 10^{19}$ 通り（約1580京）にも及び、当時の技術では総当たりによる解読は不可能とされていました。&lt;/p>
&lt;h2 id="アランチューリングとボンブbombe">アラン・チューリングと「ボンブ（Bombe）」
&lt;/h2>&lt;p>この難攻不落のエニグマに挑んだのが、ポーランドの数学者マリアン・レイェフスキらの初期の成果を引き継いだ、イギリスのブレッチリー・パークの暗号解読チームです。&lt;/p>
&lt;p>特にアラン・チューリング（Alan Turing）は、暗号文の一部に対応する平文の推測（クリブ: Crib）を利用し、電気機械式の解読機「ボンブ（Bombe）」を開発しました。ボンブは論理的な矛盾を高速に検出し、あり得ないローター設定を次々と排除することで、エニグマの解読に成功しました。この偉業は、連合国側の勝利を数年早めたと言われています。&lt;/p>
&lt;hr>
&lt;h1 id="4-現代暗号の幕開け共通鍵暗号desとaes">4. 現代暗号の幕開け：共通鍵暗号（DESとAES）
&lt;/h1>&lt;p>戦後、コンピュータの登場により、暗号は「文字」の操作から「ビット（0と1）」の操作へと劇的なパラダイムシフトを遂げます。&lt;/p>
&lt;h2 id="クロードシャノンと情報理論">クロード・シャノンと情報理論
&lt;/h2>&lt;p>1949年、クロード・シャノンは論文「秘話系の通信理論」を発表し、現代暗号の数学的基礎を築きました。彼は安全な暗号設計の原則として「混乱（Confusion）」と「拡散（Diffusion）」を提唱しました。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>混乱（Confusion）&lt;/strong>: 鍵と暗号文の関係を可能な限り複雑にすること。（換字・Sボックスによる実現）&lt;/li>
&lt;li>&lt;strong>拡散（Diffusion）&lt;/strong>: 平文の1ビットの変更が、暗号文の多くのビットに影響を与えるようにすること。（転置・並べ替えによる実現）&lt;/li>
&lt;/ul>
&lt;h2 id="des-data-encryption-standard">DES (Data Encryption Standard)
&lt;/h2>&lt;p>1977年、アメリカ国立標準技術研究所（NIST、当時はNBS）は、IBMの設計をベースとした「DES」を標準暗号として制定しました。
DESは「ファイステル構造（Feistel Network）」と呼ばれるアーキテクチャを採用しており、64ビットのブロック長と56ビットの鍵長を持ちます。暗号化と復号のアルゴリズムがほぼ同じ構造になるという実装上の利点がありました。&lt;/p>
&lt;p>しかし、コンピュータの計算能力が向上するにつれ、56ビットの鍵長（約 $7.2 \times 10^{16}$ 通り）では不十分であることが明らかになります。1998年には電子フロンティア財団（EFF）が専用マシン「Deep Crack」を開発し、数日でDESを解読してみせました。&lt;/p>
&lt;h2 id="aes-advanced-encryption-standard">AES (Advanced Encryption Standard)
&lt;/h2>&lt;p>DESに代わる新たな標準として、2001年に制定されたのが「AES」です。公募によって選ばれたベルギーの暗号学者による「Rijndael（ラインダール）」アルゴリズムが採用されました。&lt;/p>
&lt;p>AESはファイステル構造ではなく「SPN構造（Substitution-Permutation Network）」を採用し、ガロア体（有限体） $GF(2^8)$ 上の数学的演算を利用しています。鍵長は128、192、256ビットから選択でき、現在でも世界中で標準的な共通鍵暗号として広く利用されています。&lt;/p>
&lt;div class="mermaid">graph TD
subgraph "AESの1ラウンドの処理 (SPN構造)"
A["入力状態 (128-bit)"] --> B("SubBytes (バイト代換 / S-Box)")
B --> C("ShiftRows (行シフト)")
C --> D("MixColumns (列ミックス / GF(2^8)上の乗算)")
D --> E("AddRoundKey (ラウンド鍵とのXOR)")
E --> F["次のラウンドへ"]
end&lt;/div>
&lt;hr>
&lt;h1 id="5-公開鍵暗号の革命diffie-hellmanからrsaへ">5. 公開鍵暗号の革命：Diffie-HellmanからRSAへ
&lt;/h1>&lt;p>共通鍵暗号には決定的な弱点がありました。それは「鍵配送問題（Key Distribution Problem）」です。暗号通信を始める前に、遠く離れた相手とどうやって安全に「共通の鍵」を共有するのかという問題です。この問題を解決したのが1970年代に誕生した「公開鍵暗号」です。&lt;/p>
&lt;h2 id="diffie-hellman鍵交換">Diffie-Hellman鍵交換
&lt;/h2>&lt;p>1976年、ホイットフィールド・ディフィーとマーティン・ヘルマンは、画期的な論文「New Directions in Cryptography」を発表しました。彼らは「離散対数問題（Discrete Logarithm Problem）」という数学的困難性を利用し、盗聴されている通信経路上でも安全に鍵を共有できる手法を提案しました。&lt;/p>
&lt;ol>
&lt;li>大きな素数 $p$ と生成元 $g$ を公開します。&lt;/li>
&lt;li>Aliceは秘密の値 $a$ を選び、$A = g^a \pmod{p}$ をBobに送信します。&lt;/li>
&lt;li>Bobは秘密の値 $b$ を選び、$B = g^b \pmod{p}$ をAliceに送信します。&lt;/li>
&lt;li>Aliceは $K = B^a \pmod{p}$ を計算し、Bobは $K = A^b \pmod{p}$ を計算します。&lt;/li>
&lt;li>指数法則により $K = (g^b)^a = (g^a)^b = g^{ab} \pmod{p}$ となり、見事に同じ鍵 $K$ を共有できます。&lt;/li>
&lt;/ol>
&lt;h2 id="rsa暗号">RSA暗号
&lt;/h2>&lt;p>翌1977年、ロン・リベスト、アディ・シャミア、レオナルド・エーデルマンの3人によって考案されたのが「RSA暗号」です。これは「巨大な合成数の素因数分解は困難である」という性質に基づいています。&lt;/p>
&lt;p>&lt;strong>RSAの数学的仕組み:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>2つの巨大な素数 $p$ と $q$ を選び、$n = p \times q$ を計算します。&lt;/li>
&lt;li>オイラーのトーティエント関数 $\phi(n) = (p-1)(q-1)$ を計算します。&lt;/li>
&lt;li>$\phi(n)$ と互いに素な整数 $e$（公開鍵）を選びます。&lt;/li>
&lt;li>$e \times d \equiv 1 \pmod{\phi(n)}$ となる整数 $d$（秘密鍵）を計算します。&lt;/li>
&lt;/ol>
&lt;p>暗号化: 平文 $M$ に対して $C \equiv M^e \pmod{n}$
復号: 暗号文 $C$ に対して $M \equiv C^d \pmod{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;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="c1"># RSA暗号の概念を示すPythonコード（実用向けではありません）&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">ext_euclid&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="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">b&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="mi">1&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">a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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">g&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ext_euclid&lt;/span>&lt;span class="p">(&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 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">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="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="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">g&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">rsa_example&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="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">61&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">53&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>&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">17&lt;/span> &lt;span class="c1"># phiと互いに素な値&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">_&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ext_euclid&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">d&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">d&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="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">f&lt;/span>&lt;span class="s2">&amp;#34;公開鍵: (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">, 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">)&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=&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">, 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">)&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">message&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">65&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="n">decrypted&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>&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">message&lt;/span>&lt;span class="si">}&lt;/span>&lt;span class="s2"> -&amp;gt; 暗号文: &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;gt; 復号後: &lt;/span>&lt;span class="si">{&lt;/span>&lt;span class="n">decrypted&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">rsa_example&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;h1 id="6-楕円曲線暗号eccの台頭">6. 楕円曲線暗号（ECC）の台頭
&lt;/h1>&lt;p>RSA暗号は強力ですが、コンピュータの性能向上に伴い、安全性を保つために鍵長を長くする（現在では2048ビットや3072ビット）必要があり、計算コストが増大するという問題が生じました。&lt;/p>
&lt;p>そこで1985年に提案されたのが「楕円曲線暗号（Elliptic Curve Cryptography: ECC）」です。これは有限体上の楕円曲線（一般に $y^2 = x^3 + ax + b$ の形）における点の加算を利用したものです。&lt;/p>
&lt;p>楕円曲線上の離散対数問題（ECDLP）は、素因数分解問題よりもさらに解くのが困難であることが知られており、&lt;strong>RSAの3072ビットと同等の安全性を、ECCならわずか256ビットの鍵長で実現&lt;/strong>できます。これにより、スマートフォンやIoTデバイスなど、計算リソースが限られた環境でも高速かつ安全な暗号通信（ECDSAやECDHなど）が可能になりました。&lt;/p>
&lt;hr>
&lt;h1 id="7-量子コンピュータの脅威と耐量子暗号pqc">7. 量子コンピュータの脅威と耐量子暗号（PQC）
&lt;/h1>&lt;p>暗号技術は盤石に思えましたが、1994年にピーター・ショア（Peter Shor）が発表した「ショアのアルゴリズム」によって大きな衝撃が走ります。&lt;/p>
&lt;p>量子コンピュータは「重ね合わせ」と「量子もつれ」という量子力学の性質を利用して計算を行います。ショアのアルゴリズムを十分な性能の量子コンピュータ上で実行すると、素因数分解問題や離散対数問題が「多項式時間」で解けてしまうことが数学的に証明されたのです。つまり、実用的な量子コンピュータが完成した日（Q-Day）、現在使われているRSAやECCなどの公開鍵暗号はすべて瞬時に破綻します。&lt;/p>
&lt;h2 id="pqcpost-quantum-cryptographyの登場">PQC（Post-Quantum Cryptography）の登場
&lt;/h2>&lt;p>この未曾有の脅威に備え、量子コンピュータでも解読が困難な新しい数学的問題に基づく「耐量子計算機暗号（PQC）」の研究が急ピッチで進められています。NIST（米国国立標準技術研究所）は長年にわたりPQCの標準化プロセスを進めており、主に以下の数学的アプローチが有力視されています。&lt;/p>
&lt;h3 id="1-格子暗号lattice-based-cryptography">1. 格子暗号（Lattice-based Cryptography）
&lt;/h3>&lt;p>現在最も有力なアプローチであり、NISTの標準化アルゴリズム（ML-KEM / Kyber、ML-DSA / Dilithium）にも採用されています。多次元空間の「格子（Lattice）」上で特定の点を見つける問題（最短ベクトル問題: SVPなど）や、LWE（Learning With Errors: 誤差付き学習）問題の困難性に基づいています。&lt;/p>
&lt;p>LWE問題の概念は、連立一次方程式に意図的に「小さなノイズ（誤差）」を加えると、途端に解を求めるのが困難になるという性質を利用しています。
方程式系: $\mathbf{A}\mathbf{s} + \mathbf{e} \equiv \mathbf{b} \pmod{q}$
（$\mathbf{A}$ と $\mathbf{b}$ は公開、$\mathbf{s}$ は秘密鍵、$\mathbf{e}$ は微小なノイズ）&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;/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="c1"># LWE問題の概念的疑似コード（学習目的）&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">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>&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">256&lt;/span> &lt;span class="c1"># 次元&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="mi">3329&lt;/span> &lt;span class="c1"># 法&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">m&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">512&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"># 秘密鍵 s と小さなエラー e&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="n">np&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">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">size&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="n">e&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">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="o">-&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 class="n">size&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 公開行列 A と公開ベクトル b&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">np&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">0&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">size&lt;/span>&lt;span class="o">=&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">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">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dot&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">s&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">e&lt;/span>&lt;span class="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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 量子コンピュータを使っても、Aとbからsを復元するのは非常に困難とされる&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="2-ハッシュベース暗号hash-based-cryptography">2. ハッシュベース暗号（Hash-based Cryptography）
&lt;/h3>&lt;p>ハッシュ関数の衝突耐性のみに安全性の根拠を置くデジタル署名方式です。数学的な構造を持たないため量子攻撃に強いですが、署名サイズが大きくなる傾向があります（SPHINCS+など）。&lt;/p>
&lt;h3 id="3-符号ベース暗号code-based-cryptography">3. 符号ベース暗号（Code-based Cryptography）
&lt;/h3>&lt;p>誤り訂正符号の理論に基づく暗号方式です。1978年に提案されたMcEliece暗号などが有名で、歴史が古く安全性に定評がありますが、公開鍵のサイズが非常に大きい（数メガバイトに及ぶこともある）という課題があります。&lt;/p>
&lt;div class="mermaid">timeline
title "暗号技術と計算機の進化の歴史"
"古代〜中世" : "シーザー暗号" : "ヴィジュネル暗号" : "頻度分析の誕生"
"1930〜40年代" : "エニグマの運用と解読" : "チューリングマシン・ボンブの開発"
"1970年代" : "DES標準化 (1977)" : "Diffie-Hellman鍵交換 (1976)" : "RSA暗号の誕生 (1977)"
"1980〜90年代" : "楕円曲線暗号 (ECC) の提案" : "ショアのアルゴリズム発表 (1994)"
"2000年代" : "AES標準化 (2001)"
"2010年代〜現在" : "量子コンピュータの研究加速" : "NISTによるPQC標準化プロジェクトの開始"
"近未来 (Q-Day)" : "大規模量子コンピュータの実現？" : "PQC（ML-KEM/ML-DSA）への完全移行"&lt;/div>
&lt;hr>
&lt;h1 id="8-結論終わりのない盾と矛の戦い">8. 結論：終わりのない盾と矛の戦い
&lt;/h1>&lt;p>暗号技術の歴史は、新しい暗号方式（盾）の発明と、それを破る新しい解読手法（矛）の終わりのない戦いの歴史です。&lt;/p>
&lt;p>シーザー暗号は頻度分析に敗れ、無敵を誇ったエニグマはチューリングの天才的頭脳と機械の力に敗れました。そして今、現代インターネット社会の根幹を支えるRSAやECCといった強力な暗号も、量子コンピュータという新たな「矛」の前に脅威に晒されています。&lt;/p>
&lt;p>しかし、人類は既にその先の未来を見据え、耐量子暗号（PQC）という新たな「盾」を準備しつつあります。現在、世界中のITインフラにおいて、既存の公開鍵暗号からPQCへの移行準備（Crypto Agilityの確保）が急務となっています。&lt;/p>
&lt;p>暗号技術は、単なる難解な数学のパズルではなく、私たちのプライバシー、財産、そして社会インフラそのものを守るための最強の防壁なのです。&lt;/p></description></item><item><title>【数学解説】RSA暗号の仕組みを高校生でもわかるように説明する</title><link>http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/</link><pubDate>Fri, 11 Sep 2026 13:00:00 +0900</pubDate><guid>http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/</guid><description>&lt;img src="http://kenji.blog/p/rsa-encryption-math-explained-for-beginners/img/eyecatch.jpg" alt="Featured image of post 【数学解説】RSA暗号の仕組みを高校生でもわかるように説明する" />&lt;p>インターネット社会の安全を根底から支えている技術の一つが「RSA暗号」です。オンラインショッピングでのクレジットカード決済、友人とのSNSのやり取り、会社の機密情報の送受信など、私たちが毎日何気なく使っている通信の多くは、このRSA暗号やその後継技術によって守られています。&lt;/p>
&lt;p>しかし、「暗号」と聞くと、スパイ映画に出てくるような複雑な暗号機や、一部の天才しか理解できない超高度な数学を想像するかもしれません。確かに現代の暗号理論は高度な数学に基づいていますが、&lt;strong>RSA暗号の根本的な仕組みは、高校で習う数学（整数の性質、素数、合同式など）の知識があれば十分に理解できる&lt;/strong>ものです。&lt;/p>
&lt;p>この記事では、高校数学の知識を出発点として、RSA暗号がどのような数学的原理で動いているのか、なぜ解読が難しいのかを、ステップバイステップで徹底的に解説します。数学が少し苦手な方でも理解できるように、具体例を交えながら丁寧に説明していきます。&lt;/p>
&lt;hr>
&lt;h2 id="1-共通鍵暗号と公開鍵暗号">1. 共通鍵暗号と公開鍵暗号
&lt;/h2>&lt;p>RSA暗号の数学的な仕組みに入る前に、まず暗号の基本的な考え方について整理しておきましょう。暗号方式は大きく分けて「共通鍵暗号」と「公開鍵暗号」の2種類があります。&lt;/p>
&lt;h3 id="11-共通鍵暗号方式の限界">1.1 共通鍵暗号方式の限界
&lt;/h3>&lt;p>昔から使われている暗号の多くは「共通鍵暗号方式」と呼ばれるものです。これは、&lt;strong>「暗号化（メッセージを秘密の暗号文に変換すること）」と「復号（暗号文を元のメッセージに戻すこと）」に同じ鍵を使う&lt;/strong>方式です。&lt;/p>
&lt;p>例えば、アリスがボブに秘密の手紙を送るとします。アリスは南京錠（共通鍵）を使って箱に手紙を入れて鍵をかけます。ボブがその箱を開けるためには、アリスが使ったのと同じ鍵を持っている必要があります。&lt;/p>
&lt;p>この方式には大きな問題があります。「鍵の配送問題」です。遠く離れたアリスとボブが初めて通信する場合、どのようにして盗聴されることなく鍵を共有すればよいのでしょうか？もし鍵を郵送している途中で第三者に盗まれたら、その後の暗号通信はすべて筒抜けになってしまいます。&lt;/p>
&lt;h3 id="12-画期的な発明公開鍵暗号方式">1.2 画期的な発明「公開鍵暗号方式」
&lt;/h3>&lt;p>この鍵の配送問題を解決するために考案されたのが「公開鍵暗号方式」です。RSA暗号もこの一種です。&lt;/p>
&lt;p>公開鍵暗号方式では、&lt;strong>「暗号化するための鍵（公開鍵）」と「復号するための鍵（秘密鍵）」という2つの異なる鍵&lt;/strong>を使います。&lt;/p>
&lt;ol>
&lt;li>受信者のボブは、「公開鍵」と「秘密鍵」のペアを作成します。&lt;/li>
&lt;li>ボブは「公開鍵」を世界中に公開します（誰が手に入れても構いません）。&lt;/li>
&lt;li>送信者のアリスは、ボブの「公開鍵」を使ってメッセージを暗号化し、送信します。&lt;/li>
&lt;li>暗号化されたメッセージは、ボブだけが持っている「秘密鍵」でしか復号できません。&lt;/li>
&lt;/ol>
&lt;p>これを南京錠で例えると、ボブは「開いた状態の南京錠（公開鍵）」をたくさん作って世界中にばらまきます。アリスはボブ宛のメッセージを箱に入れ、拾ったボブの南京錠でカチャッと鍵をかけます。南京錠は一度閉まると、ボブが持っている「マスターキー（秘密鍵）」でしか開けられません。途中で誰かが箱を盗んでも、マスターキーがないので開けられないのです。&lt;/p>
&lt;div class="mermaid">graph TD
A["アリス (送信者)"] --> B["平文 (メッセージ)"]
B --> C["暗号化処理"]
D["ボブの公開鍵 (誰でも入手可能)"] --> C
C --> E["インターネット経由で送信: 暗号文"]
E --> F["復号処理"]
G["ボブの秘密鍵 (ボブだけが持つ)"] --> F
F --> H["復元された平文 (メッセージ)"]
H --> I["ボブ (受信者)"]&lt;/div>
&lt;p>この画期的なシステムを実現するためには、「公開鍵で簡単に暗号化できるが、秘密鍵がないと絶対に復号できない」という、ある種の**「一方向性関数（一方通行の数学的パズル）」**が必要になります。そのパズルの部品として目をつけられたのが、私たちがよく知る「素数」でした。&lt;/p>
&lt;hr>
&lt;h2 id="2-rsa暗号を支える数学的基礎1素数と素因数分解">2. RSA暗号を支える数学的基礎1：素数と素因数分解
&lt;/h2>&lt;p>RSA暗号の安全性は、**「巨大な数の素因数分解は非常に難しい」**という数学的事実に基づいています。&lt;/p>
&lt;h3 id="21-素数とは">2.1 素数とは
&lt;/h3>&lt;p>素数とは、「1と自分自身でしか割り切れない、1より大きい自然数」のことです。
例：$2, 3, 5, 7, 11, 13, 17, 19, 23...$&lt;/p>
&lt;p>素数は、すべての整数の「原子」のようなものです。どんな自然数も、素数の掛け算の形に分解することができます。これを&lt;strong>素因数分解&lt;/strong>と呼びます。例えば、$60 = 2^2 \times 3 \times 5$ のように、順番を無視すればただ一通りに素因数分解できることは「算術の基本定理」として知られています。&lt;/p>
&lt;h3 id="22-素因数分解の難しさ一方向性関数">2.2 素因数分解の難しさ（一方向性関数）
&lt;/h3>&lt;p>ここで重要なのは、**「掛け算は簡単だが、素因数分解は難しい」**という非対称性です。&lt;/p>
&lt;p>例えば、次の2つの素数の掛け算を暗算で計算してみてください。
$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>2つの巨大な素数 $p$ と $q$ が与えられたとき、$N$ を計算するのはコンピュータにとって一瞬（ミリ秒以下）です。しかし、逆に $N$ だけを与えられて元の $p$ と $q$ を見つけ出すことは、現在の最速のスーパーコンピュータを何兆年回しても解けないほど時間がかかります。&lt;/p>
&lt;p>この**「計算の非対称性（片道は簡単、逆道は困難）」**が、公開鍵と秘密鍵の関係性を作り出す土台になります。&lt;/p>
&lt;hr>
&lt;h2 id="3-rsa暗号を支える数学的基礎2合同式モジュロ演算">3. RSA暗号を支える数学的基礎2：合同式（モジュロ演算）
&lt;/h2>&lt;p>RSA暗号の計算は、私たちが普段使っているような無限に数が大きくなる足し算や掛け算ではなく、ある数で割った「余り」の世界で行われます。これを**合同式（モジュロ演算）**と呼びます。&lt;/p>
&lt;h3 id="31-時計の数学">3.1 時計の数学
&lt;/h3>&lt;p>モジュロ演算はよく「時計の数学」に例えられます。現在10時だとして、そこから5時間後は何時でしょうか？ $10 + 5 = 15$ 時ですが、通常の12時間時計では「3時」と答えます。これは、15を12で割った余りが3だからです。&lt;/p>
&lt;p>数学の世界では、これを次のように書き表します。
&lt;/p>
$$ 15 \equiv 3 \pmod{12} $$
&lt;p>
「15と3は、12を法として合同である（12で割った余りが等しい）」と読みます。&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>特に重要なのは「べき乗」の性質です。これは**「余りの累乗は、累乗の余りに等しい」**ことを意味します。
例えば、$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暗号の核心となる魔法の数学です。「フェルマーの小定理」の一般化である「オイラーの定理」が登場します。&lt;/p>
&lt;h3 id="41-オイラーのトーティエント関数-phin">4.1 オイラーのトーティエント関数 $\phi(N)$
&lt;/h3>&lt;p>オイラーのトーティエント関数（$\phi$ 関数）は、ある自然数 $N$ に対して、**「1から $N$ までの自然数のうち、$N$ と互いに素（最大公約数が1）である数の個数」**を返す関数です。&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>
2つの異なる素数 $p$ と $q$ について、$N = p \times q$ とした場合、$\phi(N)$ は以下の計算で簡単に求めることができます。
&lt;/p>
$$ \phi(N) = \phi(p) \times \phi(q) = (p - 1)(q - 1) $$
&lt;p>
この性質が、RSA暗号の「秘密の裏口（トラップドア）」として機能します。$p$ と $q$ を知っている人（鍵の作成者）は $\phi(N)$ を一瞬で計算できますが、$N$ しか知らない第三者は、$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>これは、「ある数 $a$ を $\phi(N)$ 回掛け合わせて $N$ で割ると、余りが必ず $1$ になる」という驚くべき性質です。（$N$ が素数 $p$ の場合は $a^{p-1} \equiv 1 \pmod p$ となり、フェルマーの小定理と呼ばれます）。&lt;/p>
&lt;p>このオイラーの定理を変形してみましょう。両辺に $a$ をもう一回掛けます。
&lt;/p>
$$ a^{\phi(N) + 1} \equiv a \pmod N $$
&lt;p>さらに、任意の整数 $k$ に対して、$a^{k \cdot \phi(N)}$ も $1^k = 1$ になるため、次の式が成り立ちます。
&lt;/p>
$$ a^{k \cdot \phi(N) + 1} \equiv a \pmod N $$
&lt;p>この式こそが、RSA暗号の**「暗号化して復号すると元に戻る」**という魔法を成り立たせている根本原理なのです。&lt;/p>
&lt;hr>
&lt;h2 id="5-rsa暗号のアルゴリズム鍵生成暗号化復号のステップ">5. RSA暗号のアルゴリズム：鍵生成・暗号化・復号のステップ
&lt;/h2>&lt;p>基礎知識が揃ったところで、いよいよRSA暗号の具体的な手順を見ていきましょう。RSA暗号は大きく「1. 鍵の生成」「2. 暗号化」「3. 復号」の3つのフェーズに分かれます。&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>受信者であるボブは、自分のための「公開鍵」と「秘密鍵」を生成します。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>素数の選択:&lt;/strong> 2つの大きな素数 $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)} $$
これはつまり、「$e \times d$ を $\phi(N)$ で割った余りが $1$ になるような数 $d$」です。&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>「メッセージ $M$ を $e$ 乗して、$N$ で割った余り $C$」を計算します。この $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>「暗号文 $C$ を $d$ 乗して、$N$ で割った余り」を計算すると、なんと元のメッセージ $M$ が復元されるのです！&lt;/p>
&lt;hr>
&lt;h2 id="6-なぜ復号で元に戻るのか数学的証明">6. なぜ復号で元に戻るのか？（数学的証明）
&lt;/h2>&lt;p>「$C$ を $d$ 乗するだけで、どうして元の $M$ に戻るの？」と疑問に思うかもしれません。ここで、先ほどの「オイラーの定理」が威力を発揮します。&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)}$ となるように選びました。これは「$ed$ は $\phi(N)$ の倍数に $1$ を足した数である」ということを意味します。整数 $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>2つの素数 $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> を実行すると、見事に結果は「&lt;strong>9&lt;/strong>」となります！元のメッセージを無事に受け取ることができました。&lt;/p>
&lt;hr>
&lt;h2 id="8-秘密鍵-d-の求め方拡張ユークリッドの互除法">8. 秘密鍵 $d$ の求め方：拡張ユークリッドの互除法
&lt;/h2>&lt;p>手計算の例では勘で $k$ を探して $d=103$ を見つけましたが、数が何百桁にもなるとこの方法は不可能です。実際のプログラムでは**「拡張ユークリッドの互除法」**というアルゴリズムを使います。&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暗号の素晴らしい点は、公開鍵と秘密鍵の役割を逆にすることで**「デジタル署名」**としても使えることです。&lt;/p>
&lt;p>暗号化のときは「公開鍵で暗号化 $\Rightarrow$ 秘密鍵で復号」でしたが、
デジタル署名では「秘密鍵で暗号化 $\Rightarrow$ 公開鍵で復号」という手順を踏みます。&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$ を使って検証計算をします。もし計算結果が元のメッセージと一致すれば、「アリスの秘密鍵でしか作れないデータである」ことと、「メッセージが途中で改ざんされていないこと」が同時に証明されるのです。&lt;/p>
&lt;hr>
&lt;h2 id="10-プログラムで実感するrsa暗号">10. プログラムで実感するRSA暗号
&lt;/h2>&lt;p>手計算では大変な累乗計算も、Pythonを使えば非常に簡単に実装できます。以下はRSA暗号のコアとなるロジックを体験できる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;/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>Pythonの &lt;code>pow(base, exp, mod)&lt;/code> 関数は内部で「繰り返し二乗法」という高速アルゴリズムを使っているため、何百桁の数であっても一瞬で計算が終わります。&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$ という法則により、「ある数で累乗すると元に戻る」魔法のトラップドアが完成する。&lt;/li>
&lt;li>&lt;strong>公開鍵と秘密鍵:&lt;/strong> 誰でも暗号化できるが、復号できるのは正当な受信者だけ。&lt;/li>
&lt;/ol>
&lt;p>現在使われているRSA暗号の $N$ は600桁以上あり、世界中のスーパーコンピュータを総動員しても素因数分解には宇宙の年齢以上の時間がかかります。しかし、近年研究が進んでいる「量子コンピュータ」が将来実用化されると、「ショアのアルゴリズム」によってこの素因数分解が一瞬で解かれてしまう可能性があります。そのため、現在は量子コンピュータでも解読できない「耐量子計算機暗号」の開発が世界中で急ピッチで進められています。&lt;/p>
&lt;p>「役に立たない」と思われがちな高度な数学が、実は私たちの日常生活を根底から守っている。RSA暗号は、そんな数学の奥深さと美しさを教えてくれる最高の教材です。この記事を通して、暗号と数学の面白さを少しでも感じていただけたなら幸いです。&lt;/p></description></item><item><title>完全準同型暗号（FHE）とは？次世代セキュリティの要を解説</title><link>http://kenji.blog/p/fully-homomorphic-encryption-fhe-explained/</link><pubDate>Fri, 11 Sep 2026 11:00:00 +0900</pubDate><guid>http://kenji.blog/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技術が社会の基盤として定着する中、「データのプライバシー」と「データの利活用」のトレードオフは、最も重要な課題の一つとなっています。医療データ、金融情報、個人の生体情報など、機密性の高いデータをクラウド上でAIに解析させたいという需要は高まっていますが、セキュリティ上の懸念からデータの外部送信を躊躇する企業は少なくありません。&lt;/p>
&lt;p>従来の暗号化技術（AESやRSAなど）は、ストレージに保存されているデータ（Data at Rest）やネットワーク上を流れるデータ（Data in Transit）を保護することには長けています。しかし、サーバー側でデータに対して検索や機械学習などの&lt;strong>処理（計算）を行う際（Data in Use）には、一度暗号を復号して平文に戻す必要があります&lt;/strong>。もしこの復号されたタイミングでサーバーがハッキングされたり、内部の悪意ある管理者がデータを覗き見たりすれば、情報漏洩に直結します。&lt;/p>
&lt;p>この「処理時の復号」という根本的な弱点を克服する夢の技術が、**完全準同型暗号（Fully Homomorphic Encryption: FHE）**です。FHEを利用すれば、データを暗号化したまま一切復号せずに計算処理を行い、その結果の暗号文だけをクライアントに返すことが可能になります。&lt;/p>
&lt;p>本記事では、このFHEの概念から歴史、Craig Gentryによる画期的なブレイクスルー、数学的な基礎（Ring-LWEなど）、最大の課題である「ノイズ」とその解決策（ブートストラッピング）、そして最新の実装ライブラリに至るまで、次世代セキュリティの要であるFHEを徹底的に深く解説します。&lt;/p>
&lt;hr>
&lt;h2 id="1-準同型暗号とは何か基本的な概念">1. 準同型暗号とは何か？基本的な概念
&lt;/h2>&lt;p>「準同型（Homomorphic）」とは代数学の用語であり、ある構造を持つ集合間で、演算の構造を保ったまま写像できる性質を指します。暗号理論における「準同型性」とは、&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>準同型暗号は、一度に現在の「完全」な形になったわけではありません。実現できる演算の種類や回数によって、大きく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>がある方式です。後述する「ノイズ」の蓄積により、ある一定回数以上の乗算を行うと復号が不可能になってしまいます。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は長らく「暗号界の聖杯」と呼ばれ、実現不可能ではないかとも言われていました。しかし、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）としても知られる「格子暗号（Lattice-based Cryptography）」の数学的難問である &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>しかし、この方程式の結果に、ごく僅かな「ランダムな誤差（ノイズ）」$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困難な問題へと変貌します。これがLWE問題です。&lt;/p>
&lt;h3 id="ring-lwe問題rlwe">Ring-LWE問題（RLWE）
&lt;/h3>&lt;p>標準のLWE問題は行列演算を含むため、鍵のサイズが非常に大きく（ギガバイト単位になることも）、計算効率も悪いという問題がありました。これを解決するために導入されたのが、多項式環上の演算を用いる &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. 最大の障壁「ノイズ」とGentryのブートストラッピング
&lt;/h2>&lt;p>FHEの理解において最も重要な概念が**「ノイズの管理」**です。&lt;/p>
&lt;p>LWE/RLWEベースの暗号では、安全性を担保するために意図的に小さな「ノイズ（誤差）」を含めています。
平文 $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の天才的な貢献は、**「ブートストラッピング」**と呼ばれるノイズ削減の手法を発明したことです。これは暗号学におけるパラダイムシフトでした。&lt;/p>
&lt;p>直感的には「暗号文がノイズまみれになって壊れる前に、暗号化されたままの状態で『復号』して綺麗にし、新しい暗号文に入れ直す」という操作です。&lt;/p>
&lt;ol>
&lt;li>ノイズが大きくなった暗号文 $C_{noisy}$ があるとします。&lt;/li>
&lt;li>クライアントは、秘密鍵 $sk$ を「公開鍵で暗号化したもの」$E_{pk}(sk)$（これをブートストラッピングキーと呼ぶ）をあらかじめサーバーに渡しておきます。&lt;/li>
&lt;li>サーバーは、$C_{noisy}$ に対して、準同型的に**復号回路（Decryption Circuit）**を実行します。&lt;/li>
&lt;li>具体的には、$E_{pk}(C_{noisy})$ に対して $E_{pk}(sk)$ を用いて「暗号化された空間内での復号」を行います。&lt;/li>
&lt;li>この復号回路自体も準同型演算であるため新たなノイズを生みますが、出力される新しい暗号文 $C_{fresh}$ のノイズは、一定の「固定レベル」にリセットされます。&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）技術をサポートしており、1つの巨大な多項式の暗号文の中に、何千ものデータスロットを詰め込んで一度に並列計算できるのが特徴です。&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が「正確な整数計算」にこだわっていたのに対し、CKKSは**「浮動小数点数の近似計算」**を暗号化されたままサポートします。ニューラルネットワークの学習や推論など、小さな誤差が許容される実数計算において、圧倒的なパフォーマンスを発揮します。&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は理論的に完璧なセキュリティを提供しますが、実用化における最大の課題は**「パフォーマンスのオーバーヘッド」**です。&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倍以内）に引き上げるための専用ハードウェア開発プロジェクト「DPRIVE (Data Protection in Virtual Environments)」を推進しており、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）のように、サーバーやクラウドインフラに「FPU」が標準搭載される時代が来るかもしれません。&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プロバイダーはユーザーの入力を一切知ることなく回答を生成し、暗号文として返します。これにより、「AIに個人情報を学習される・盗み見られる」という懸念が完全に払拭されます。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="9-結論暗号の未来は見えない計算へ">9. 結論：暗号の未来は「見えない計算」へ
&lt;/h2>&lt;p>1970年代に公開鍵暗号（RSA）が発明されてインターネット上の安全な通信（HTTPSなど）が可能になったように、Craig GentryによるFHEの発明は、暗号の歴史における最も重要なマイルストーンの一つです。&lt;/p>
&lt;p>現在、完全準同型暗号（FHE）は研究室の理論から飛び出し、Microsoft、IBM、Intel、Google、そして多くのスタートアップが実用化に向けてしのぎを削る段階に入っています。計算コストやデータサイズの課題は依然として存在しますが、アルゴリズムの洗練とハードウェアアクセラレータの進化により、ムーアの法則を超えるペースで性能向上が続いています。&lt;/p>
&lt;p>数年後、「データを暗号化したまま計算する」ことは特別なことではなく、クラウドサービスにおける標準的なデータ保護のベストプラクティスとなるでしょう。FHEは、データ駆動型社会における&lt;strong>究極のプライバシーとデータ利活用の両立&lt;/strong>を実現する、次世代セキュリティの要なのです。&lt;/p></description></item><item><title>楕円曲線暗号（ECC）の数学的基礎とC++での実装</title><link>http://kenji.blog/p/elliptic-curve-cryptography-math-cpp/</link><pubDate>Fri, 11 Sep 2026 10:00:00 +0900</pubDate><guid>http://kenji.blog/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）から、スマートフォンのセキュアエンクレーブ、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暗号は「巨大な合成数の素因数分解の困難性」を安全性の根拠としています。しかし、コンピューターの計算能力の向上に伴い、安全性を維持するためにはRSAの鍵長（モジュラスのビット数）を継続的に長くする必要が生じました。現在では、最低でも2048ビット、より安全を期すなら3072ビットや4096ビットの鍵長が推奨されています。&lt;/p>
&lt;p>これに対して、楕円曲線暗号（ECC）は**「楕円曲線上の離散対数問題（ECDLP）」**という別の数学的困難性を安全性の根拠としています。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>暗号理論においては、実数や複素数のような連続的で無限の要素を持つ体ではなく、要素の数が有限である**有限体（Finite Field）**またはガロア体（Galois Field）を使用します。&lt;/p>
&lt;p>最も基本的な有限体は、素数 $p$ を用いた&lt;strong>素体 $\mathbb{F}_p$&lt;/strong> です。これは、$\{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}$ を求めるには、主に以下の2つのアルゴリズムが用いられます。&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$ 乗を計算することで逆元が求まります。べき乗演算は定数時間で実装しやすいため、暗号実装ではこちらが好まれます。&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）&lt;strong>は、一般に以下の&lt;/strong>ワイエルシュトラスの標準形（Weierstrass normal form）**と呼ばれる方程式で定義される平面曲線です。&lt;/p>
$$ y^2 = x^3 + ax + b $$
&lt;p>ここで、$a$ と $b$ は定数であり、曲線が特異点（自己交差や尖点）を持たない（滑らかな曲線である）ための条件として、以下の&lt;strong>判別式（Discriminant） $\Delta$&lt;/strong> がゼロでないことが求められます。&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>楕円曲線を数学的に完全な群にするために、平面上の点に加えて**「無限遠点（Point at Infinity）」**と呼ばれる仮想的な点を導入します。これを $\mathcal{O}$ （オー）と表記します。&lt;/p>
&lt;p>無限遠点 $\mathcal{O}$ は、すべての垂直線が無限の彼方で交わる点として定義されます。群論において、この無限遠点 $\mathcal{O}$ は&lt;strong>加法における単位元&lt;/strong>（ゼロ）として機能します。&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>楕円曲線暗号の根幹をなすのが、曲線上の点同士の**「加算（Addition）」**という演算です。これは通常の整数の足し算とは異なり、幾何学的な操作に基づいて定義されています。&lt;/p>
&lt;h3 id="41-幾何学的な加算tangent-and-chord-method">4.1. 幾何学的な加算（Tangent and Chord Method）
&lt;/h3>&lt;p>曲線上の異なる2点 $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$）、2点を通る直線を引くことができません。この場合は、**点 $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$ は、2点間の変化の割合です。
&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>が含まれています。モジュロ逆元の計算は計算コストが非常に高いため、実際の実装では除算を遅延させる&lt;/strong>「ヤコビ座標系（Jacobian Coordinates）」**などの射影座標系が一般的に用いられます。&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$ が「秘密鍵」、$Q$ が「公開鍵」**に対応します。&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>これは、整数のべき乗を高速に計算する「繰り返し二乗法」の楕円曲線版です。スカラー $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>理論的に完璧な暗号アルゴリズムであっても、それをプログラムとして実装する過程で脆弱性が生まれ得ます。それが**「サイドチャネル攻撃（Side-Channel Attack）」**です。&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>タイミング攻撃を防ぐためには、**秘密鍵のビット値にかかわらず、実行される命令のシーケンスと計算時間が常に一定（Constant-Time）**であるアルゴリズムを採用する必要があります。&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> であっても、**「常に1回のPoint Additionと1回のPoint Doubling」**が実行されることです。これにより、計算時間のデータ依存性が完全に排除されます。&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>アフィン座標系を用いたわかりやすい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）は、初見では「なぜ直線を引いて交点を折り返すという幾何学的な操作が暗号になるのか？」と不思議に思えるかもしれません。しかし、有限体という離散的な世界にマッピングすることで、見事な一方向性関数（離散対数問題）を構築できるという、数学と暗号学の奇跡的な融合の産物です。&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>次回の記事では、この楕円曲線を用いたデジタル署名アルゴリズムである &lt;strong>ECDSA (Elliptic Curve Digital Signature Algorithm)&lt;/strong> のメカニズムや、ビットコインで採用されている &lt;strong>Schnorr署名&lt;/strong> についてさらに深く掘り下げていきたいと思います。&lt;/p></description></item><item><title>GNFS（一般数体篩法）を超えるアルゴリズムは存在するのか？</title><link>http://kenji.blog/p/beyond-gnfs-integer-factorization-algorithms/</link><pubDate>Fri, 11 Sep 2026 09:00:00 +0900</pubDate><guid>http://kenji.blog/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暗号の安全性は「巨大な合成数の素因数分解の困難さ」という数学的仮定に基づいています。もし、極めて効率的な素因数分解アルゴリズムが発見されれば、世界中の通信インフラは根底から崩れ去ることになります。&lt;/p>
&lt;p>現在、古典コンピュータを用いた巨大整数の素因数分解において、最も高速かつ最強のアルゴリズムとして君臨しているのが**一般数体篩法（GNFS: General Number Field Sieve）**です。GNFSは、1980年代後半に提唱された特殊数体篩法（SNFS）を拡張する形で誕生し、今日に至るまでRSA-768やRSA-250といった巨大な合成数の素因数分解記録を打ち立ててきました。&lt;/p>
&lt;p>しかし、暗号学者や数学者たちは常に次のような疑問を抱いています。「GNFSを超える古典的アルゴリズムは存在するのか？」「古典コンピュータの限界はどこにあるのか？」そして、「量子コンピュータはどのようにこの状況を打破するのか？」&lt;/p>
&lt;p>本記事では、GNFSの背後にある深淵なる数学的構造を徹底的に解剖し、多項式選択、篩処理、ブロック・ヴィーデマン法による線形代数ステップなどの詳細な技術的分析を行います。さらに、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)$ と有理数体の両方で同時に「滑らかな数（Smooth numbers）」を探索し、準同型写像を通じて合同関係を構築する点にあります。&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$ を持つ2つの既約多項式 $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）のモニック多項式を選びます。最も古典的なアプローチは&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）を最適化し、篩処理で滑らかな数が生成されやすい多項式を探索します。このステップだけでも多大な計算資源が投入されます。&lt;/p>
&lt;h3 id="32-フェーズ2篩処理-sieving-phase">3.2 フェーズ2：篩処理 (Sieving Phase)
&lt;/h3>&lt;p>多項式が決定すると、アルゴリズムの最も計算負荷が高い「篩（Sieving）」フェーズに入ります。ここでは、ペア $(a, b)$ を探索します。このペアは互いに素であり、以下の2つの値が同時に「滑らか（Smooth）」であることを求められます。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>有理側のノルム&lt;/strong>: $F_1(a, b) = b \cdot f_1(a/b) = a - bm$&lt;/li>
&lt;li>&lt;strong>代数側のノルム&lt;/strong>: $F_2(a, b) = b^d \cdot f_2(a/b)$&lt;/li>
&lt;/ol>
&lt;p>「滑らか」とは、指定された上限（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>篩処理で見つかった滑らかな関係式（リレーション）の数は数億、数十億に上ります。しかし、これらは無駄な情報も多く含んでいます。
フィルタリングの目的は、巨大な疎行列（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)$）では到底計算不可能です。そこで、クリロフ部分空間法の一種である反復法が用いられます。歴史的には**ブロック・ランチョス法（Block Lanczos）&lt;strong>が使われてきましたが、現在の分散コンピューティング環境においては、通信オーバーヘッドを劇的に削減できる&lt;/strong>ブロック・ヴィーデマン法（Block Wiedemann Algorithm）**が主流です。&lt;/p>
&lt;p>ブロック・ヴィーデマン法は、行列 $\mathbf{A}$ とベクトル系列から最小多項式を計算し、ベルカンプ・マッシー（Berlekamp-Massey）アルゴリズムを用いて零空間の基底を構成します。このステップは並列化が非常に困難であり、スーパーコンピュータや大規模クラスターの密結合な通信ネットワークを要求する、GNFS最大のボトルネックの1つです。&lt;/p>
&lt;h3 id="35-フェーズ5平方根-square-root-phase">3.5 フェーズ5：平方根 (Square Root Phase)
&lt;/h3>&lt;p>線形代数の解から、有理側と代数側のそれぞれで「完全平方」となる積が構築されます。
有理側では $\prod (a-bm)$ がある整数 $X$ の平方 $X^2$ となり、代数側では対応するイデアルの積が代数体上で完全平方 $\gamma^2$ となります。
この $\gamma$ を代数体上で計算し、有理整数環への準同型写像 $\phi: \alpha \mapsto m \pmod n$ を適用することで、合同式：
$X^2 \equiv \phi(\gamma)^2 \equiv Y^2 \pmod n$
が得られます。&lt;/p>
&lt;p>代数体上の平方根の計算には、**Montgomeryの手法（Montgomery&amp;rsquo;s Method）**などの複雑なアルゴリズムが用いられ、代数的整数論の深い知識が要求されます。最終的に $\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>複数の代数体を利用することで、各篩のステップにおいて「いずれかの代数体で滑らかになる」確率を飛躍的に高めることができます。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>素因数分解の古典的アルゴリズムの限界について、数学者たちの間で長年議論されてきたテーマが「指数 $\alpha = 1/4$ のアルゴリズムは存在するのか？」という問いです。
現在のGNFSやその派生は、篩による「滑らかさの探索」という枠組みに強く縛られており、このパラダイムの中では $\alpha = 1/3$ が限界であると広く信じられています。ディクマン関数（Dickman function）を用いた滑らかな整数の分布確率の解析からも、現在の代数体構築法と篩の組み合わせでは、どう最適化しても $O(L_n[1/3])$ の壁を越えることはできないと考えられています。&lt;/p>
&lt;p>もし $L_n[1/4]$ や、あるいは古典的な多項式時間アルゴリズムが存在するとすれば、それはGNFSのような「滑らかさベース」のアプローチとは全く異なる、現在人類が思いもよらない新しい数学的構造（例えば、楕円曲線暗号に対する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>ショアのアルゴリズムは、素因数分解問題を「位数発見問題（Order Finding Problem）」に帰着させます。ある整数 $a$ に対して、関数 $f(x) = a^x \pmod n$ の周期（位数） $r$ を見つける問題です。
古典コンピュータではこの周期を見つけるために指数関数的な時間を要しますが、量子コンピュータ上の**量子位相推定（QPE: Quantum Phase Estimation）&lt;strong>と&lt;/strong>量子フーリエ変換（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>これら2つの計算量クラスの差は、現実世界の暗号セキュリティにおいて決定的な意味を持ちます。&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>「指数 $\alpha=1/3$」の準指数関数から「多項式時間」へのパラダイムシフトは、鍵長を伸ばすことで安全性を担保するという従来の暗号学の戦略を無力化します。&lt;/p>
&lt;hr>
&lt;h2 id="6-まとめ次世代への展望">6. まとめ：次世代への展望
&lt;/h2>&lt;p>「GNFSを超える古典アルゴリズムは存在するのか？」という問いに対する現在の科学界のコンセンサスは以下の通りです。&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）は、人類が古典数学とアルゴリズム設計の極限まで挑み、到達した「最高到達点」の一つです。GNFSの奥深い数学的構造を理解することは、単に暗号解読の歴史を学ぶだけでなく、計算複雑性理論や代数的整数論の美しさに触れる知的探求の旅でもあります。量子コンピュータが実用化されるその日まで、GNFSは最強の素因数分解アルゴリズムとしての王座を守り続けることでしょう。&lt;/p></description></item><item><title>【図解PQC】耐量子計算機暗号の主要なアルゴリズム比較</title><link>http://kenji.blog/p/post-quantum-cryptography-algorithms-comparison/</link><pubDate>Fri, 11 Sep 2026 07:00:00 +0900</pubDate><guid>http://kenji.blog/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. はじめに：量子コンピュータがもたらす「暗号の危機」
&lt;/h2>&lt;p>現代のインターネット社会において、通信の機密性やデータの完全性を守るために公開鍵暗号技術はインフラとして必要不可欠です。現在広く利用されているRSA暗号や楕円曲線暗号（ECC）は、それぞれ「巨大な合成数の素因数分解の困難性」や「楕円曲線上の離散対数問題の困難性」という数学的な壁に依存して安全性を担保しています。古典的なコンピュータ（スーパーコンピュータを含む現在私たちが使っているコンピュータ）では、これらの数学的問題を解くためには宇宙の年齢よりも長い時間がかかると証明されており、それが安全性の根拠となってきました。&lt;/p>
&lt;p>しかし、この堅牢な前提は&lt;strong>量子コンピュータ&lt;/strong>の理論と実用化の進展によって根底から覆されようとしています。1994年に暗号学者ピーター・ショア（Peter Shor）が発表した「&lt;strong>Shorのアルゴリズム&lt;/strong>」は、十分な性能を持つ誤り耐性汎用量子コンピュータ（CRQC: Cryptographically Relevant Quantum Computer）上で実行することで、素因数分解問題や離散対数問題を「多項式時間」で解読できることを理論的に証明しました。これは、現在使われている公開鍵暗号がすべて無力化されることを意味します。&lt;/p>
&lt;div class="mermaid">graph TD
A["大規模な量子コンピュータ (CRQC)"] -->|実行| B["Shorのアルゴリズム"]
B -->|多項式時間で解読| C["素因数分解問題 (RSA)"]
B -->|多項式時間で解読| D["離散対数問題 (ECC / ECDSA)"]
C --> E["暗号通信の盗聴・データ改ざん・なりすまし"]
D --> E
F["Store Now, Decrypt Later (SNDL)"] --> E&lt;/div>
&lt;p>「量子コンピュータの本格的な完成はまだ数十年先だから問題ない」と考えるのは非常に危険です。なぜなら、**Store Now, Decrypt Later (SNDL：今すぐ保存し、後で復号する)**と呼ばれる攻撃手法がすでに現実の脅威となっているからです。これは、悪意のある国家やハッカー組織が、現在の暗号化された通信データ（TLSのトラフィックなど）を大量にストレージに保存しておき、将来強力な量子コンピュータが利用可能になった瞬間にそれらをすべて復号するという攻撃です。国家機密、インフラ情報、長期間保護すべき医療データなどは、すでにこの脅威に晒されています。&lt;/p>
&lt;p>また、対称鍵暗号（AESなど）やハッシュ関数（SHA-256など）に対しても、1996年に発見された&lt;strong>Groverのアルゴリズム&lt;/strong>が存在します。これにより、総当たり攻撃（ブルートフォース）の計算量が平方根に削減されます。つまり、AES-128のセキュリティレベルは実質的に2の64乗に半減するため、量子時代においてはAES-256やSHA-384といった、より長い鍵やハッシュ長を使用することが推奨されています。&lt;/p>
&lt;p>この未曾有の暗号危機に対抗するために誕生したのが、量子コンピュータを用いても解読が困難な新しい数学的問題に基づいた**耐量子計算機暗号（Post-Quantum Cryptography: PQC）**です。本記事では、米国国立標準技術研究所（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>標準化の対象となったのは以下の2つの主要カテゴリです。&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+に基づくステートレスハッシュベース署名&lt;/li>
&lt;li>&lt;strong>（今後の策定予定）FN-DSA&lt;/strong>: FALCONに基づくデジタル署名&lt;/li>
&lt;/ul>
&lt;p>これらの選定されたアルゴリズムは、依拠する数学的な「困難性問題」がそれぞれ異なっており、ある一つのアルゴリズムに将来致命的な脆弱性が発見された場合でも、システム全体が崩壊しないように多様性（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など）の「衝突耐性」と「一方向性」のみに安全性の根拠を置きます。デジタル署名（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の最終ラウンドの最中にパソコン1台で数日で解読されるという強力な攻撃手法が発見され、多くのアルゴリズムが標準化から脱落しました。&lt;/li>
&lt;li>&lt;strong>同種写像暗号 (Isogeny-based Cryptography)&lt;/strong>:
楕円曲線の同種写像（Isogeny）グラフ上での経路探索問題に基づきます。鍵サイズが非常に小さく、ECCの正当な後継として期待されていましたが、最終候補であった「SIKE」が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>。その安全性の根底にあるのが**LWE問題 (Learning with Errors: 誤差付き学習問題)**です。2005年にOded Regevによって提唱され、この画期的な功績により彼はGödel賞を受賞しました。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}$ を求めるのは簡単です。古典的なアルゴリズムである「ガウスの消去法（掃き出し法）」を使えば、多項式時間で容易に $\vec{s}$ を計算できます。&lt;/p>
&lt;p>しかし、この式に「小さな意図的な誤差（ノイズ）」を加えると、問題の難易度が劇的に跳ね上がります。これが&lt;strong>LWE問題&lt;/strong>です。&lt;/p>
&lt;p>未知の秘密ベクトル $\vec{s} \in \mathbb{Z}_q^n$ と、ランダムに選ばれた行列 $A \in \mathbb{Z}_q^{m \times n}$ を用意します。さらに、正規分布や二項分布などに従って選ばれた「要素の値が十分に小さい」誤差ベクトル $\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>とは、「公開情報 $(A, \vec{b})$ から秘密情報 $\vec{s}$ を求めよ」という問題です。この誤差 $\vec{e}$ が存在することにより、ガウスの消去法などの代数的な解法を行おうとすると、方程式を足し引きする過程で誤差 $\vec{e}$ が雪だるま式に増幅されてしまい、最終的にランダムな値と区別がつかなくなり破綻してしまいます。&lt;/p>
&lt;p>LWE問題の凄さは、「格子上の最悪時計算量問題（Worst-case hardness）」である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標準で一般的に用いられるのは以下のような円分多項式環です。&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$ 個の係数を持つため、データを大幅に圧縮でき、さらに**NTT (Number Theoretic Transform: 数論変換)**という高速フーリエ変換（FFT）の有限体バージョンを用いることで、$O(n \log n)$ の計算量で超高速に多項式の乗算が可能になります。&lt;/p>
&lt;p>しかし、Ring-LWEには「環の特殊な代数構造に起因する未知の脆弱性が存在するかもしれない」という懸念がありました。また、セキュリティレベル（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$ を変えるだけで容易にセキュリティレベルをスケールさせることができる点です。
例えば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のように「クライアントが共通鍵を作ってサーバーの公開鍵で暗号化して送る」という直接的なアプローチではなく、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の内部アルゴリズムの仕組みと藤崎岡本変換">5.2. Kyberの内部アルゴリズムの仕組みと藤崎・岡本変換
&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>括弧内の項は「小さな誤差同士の積や和」であるため、全体としても十分に小さな値（ノイズ）に留まります。したがって、各係数が $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）内にパケット分割なしで収めることが可能であり、ネットワークのレイテンシにほとんど悪影響を与えません。&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問題）に基づくデジタル署名スキームです。設計のベースには「&lt;strong>Fiat-Shamir with Aborts (中途終了を伴うフィアット・シャミア変換)&lt;/strong>」という極めて重要なパラダイムが用いられています。&lt;/p>
&lt;p>Fiat-Shamir変換自体は、対話型のゼロ知識証明プロトコルを非対話型のデジタル署名へと変換するための標準的な手法です。証明者（署名者）がコミットメント $y$ を生成し、$w = Ay$ を計算してハッシュ関数に通すことで、ランダムなチャレンジ $c$ を得て、応答 $z = y + cs$ を計算します。&lt;/p>
&lt;p>しかし、格子暗号においてこれを単純に適用すると、応答 $z$ の分布が秘密鍵 $s$ の値に依存して歪んでしまい、多数の署名を観測した攻撃者に秘密鍵 $s$ の情報が少しずつ漏洩してしまうという致命的な問題（サイドチャネル的な数学的漏洩）がありました。&lt;/p>
&lt;p>Dilithiumの設計チーム（Lyubashevskyら）は、署名の計算結果 $z$ の係数が、あらかじめ設定された安全な閾値の範囲内に収まらなかった場合、その署名プロセス全体を破棄（Abort）して、新しい乱数 $y$ を使って最初から計算をやり直す「&lt;strong>棄却サンプリング (Rejection Sampling)&lt;/strong>」という手法を導入しました。&lt;/p>
&lt;p>これにより、最終的に出力される署名 $z$ は秘密鍵に一切依存しない完全な均一分布となり、情報漏洩を数学的に完全に防ぐことに成功しました。&lt;/p>
&lt;h3 id="62-dilithiumの利点と実装の容易さ">6.2. Dilithiumの利点と実装の容易さ
&lt;/h3>&lt;p>Dilithiumの設計上の大きな利点は、署名生成プロセスにおいて複雑な「ガウス分布からのサンプリング」や「浮動小数点演算」を&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) フレームワークに基づく「&lt;strong>ハッシュ・アンド・サイン (Hash-and-Sign)&lt;/strong>」パラダイムを採用しています。&lt;/p>
&lt;p>Hash-and-Signでは、メッセージのハッシュ値を空間内のターゲット点とし、その点に最も近い格子上の点（最近接ベクトル問題の近似解）を見つけることで署名とします。これには、秘密鍵である「質の良い短い基底」を用いて、離散ガウス分布に従って点をサンプリングする必要があります。&lt;/p>
&lt;p>FALCONは、この重い計算を「&lt;strong>高速フーリエ直交化 (Fast Fourier Orthogonalization: FFO)&lt;/strong>」という手法を用いて劇的に高速化しました。&lt;/p>
&lt;h3 id="72-falconのメリットとデメリット">7.2. FALCONのメリットとデメリット
&lt;/h3>&lt;p>FALCONの圧倒的なメリットは、その**署名サイズと公開鍵サイズが極めて小さい（コンパクトである）**ことです。Dilithium3の署名サイズが約3,309バイトであるのに対し、FALCON-512の署名サイズはわずか約666バイトです。公開鍵も897バイトと非常に小さく、通信帯域が極端に制限される環境や、IoTデバイス、特定のネットワークプロトコルにおいては救世主となります。&lt;/p>
&lt;p>しかし、重大なデメリットが存在します。署名生成時に複雑な**浮動小数点演算（64-bit IEEE 754）**を伴う離散ガウスサンプリングが必須となるため、タイミング漏洩を防ぐ定数時間実装（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>に分類されます。その安全性の根拠は、「用いている暗号学的ハッシュ関数（SHA-2やSHAKE256など）が衝突耐性と一方向性を持つこと」という、ただ1点のみに依存しています。LWEや素因数分解のような、特定の代数構造を持つ数学的問題に依存していないため、将来的にどんな強力な量子アルゴリズムが登場しようとも、ハッシュ関数の出力長を単純に伸ばすだけで対抗できるという、極めて強固な安全性（最も保守的なセキュリティ）を誇ります。&lt;/p>
&lt;h3 id="81-wots-と-fors-によるステートレスアーキテクチャ">8.1. WOTS+ と FORS によるステートレスアーキテクチャ
&lt;/h3>&lt;p>ハッシュベース署名の歴史は古く、1970年代のLamport署名やWinternitzワンタイム署名（WOTS）に遡ります。これらは「1回だけ安全に署名できる」という使い捨ての鍵でした。これを複数回使えるようにするために、マークル木（Merkle Tree）を組み合わせて無数のワンタイム鍵を一つのルートハッシュで管理するXMSS（eXtended Merkle Signature Scheme）やLMSといったアルゴリズムが開発されました。&lt;/p>
&lt;p>しかし、XMSSやLMSには「&lt;strong>ステートフル（状態保持型）&lt;/strong>」であるという重大な欠点がありました。署名するたびに「何番目のワンタイム鍵を使用したか」というインデックス状態を不揮発性メモリに厳密に記録し続ける必要があり、もし仮想マシンのスナップショットの復元などで状態が巻き戻り、同じワンタイム鍵を二度使ってしまうと、秘密鍵が即座に漏洩してシステムが崩壊してしまいます。&lt;/p>
&lt;p>SPHINCS+は、この状態管理の煩わしさを解決した「&lt;strong>ステートレス（状態非保持型）&lt;/strong>」のハッシュベース署名です。
その中核技術は以下の組み合わせです。&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鍵の中から、擬似乱数を用いてランダムに一つを選び出して署名します。木の葉の数が天文学的に多いため、同じ鍵を偶然二度選んでしまう確率（コリジョン）が無視できるほど小さくなり、結果としてステートレスを実現しています。&lt;/p>
&lt;p>SPHINCS+の唯一にして最大の弱点は、&lt;strong>署名サイズが極めて大きい&lt;/strong>ことです。パラメータにもよりますが、署名サイズは17キロバイト〜49キロバイトにも達し、署名生成速度も格子暗号に比べて圧倒的に遅くなります。そのため、日常的なWebブラウジングでの利用よりも、ソフトウェアのアップデート署名やルート認証局（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と並んで最も古いものの一つです。「Goppa符号（ゴッパ符号）」と呼ばれる代数幾何符号を利用しており、メッセージに意図的にエラー（ノイズベクトル）を加えて暗号化し、秘密鍵としてGoppa符号のパリティ検査行列を持つ者だけが、強力な誤り訂正能力を使ってエラーを取り除き、元のメッセージを復号できる、という「&lt;strong>シンドローム復号問題 (Syndrome Decoding Problem)&lt;/strong>」に基づいています。&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の中で最も「時間が証明した強固な安全性」を持っています。&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〜3相当、AES-128〜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の世界においては、「サイズが小さく、速度が速く、数学的保証も完璧」という魔法のような単一のアルゴリズムは存在しません。&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. 実用化に向けた課題と「ハイブリッド暗号」という現実解
&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>この不確実性リスクを軽減するための現実的かつ推奨されるアプローチが「&lt;strong>ハイブリッド暗号 (Hybrid Cryptography)&lt;/strong>」です。&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 -->|"量子の脅威 ＆ 古典的な脆弱性 の両方に耐性"| E["安全なハイブリッド暗号通信 (TLS 1.3)"]&lt;/div>
&lt;p>これにより、「万が一量子コンピュータが実現してECCが破られてもKyberが通信を守り」、逆に「万が一Kyberに未知の数学的欠陥が見つかってもECCが通信を守る」という、堅牢な二段構えの安全性を実現できます。代表的な例として、IETFで標準化が進められている &lt;strong>X25519MLKEM768 (旧 X25519Kyber768)&lt;/strong> ドラフトがあり、現在のWebブラウザと最先端のサーバー間の通信は、まさにこのハイブリッド方式を用いて行われています。&lt;/p>
&lt;p>また、システム設計において「特定の暗号アルゴリズムに過度に依存せず、アルゴリズムが破綻した際に迅速に別のアルゴリズム（例：KyberからMcElieceへ、DilithiumからSPHINCS+へ）に切り替えられるアーキテクチャ」を構築する &lt;strong>Crypto Agility（暗号の俊敏性）&lt;/strong> の概念が、今後のシステム開発において必須の要件となります。&lt;/p>
&lt;hr>
&lt;h2 id="12-まとめ暗号技術の新たな地平線">12. まとめ：暗号技術の新たな地平線
&lt;/h2>&lt;p>量子コンピュータという人類の夢のテクノロジーは、皮肉にも私たちが長年信頼を寄せてきた「素因数分解」や「離散対数問題」という数学的防壁を打ち破る最大の脅威となりました。しかし、世界中の暗号学者たちはそれに屈することなく、格子理論、ハッシュ関数木、誤り訂正符号といったより複雑で深遠な多次元の数学的領域を開拓し、耐量子計算機暗号（PQC）という新たな防壁を築き上げました。&lt;/p>
&lt;p>NISTによるFIPS 203 (ML-KEM)、FIPS 204 (ML-DSA)、FIPS 205 (SLH-DSA) の標準化完了は、ゴールではありません。これから数十年続くPQCマイグレーションという壮大な旅の第一歩に過ぎません。ソフトウェアエンジニアやシステムアーキテクトにとって、これらの新アルゴリズムがもたらす「鍵サイズの増大」や「計算コストの変化」をネットワークプロトコルやシステムにどのように最適に適応させていくかが、これからの大きな技術的課題となるでしょう。&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></channel></rss>