<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Math on kenji.blog</title><link>http://kenji.blog/tags/math/</link><description>Recent content in Math 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/tags/math/index.xml" rel="self" type="application/rss+xml"/><item><title>【初心者向け】Transformerモデルの数学的構造を読み解く</title><link>http://kenji.blog/p/transformer-mathematical-structure/</link><pubDate>Fri, 11 Sep 2026 22:00:00 +0900</pubDate><guid>http://kenji.blog/p/transformer-mathematical-structure/</guid><description>&lt;img src="http://kenji.blog/p/transformer-mathematical-structure/img/eyecatch.jpg" alt="Featured image of post 【初心者向け】Transformerモデルの数学的構造を読み解く" />&lt;h1 id="はじめになぜtransformerの数学を学ぶのか">はじめに：なぜTransformerの数学を学ぶのか？
&lt;/h1>&lt;p>現代の自然言語処理（NLP）、そしてAI全体の歴史を塗り替えたと言っても過言ではないアーキテクチャが「Transformer」です。2017年にGoogleの研究者らによって発表された論文『Attention Is All You Need』で初めて提案されたこのモデルは、OpenAIのGPTシリーズ（ChatGPTの基盤技術）やGoogleのBERT、AnthropicのClaudeなど、現在世界を席巻している大規模言語モデル（LLM）の心臓部として機能しています。&lt;/p>
&lt;p>しかし、Transformerの仕組みについて「Attention（注意機構）を使って文脈を理解する」といった定性的な説明はよく見かけますが、その背後にある&lt;strong>数学的な構造&lt;/strong>について初心者向けに深く掘り下げた解説は意外と少ないのが現状です。AIがどのようにして「言葉」を「数式」として処理し、驚くほど自然な文章を生成しているのかを真に理解するためには、その数学的メカニズムを読み解くことが不可欠です。&lt;/p>
&lt;p>本記事では、数学やプログラミングの基礎知識を持つ方（高校レベルの行列や微分の概念がわかる方）を対象に、Transformerの心臓部である「Self-Attention機構」「クエリ・キー・バリュー（Q/K/V）モデル」「Softmax関数による正規化」、そして「Positional Encoding」などの数学的構造を徹底的に、かつわかりやすく解き明かしていきます。&lt;/p>
&lt;p>数式の羅列に圧倒されるかもしれませんが、一つ一つの計算には明確な「意味」があります。この記事を読み終える頃には、Transformerが単なる魔法のブラックボックスではなく、精緻に設計された数学と統計の結晶であることが理解できるはずです。&lt;/p>
&lt;hr>
&lt;h1 id="1-従来手法の限界とtransformerの革新性">1. 従来手法の限界とTransformerの革新性
&lt;/h1>&lt;p>Transformerが登場する前、自然言語処理の主流はリカレントニューラルネットワーク（RNN）や、その派生であるLSTM（Long Short-Term Memory）でした。RNNは時系列データを処理するために設計されており、文章を単語ごとに先頭から順番に読み込んでいきます。&lt;/p>
&lt;p>しかし、RNNには致命的な弱点が2つありました。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>長期依存性の学習が困難&lt;/strong>：文章が長くなると、最初の方に入力された単語の情報が、最後の方に到達するまでに薄れてしまう（勾配消失問題）。&lt;/li>
&lt;li>&lt;strong>並列計算が不可能&lt;/strong>：単語を順番に処理しなければならないため、GPUを用いた大規模な並列計算が難しく、学習に膨大な時間がかかる。&lt;/li>
&lt;/ol>
&lt;p>Transformerは、RNNの構造を完全に捨て去り、「Attention」のみを用いて文脈を捉えるというパラダイムシフトを起こしました。これにより、系列長がどれだけ長くても情報の損失がなく、かつ計算を並列化してGPUの性能を最大限に引き出すことが可能になったのです。&lt;/p>
&lt;hr>
&lt;h1 id="2-transformerの全体アーキテクチャ">2. Transformerの全体アーキテクチャ
&lt;/h1>&lt;p>まずは、Transformer全体のアーキテクチャを俯瞰してみましょう。Transformerは大きく分けて「Encoder（エンコーダ）」と「Decoder（デコーダ）」の2つのブロックから構成されています。翻訳タスクを例にとると、Encoderが入力言語（例：英語）を数学的なベクトル表現に変換し、Decoderがそのベクトル表現をもとに出力言語（例：日本語）を生成します。&lt;/p>
&lt;p>以下の図は、Encoderブロックの内部構造を簡略化したものです。&lt;/p>
&lt;div class="mermaid">graph TD
A["Input Tokens"] --> B["Input Embedding"]
B --> C["Positional Encoding"]
C --> D["Multi-Head Self-Attention"]
D --> E["Add &amp; Layer Normalization"]
E --> F["Feed Forward Network"]
F --> G["Add &amp; Layer Normalization"]
G --> H["Output to Next Layer"]
C -.->|"Residual Connection"| E
E -.->|"Residual Connection"| G&lt;/div>
&lt;p>ここからは、各コンポーネントで行われている数学的な操作を順を追って見ていきましょう。&lt;/p>
&lt;hr>
&lt;h1 id="3-単語のベクトル化と位置エンコーディングpositional-encoding">3. 単語のベクトル化と位置エンコーディング（Positional Encoding）
&lt;/h1>&lt;p>コンピュータはテキストをそのまま理解することはできません。入力されたテキストはまず「トークン（Token）」と呼ばれる単位に分割され、それぞれが固定長のベクトルに変換されます。これが&lt;strong>Input Embedding&lt;/strong>です。&lt;/p>
&lt;h2 id="31-input-embeddingの数学">3.1 Input Embeddingの数学
&lt;/h2>&lt;p>語彙（ボキャブラリー）のサイズを $V$、埋め込みベクトルの次元数を $d_{model}$（元の論文では $d_{model} = 512$）とします。各単語 $w_i$ は、埋め込み行列 $W_E \in \mathbb{R}^{V \times d_{model}}$ を用いてベクトル $x_i \in \mathbb{R}^{d_{model}}$ に変換されます。&lt;/p>
$$ x_i = W_E \cdot \text{one\_hot}(w_i) $$
&lt;p>これにより、文章全体は行列 $X \in \mathbb{R}^{N \times d_{model}}$ として表現されます（$N$ は文章の長さ）。&lt;/p>
&lt;h2 id="32-positional-encoding位置エンコーディングの必要性と数式">3.2 Positional Encoding（位置エンコーディング）の必要性と数式
&lt;/h2>&lt;p>TransformerはRNNのように単語を順番に処理するわけではなく、すべての単語を同時に並列処理します。これは計算速度の観点では大きなメリットですが、同時に**「単語の順番（語順）」という重要な情報が失われてしまう**という問題を引き起こします。例えば、「犬が人を噛む」と「人が犬を噛む」は、入力される単語集合は同じですが意味は全く異なります。&lt;/p>
&lt;p>この語順情報をモデルに与えるために考案されたのが&lt;strong>Positional Encoding&lt;/strong>です。
位置 $pos$ にある単語の $i$ 番目の次元に対するPositional Encoding $PE$ は、以下の三角関数を用いて計算されます。&lt;/p>
$$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
$$ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
&lt;p>ここで、$pos$ は単語の位置（$0, 1, 2, \dots, N-1$）、$i$ はベクトルの次元のインデックス（$0, 1, \dots, d_{model}/2 - 1$）です。&lt;/p>
&lt;h3 id="なぜサインとコサインを使うのか">なぜサインとコサインを使うのか？
&lt;/h3>&lt;p>一見すると非常に複雑で奇妙な数式に見えますが、これには深い数学的な理由があります。三角関数を使用することで、モデルは**「絶対的な位置」だけでなく「相対的な位置」の差分**を容易に学習できるようになるのです。&lt;/p>
&lt;p>高校数学で学ぶ三角関数の加法定理を思い出してください。
&lt;/p>
$$ \sin(\alpha + \beta) = \sin\alpha \cos\beta + \cos\alpha \sin\beta $$
$$ \cos(\alpha + \beta) = \cos\alpha \cos\beta - \sin\alpha \sin\beta $$
&lt;p>ある位置 $pos$ からオフセット $k$ だけ離れた位置 $pos + k$ のPositional Encodingは、位置 $pos$ のPositional Encodingの線形結合として表すことができます。つまり、行列 $M_k$ を用いて次のように書けます。&lt;/p>
$$ PE_{pos+k} = M_k \cdot PE_{pos} $$
&lt;p>これにより、Attention機構は単語同士が「どれくらい離れているか」という相対距離を、内積計算を通じて簡単に認識できるようになります。また、波長が異なる複数のサイン・コサイン波を組み合わせることで、どんなに長い文章であっても一意の位置ベクトルを生成できるという利点もあります。&lt;/p>
&lt;p>最終的な入力行列 $X_{input}$ は、単語の埋め込みベクトルにこの位置エンコーディングを足し合わせたものになります。&lt;/p>
$$ X_{input} = X + PE $$
&lt;hr>
&lt;h1 id="4-self-attention自己注意機構の深淵なる数学">4. Self-Attention（自己注意機構）の深淵なる数学
&lt;/h1>&lt;p>いよいよTransformerの最重要コンポーネントである**Self-Attention（自己注意機構）**に踏み込みます。Self-Attentionの目的は、「文章内のすべての単語同士の関連度を計算し、各単語のベクトルを、文脈を考慮したより豊かな表現に更新すること」です。&lt;/p>
&lt;p>ここでは、「検索システム」のアナロジーが使われます。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Query (Q)&lt;/strong>: クエリ（検索語）。「私が今探している情報は何か？」&lt;/li>
&lt;li>&lt;strong>Key (K)&lt;/strong>: キー（見出し）。「私が持っている情報は何か？」&lt;/li>
&lt;li>&lt;strong>Value (V)&lt;/strong>: バリュー（実体）。「私が実際に提供する情報は何か？」&lt;/li>
&lt;/ul>
&lt;h2 id="41-行列-q-k-v-の生成">4.1 行列 $Q, K, V$ の生成
&lt;/h2>&lt;p>入力行列 $X \in \mathbb{R}^{N \times d_{model}}$（ここでは話を簡単にするためバッチサイズは無視します）に対して、学習可能な重み行列 $W^Q, W^K, W^V \in \mathbb{R}^{d_{model} \times d_k}$ を掛け合わせることで、クエリ $Q$、キー $K$、バリュー $V$ を計算します。（通常 $d_k = d_v = d_{model} / h$）&lt;/p>
$$ Q = X W^Q $$
$$ K = X W^K $$
$$ V = X W^V $$
&lt;p>ここで、$Q, K, V$ はすべて $\mathbb{R}^{N \times d_k}$ の行列になります。&lt;/p>
&lt;h2 id="42-attention-scoreの計算内積">4.2 Attention Scoreの計算（内積）
&lt;/h2>&lt;p>各単語のQueryが、他のすべての単語のKeyとどれくらい関連しているかを測るために、ベクトルの&lt;strong>内積&lt;/strong>を計算します。行列演算で書くと以下のようになります。&lt;/p>
$$ \text{Scores} = Q K^T $$
&lt;p>この計算により得られる行列 $\text{Scores} \in \mathbb{R}^{N \times N}$ の各要素 $s_{ij}$ は、$i$ 番目の単語のQueryと $j$ 番目の単語のKeyの内積、すなわち「関連度の強さ」を表します。&lt;/p>
&lt;h2 id="43-スケーリングscale">4.3 スケーリング（Scale）
&lt;/h2>&lt;p>内積によるスコア計算には一つの問題があります。ベクトルの次元 $d_k$ が大きくなると、内積の値が極端に大きくなったり小さくなったりしてしまうのです。&lt;/p>
&lt;p>これを数学的に証明しましょう。
クエリの各要素 $q \sim \mathcal{N}(0, 1)$、キーの各要素 $k \sim \mathcal{N}(0, 1)$ が独立した標準正規分布に従うと仮定します。
内積 $q \cdot k = \sum_{i=1}^{d_k} q_i k_i$ の平均と分散を求めます。
平均： $\mathbb{E}[q_i k_i] = \mathbb{E}[q_i] \mathbb{E}[k_i] = 0 \times 0 = 0$ なので、和の平均も $0$。
分散： $q_i k_i$ の分散は、独立性から $\text{Var}(q_i k_i) = \mathbb{E}[(q_i k_i)^2] - (\mathbb{E}[q_i k_i])^2 = 1 \times 1 - 0 = 1$。
したがって、内積全体の分散は次元数 $d_k$ に等しくなります。&lt;/p>
$$ \text{Var}(q \cdot k) = d_k $$
&lt;p>分散が大きくなると、この後適用するSoftmax関数において、最大値以外の勾配が極端に小さくなる「勾配消失」が発生し、学習が進まなくなってしまいます。
これを防ぐため、スコアを $\sqrt{d_k}$ で割る（スケーリングする）ことで、分散を常に $1$ に保つようにします。&lt;/p>
$$ \text{Scaled Scores} = \frac{Q K^T}{\sqrt{d_k}} $$
&lt;h2 id="44-softmax関数による確率化">4.4 Softmax関数による確率化
&lt;/h2>&lt;p>得られたスコアを、合計が $1$ になるような確率分布（重み）に変換するために &lt;strong>Softmax関数&lt;/strong> を行ごとに適用します。&lt;/p>
$$ a_{ij} = \text{softmax}(s_i)_j = \frac{\exp(s_{ij} / \sqrt{d_k})}{\sum_{m=1}^N \exp(s_{im} / \sqrt{d_k})} $$
&lt;p>行列 $A \in \mathbb{R}^{N \times N}$ はAttention Weight（注意の重み）行列と呼ばれます。この行列の各行 $i$ を見ると、「単語 $i$ を理解する上で、他のどの単語 $j$ にどれくらい注目（Attention）すべきか」が 0 から 1 の値で表現されています。&lt;/p>
&lt;h2 id="45-valueの重み付き和">4.5 Valueの重み付き和
&lt;/h2>&lt;p>最後に、得られたAttention Weight行列 $A$ を用いて、Value行列 $V$ の重み付き和を計算します。&lt;/p>
$$ \text{Output} = A V = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V $$
&lt;p>この演算によって出力される行列 $Z \in \mathbb{R}^{N \times d_v}$ は、「文脈を考慮して更新された単語のベクトル表現」の集まりとなります。
これが、論文で定義されている &lt;strong>Scaled Dot-Product Attention&lt;/strong> の全容です。&lt;/p>
&lt;hr>
&lt;h1 id="5-multi-head-attentionマルチヘッドアテンション">5. Multi-Head Attention（マルチヘッド・アテンション）
&lt;/h1>&lt;p>一回のAttention計算（シングルヘッド）だけでは、一つの観点（例えば「文法的な関係」）しか文脈を捉えられない可能性があります。そこで、言語の持つ多様な意味的・構文的関係（「主語と述語」「代名詞とその指示先」など）を同時に捉えるために、&lt;strong>Multi-Head Attention&lt;/strong> が導入されました。&lt;/p>
&lt;p>先ほどの $Q, K, V$ の生成とAttention計算を、並列に $h$ 回（ヘッドの数。元の論文では $h=8$）行います。&lt;/p>
$$ \text{head}_i = \text{Attention}(X W_i^Q, X W_i^K, X W_i^V) $$
&lt;p>ここで、$W_i^Q, W_i^K, W_i^V \in \mathbb{R}^{d_{model} \times d_k}$ は $i$ 番目のヘッド専用の学習可能な重み行列です。&lt;/p>
&lt;p>各ヘッドから出力された結果 $\text{head}_i \in \mathbb{R}^{N \times d_v}$ は、横に結合（Concatenate）されます。&lt;/p>
$$ \text{Concat}(\text{head}_1, \dots, \text{head}_h) \in \mathbb{R}^{N \times (h \cdot d_v)} $$
&lt;p>通常 $h \cdot d_v = d_{model}$ となるように設定されるため、結合後の次元は再び入力と同じ $d_{model}$ に戻ります。最後に、この行列に重み行列 $W^O \in \mathbb{R}^{d_{model} \times d_{model}}$ を掛けて、最終的な出力を得ます。&lt;/p>
$$ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h) W^O $$
&lt;div class="mermaid">graph TD
X["Input X"] --> Q1["Q1"]
X --> K1["K1"]
X --> V1["V1"]
Q1 &amp; K1 &amp; V1 --> H1["Head 1"]
X --> Q2["Q2"]
X --> K2["K2"]
X --> V2["V2"]
Q2 &amp; K2 &amp; V2 --> H2["Head 2"]
X --> QN["..."]
X --> KN["..."]
X --> VN["..."]
QN &amp; KN &amp; VN --> HN["Head h"]
H1 &amp; H2 &amp; HN --> C["Concatenate"]
C --> WO["Multiply by WO"]
WO --> OUT["Multi-Head Output"]&lt;/div>
&lt;hr>
&lt;h1 id="6-feed-forward-neural-network-ffn">6. Feed-Forward Neural Network (FFN)
&lt;/h1>&lt;p>Multi-Head Attentionの出力は、次に &lt;strong>Position-wise Feed-Forward Network (FFN)&lt;/strong> に入力されます。
これは、系列の「各位置（単語）ごとに独立して」適用される2層の全結合ニューラルネットワークです。&lt;/p>
&lt;p>数式で表すと以下のようになります。&lt;/p>
$$ \text{FFN}(x) = \max(0, x W_1 + b_1) W_2 + b_2 $$
&lt;p>ここで、$\max(0, z)$ はReLU（Rectified Linear Unit）活性化関数を表しています（最近のモデルではGELUやSwiGLUが使われることも多いです）。&lt;/p>
&lt;p>このネットワークの役割は非常に重要です。Attention機構は「単語間の関係性（空間的・系列的な関係）」を学習しますが、FFNは「各単語ベクトル自体の非線形な特徴変換」を担当します。
通常、第一層の重み $W_1$ によって次元を一時的に大きく拡大（例えば $d_{model}=512$ から $d_{ff}=2048$ に4倍拡大）し、特徴空間上で複雑な計算を行った後、第二層の重み $W_2$ で再び元の次元に戻します。この「次元の拡大と縮小」によって、モデルの表現力が飛躍的に高まっています。&lt;/p>
&lt;hr>
&lt;h1 id="7-残差接続residual-connectionとlayer-normalization">7. 残差接続（Residual Connection）とLayer Normalization
&lt;/h1>&lt;p>ディープラーニングにおいて、ネットワークの層を深くしていくと、学習時に勾配が消失または爆発してしまい、うまく学習できなくなる問題が発生します。これを防ぐために、Transformerの各サブレイヤー（AttentionとFFN）の周囲には、&lt;strong>残差接続（Residual Connection）&lt;/strong> と &lt;strong>Layer Normalization（層正規化）&lt;/strong> が配置されています。&lt;/p>
&lt;p>数式で書くと、サブレイヤーの出力は次のように処理されます。&lt;/p>
$$ \text{Output} = \text{LayerNorm}(x + \text{Sublayer}(x)) $$
&lt;h2 id="71-残差接続-x--textsublayerx">7.1 残差接続 ($x + \text{Sublayer}(x)$)
&lt;/h2>&lt;p>入力 $x$ をサブレイヤーの出力に直接足し合わせます。これにより、逆伝播時に勾配がショートカットを通って直接浅い層に伝わるため、層を深くしても学習が安定します。&lt;/p>
&lt;h2 id="72-layer-normalizationの数学">7.2 Layer Normalizationの数学
&lt;/h2>&lt;p>Layer Normalizationは、特徴次元方向の平均と分散を計算し、データを正規化する技術です。バッチサイズ $B$、系列長 $N$、次元数 $d_{model}$ の入力において、ある一つの単語ベクトル $x \in \mathbb{R}^{d_{model}}$ に対して正規化を行います。&lt;/p>
&lt;p>平均 $\mu$ と分散 $\sigma^2$ を計算します。
&lt;/p>
$$ \mu = \frac{1}{d_{model}} \sum_{i=1}^{d_{model}} x_i $$
$$ \sigma^2 = \frac{1}{d_{model}} \sum_{i=1}^{d_{model}} (x_i - \mu)^2 $$
&lt;p>そして、正規化された出力 $\hat{x}$ を得ます。
&lt;/p>
$$ \text{LN}(x) = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} \odot \gamma + \beta $$
&lt;p>
（$\epsilon$ はゼロ除算を防ぐための微小な定数。$\gamma, \beta$ は学習可能なスケールとシフトのパラメータ）&lt;/p>
&lt;p>バッチ方向の正規化（Batch Normalization）ではなく、層方向の正規化（Layer Normalization）を採用した理由は、文章のように長さが一定でない系列データを処理する際、バッチ間の統計量が不安定になりやすいためです。Layer Normalizationにより、Transformerはバッチサイズに依存せずに安定した学習が可能になります。&lt;/p>
&lt;hr>
&lt;h1 id="8-デコーダ特有の構造masked-attentionとcross-attention">8. デコーダ特有の構造：Masked AttentionとCross-Attention
&lt;/h1>&lt;p>ここまで解説した構造はエンコーダのものです。文章を生成するデコーダブロックでは、構造が少し異なります。&lt;/p>
&lt;h2 id="81-masked-multi-head-attention">8.1 Masked Multi-Head Attention
&lt;/h2>&lt;p>デコーダの役割は「過去の単語から次の単語を予測すること」です。したがって、訓練時に「未来の単語」を見てしまってはカンニングになってしまいます。これを防ぐための数学的操作が &lt;strong>Masking（マスキング）&lt;/strong> です。&lt;/p>
&lt;p>スコア行列 $Q K^T$ に対して、上三角部分（未来の情報に相当）に $-\infty$ に近い非常に小さな値を設定するマスク行列 $M$ を足し合わせます。&lt;/p>
$$ M_{ij} = \begin{cases} 0 &amp; (i \le j) \\ -\infty &amp; (i > j) \end{cases} $$
$$ \text{Masked Attention}(Q, K, V) = \text{softmax}\left(\frac{Q K^T + M}{\sqrt{d_k}}\right) V $$
&lt;p>Softmax関数を計算する際、$\exp(-\infty) = 0$ となるため、未来の単語へのAttention Weightは完全に $0$ になります。これにより、因果関係（Causality）を保持した自己回帰的な生成が可能になります。&lt;/p>
&lt;h2 id="82-encoder-decoder-cross-attention">8.2 Encoder-Decoder Cross-Attention
&lt;/h2>&lt;p>デコーダの2つ目のサブレイヤーは、エンコーダからの出力を参照する &lt;strong>Cross-Attention&lt;/strong> です。
ここでは、$Q$ は一つ前のデコーダ層から生成されますが、$K$ と $V$ はエンコーダの最終層の出力から生成されます。&lt;/p>
$$ Q_{decoder} = X_{dec} W^Q $$
$$ K_{encoder} = X_{enc} W^K $$
$$ V_{encoder} = X_{enc} W^V $$
&lt;p>この計算により、翻訳タスクなどで「今翻訳している単語が、元の外国語の文章のどの部分に強く関連しているか」をモデルが学習できるようになります。&lt;/p>
&lt;hr>
&lt;h1 id="9-計算量と現代における最適化の数学">9. 計算量と現代における最適化の数学
&lt;/h1>&lt;p>Transformerは素晴らしいモデルですが、その数学的構造ゆえの「弱点」も存在します。
Self-Attentionの計算量に注目してください。スコア行列 $Q K^T$ の計算では、$(N \times d_k)$ の行列と $(d_k \times N)$ の行列を掛け合わせるため、その計算量は &lt;strong>$O(N^2 \cdot d_{model})$&lt;/strong> となります。&lt;/p>
&lt;p>つまり、&lt;strong>系列長 $N$ に対して計算量とメモリ使用量が二乗で増加する&lt;/strong>のです。
文章が短い場合は問題になりませんが、書籍丸ごと一冊のような長大なコンテキストをLLMに入力しようとすると、$N$ が数万〜数十万に達し、従来のAttention計算ではGPUのメモリが即座に枯渇してしまいます。&lt;/p>
&lt;p>この $O(N^2)$ の呪いを断ち切るために、近年では数学的・ハードウェア的アプローチから様々な最適化が提案されています。
その代表例が &lt;strong>FlashAttention&lt;/strong> です。FlashAttentionは、GPUのメモリ階層（SRAMとHBM）間のデータ転送（メモリアクセス）を最小限に抑えるよう、Attention計算をタイル状に分割（Tiling）して行うアルゴリズムです。数式上は標準的なAttentionと全く同じ結果を出力する（Exact Attention）にもかかわらず、ハードウェアレベルの最適化により劇的な高速化とメモリ削減を実現し、GPT-4などの長文脈モデルの実現を可能にしました。&lt;/p>
&lt;p>他にも、計算量を $O(N \log N)$ や $O(N)$ に近似する Sparse Attention や Linear Attention などの研究も盛んに行われています。&lt;/p>
&lt;hr>
&lt;h1 id="10-実装のイメージpytorch風の擬似コード">10. 実装のイメージ（PyTorch風の擬似コード）
&lt;/h1>&lt;p>ここまでの数学的構造を、実際のプログラミングコード（Python / PyTorch）に落とし込むと、驚くほどシンプルに記述できることがわかります。Self-Attentionのコア部分の擬似コードを示します。&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;/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">torch&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">torch.nn.functional&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">F&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">math&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">scaled_dot_product_attention&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">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">mask&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># q, k, vの形状: [batch_size, num_heads, seq_length, d_k]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">d_k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">q&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">size&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># 1. 内積によるスコア計算: Q * K^T&lt;/span>
&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">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&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">k&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">transpose&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&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">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">scores&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="n">math&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sqrt&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">d_k&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. マスキング (Masked Attentionの場合)&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">mask&lt;/span> &lt;span class="ow">is&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scores&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">scores&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">masked_fill&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">mask&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mf">1e9&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"># 4. Softmaxによる確率化&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">attention_weights&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">softmax&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">scores&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&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"># 5. Value行列の掛け合わせ&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">output&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">matmul&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">attention_weights&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v&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">output&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">attention_weights&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>数式で表された $Q K^T / \sqrt{d_k}$ が &lt;code>torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k)&lt;/code> として直感的に実装されていることがわかります。数学の理論が、高度な最適化ライブラリの力を借りて数行のコードで実現されるのは、ディープラーニングの非常に面白い側面です。&lt;/p>
&lt;hr>
&lt;h1 id="おわりに数式から見えてくる知能の形">おわりに：数式から見えてくる「知能」の形
&lt;/h1>&lt;p>本記事では、Transformerモデルの深奥にある数学的構造を解き明かしてきました。&lt;/p>
&lt;p>単語を多次元ベクトル空間にマッピングするEmbedding、位置情報を三角波の合成で表現するPositional Encoding、そして情報検索のアナロジーから生まれた行列の内積計算であるSelf-Attention機構。これら一つ一つのコンポーネントは、線形代数、微積分、確率統計といった基礎的な数学の積み重ねにすぎません。&lt;/p>
&lt;p>しかし、これらの単純な行列演算が何層にも重なり、何十億、何千億というパラメータを通じて巨大なデータセットからパターンを学習するとき、そこには私たちの「言葉」を解し、論理的な推論を行い、時に創造的なアイデアを生み出すかのような「知能の形」が立ち現れます。&lt;/p>
&lt;p>「Attention Is All You Need」という挑発的なタイトルが示す通り、複雑なリカレント処理や畳み込み処理を捨て去り、純粋な「アテンション（関連度）」の計算に特化したこのアーキテクチャの美しさは、その数学的なシンプルさにこそあると言えるでしょう。&lt;/p>
&lt;p>今後、Transformerを超える新たなアーキテクチャ（State Space ModelであるMambaなど）が登場する可能性もありますが、Transformerが築き上げた「Attentionによる文脈理解」の数学的枠組みは、AIの歴史に永遠に刻まれるはずです。&lt;/p>
&lt;p>もしあなたが今後、ChatGPTやClaudeなどのLLMを使う機会があれば、そのバックグラウンドで毎秒何兆回もの $Q K^T$ の行列積が計算され、Softmax関数が確率を弾き出している様子を想像してみてください。技術に対する解像度が上がり、よりAIの世界が面白く感じられるはずです。&lt;/p>
&lt;h3 id="参考文献">参考文献
&lt;/h3>&lt;ul>
&lt;li>Vaswani, A., et al. (2017). &amp;ldquo;Attention Is All You Need.&amp;rdquo; &lt;em>Advances in Neural Information Processing Systems&lt;/em>.&lt;/li>
&lt;li>Alammar, J. (2018). &amp;ldquo;The Illustrated Transformer.&amp;rdquo;&lt;/li>
&lt;/ul>
&lt;hr>
&lt;p>&lt;em>この記事は、自然言語処理とAIの数学的基礎を学ぶ方々へのガイドとして執筆されました。質問や議論があれば、ぜひコメント欄でお知らせください！&lt;/em>&lt;/p></description></item><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>リーマン予想と素数の分布：現代暗号技術との深い関係</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>【数学解説】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></channel></rss>