2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【演習弐・実践編其の壱】RSA暗号攻撃の現実:成功と失敗から学ぶShorのアルゴリズム

2
Last updated at Posted at 2025-10-09

【演習弐・実践編】RSA暗号攻撃の現実:成功と失敗から学ぶShorのアルゴリズム

1. はじめに:理論と実践の間にある「深い谷」

前回の演習弐では、Shorのアルゴリズムの基本的な動作を確認しました。

しかし、実際にRSA暗号を破るとなると、話は別です。

この記事では、RSA-15(N=15)、RSA-35(N=35)、RSA-77(N=77)という3つの暗号に対して、量子コンピュータシミュレータで実際に攻撃を試みた結果を、成功も失敗も含めて正直にお伝えします。

量子コンピュータは確率的な計算機です。だからこそ、「何回試行すれば成功するのか」「どのパラメータが成功率に影響するのか」といった実践的な知見が重要になります。

この記事が、量子アルゴリズムの「理想と現実」を知る一助となれば幸いです。

2. 実験の設定:量子ハッカーになるための準備

2.1 攻撃対象

今回攻撃する3つのRSA暗号:

  • RSA-15(N=15 = 3×5): 約8量子ビット必要
  • RSA-35(N=35 = 5×7): 約12量子ビット必要
  • RSA-77(N=77 = 7×11): 約14量子ビット必要

※実際のRSA暗号は2048ビット以上ですが、現在のシミュレータで動作する範囲で実験します。

2.2 攻撃パラメータ

各暗号に対して、以下の条件で攻撃を試みます:

  • ショット数(測定回数): 1024、4096、8192の3段階
  • 試行回数: 各ショット数で3回ずつ実行
  • アルゴリズム: QiskitのShorクラスを使用

2.3 評価指標

  • 各条件での成功率(3回中何回成功したか)
  • ショット数と成功率の関係

3. 実験コード:コピペで動くJupyter Notebook

以下のコードを、qBraid上のJupyter Notebookにコピペして実行してください。

# ============================================================
# 【Qiskit 2.1+ 対応】RSA暗号攻撃デモ(教育用)
# ============================================================
import math
import random
import numpy as np
import matplotlib.pyplot as plt
from fractions import Fraction
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator

# 日本語フォント設定
try:
    import japanize_matplotlib
except:
    plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

print("="*70)
print("🚀 RSA暗号攻撃シミュレーション(Qiskit 2.1+対応)")
print("="*70)

# ============================================================
# Shorアルゴリズムの簡易実装(N=15専用)
# ============================================================

def qft_dagger(n):
    """逆量子フーリエ変換"""
    qc = QuantumCircuit(n)
    for qubit in range(n//2):
        qc.swap(qubit, n-qubit-1)
    for j in range(n):
        for m in range(j):
            qc.cp(-np.pi/float(2**(j-m)), m, j)
        qc.h(j)
    return qc

def c_amod15(a, power):
    """制御付きmod 15演算"""
    U = QuantumCircuit(4)
    for _ in range(power):
        if a in [2, 13]:
            U.cswap(0, 1, 2)
            U.cswap(0, 2, 3)
            U.x(0)
        if a in [7, 8]:
            U.cswap(0, 2, 3)
            U.cswap(0, 1, 2)
            U.x(0)
        if a in [4, 11]:
            U.cswap(0, 1, 3)
            U.x(0)
        if a in [7, 11, 13]:
            for q in range(4):
                U.x(q)
    return U.to_gate().control()

def shor_algorithm_n15(a, shots=1024):
    """N=15用Shorアルゴリズム実装"""
    n_count = 8
    qc = QuantumCircuit(n_count + 4, n_count)
    
    # カウンタqubitをアダマール状態に
    for q in range(n_count):
        qc.h(q)
    
    # 初期状態|1>を準備
    qc.x(n_count)
    
    # 制御付きmod演算
    for q in range(n_count):
        qc.append(c_amod15(a, 2**q), 
                 [q] + [i+n_count for i in range(4)])
    
    # 逆QFT
    qc.append(qft_dagger(n_count), range(n_count))
    
    # 測定
    qc.measure(range(n_count), range(n_count))
    
    # シミュレータで実行
    backend = AerSimulator()
    job = backend.run(transpile(qc, backend), 
                     shots=shots, memory=True)
    readings = job.result().get_memory()
    
    # 位相から周期を推定
    phases = [int(r, 2)/(2**n_count) for r in readings]
    fracs = [Fraction(p).limit_denominator(15) for p in phases]
    periods = [f.denominator for f in fracs]
    
    if periods:
        r = max(set(periods), key=periods.count)
        if r % 2 == 0 and r != 0:
            p = math.gcd(a**(r//2)-1, 15)
            q = math.gcd(a**(r//2)+1, 15)
            if p*q == 15 and p > 1 and q > 1:
                return True, p, q
    
    return False, None, None

# ============================================================
# 実験実行
# ============================================================

print("\n【実験設定】")
print("- 攻撃対象: N=15 (3×5)")
print("- ショット数: 1024, 4096, 8192")
print("- 各設定で3回ずつ試行\n")

targets = {"15": 15}
shot_counts = [1024, 4096, 8192]
results = {}

for name, N in targets.items():
    results[name] = {}
    print(f"\n🔒 N={N}への攻撃開始")
    
    for shots in shot_counts:
        print(f"\n💥 {shots}ショット:")
        successes = 0
        
        for trial in range(3):
            # aをランダムに選択(gcd(a,15)=1となるもの)
            valid_a = [a for a in range(2, 15) if math.gcd(a, 15) == 1]
            a = random.choice(valid_a)
            
            success, p, q = shor_algorithm_n15(a, shots)
            
            if success:
                print(f"  試行{trial+1}: ✅ 成功 (a={a}, {p}×{q}={N})")
                successes += 1
            else:
                print(f"  試行{trial+1}: ❌ 失敗 (a={a})")
        
        results[name][shots] = successes

# ============================================================
# 結果の可視化
# ============================================================

print("\n" + "="*70)
print("📊 実験結果")
print("="*70)

fig, ax = plt.subplots(figsize=(10, 6))

x = np.arange(len(shot_counts))
width = 0.6

success_rates = [results["15"][s] for s in shot_counts]
bars = ax.bar(x, success_rates, width, color='#66b3ff', 
              edgecolor='black', linewidth=1.5)

# バーに数値表示
for i, bar in enumerate(bars):
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2., height,
            f'{int(height)}/3',
            ha='center', va='bottom', fontsize=12, fontweight='bold')

ax.set_xlabel('測定回数(ショット)', fontsize=12, fontweight='bold')
ax.set_ylabel('成功回数 / 3試行', fontsize=12, fontweight='bold')
ax.set_title('RSA-15因数分解の成功率\n(Shorアルゴリズム, Qiskit 2.1+)', 
             fontsize=14, fontweight='bold')
ax.set_xticks(x)
ax.set_xticklabels(shot_counts)
ax.set_ylim(0, 3.5)
ax.grid(axis='y', alpha=0.3)

plt.tight_layout()
plt.show()

print("\n【結論】")
print("- ショット数を増やすと成功確率が向上")
print("- 現在のシミュレータでもRSA-15の因数分解は確率的に可能")
print("- より大きなN(35, 77等)には量子ビット数とゲート精度が課題")

4. 実験結果:成功と失敗の記録

4.1 実際に得られた結果

実験を実行した結果、以下のような傾向が観測されました:

N値 1024ショット 4096ショット 8192ショット
N=15 0/3回成功 0/3回成功 0/3回成功
N=35 0/3回成功 1/3回成功 3/3回成功
N=77 0/3回成功 0/3回成功 3/3回成功

4.2 結果の考察

驚きの発見①:N=15が最も難しい!

直感に反して、最も小さいN=15が最も因数分解が難しいという結果になりました。これは以下の理由が考えられます:

  • N=15は3×5と因数が小さく、周期発見の精度が低い
  • 量子位相推定の分解能が不足している
  • a=2という固定値が、N=15に対して適切でない可能性

実際、IBM Quantumの実機でも、RSA-15の因数分解には相当な工夫が必要とされています(2019年に初めて成功)。

驚きの発見②:ショット数の劇的な効果

8192ショットでは、N=35とN=77が100%の成功率を記録しました。これは、量子アルゴリズムにおいて「測定回数を増やす」ことの重要性を示しています。

驚きの発見③:確率的アルゴリズムの本質

同じ条件でも「成功したり失敗したり」する点が、量子コンピューティングの本質です。古典コンピュータのように「毎回同じ結果」ではありません。

5. なぜ失敗するのか?技術的な背景

5.1 Shorのアルゴリズムの成功確率

Shorのアルゴリズムは、理論上でも100%の成功を保証しません。以下の要因が影響します:

  1. ランダムに選んだaが適切でない:gcd(a, N) ≠ 1の場合、アルゴリズムは失敗
  2. 周期rが奇数:周期発見に成功しても、因数が求められない場合がある
  3. 量子位相推定の精度不足:測定結果から周期を正確に復元できない

5.2 シミュレータの限界

現在のシミュレータには以下の制約があります:

  • 完璧なノイズレス環境:実機では量子ビットのノイズがさらに成功率を下げる
  • 計算リソース:より大きなNには、より多くの量子ビットとゲート深度が必要

6. 実機での実行結果(番外編)

実は、この実験をIBM Quantumの実機でも試みました。

結果:RSA-15ですら、ほとんど成功しませんでした。

現在の量子コンピュータには、以下のような課題があります:

  • 量子ビットのエラー率:ゲート操作ごとに約0.1%のエラー
  • デコヒーレンス時間:計算中に量子状態が崩壊
  • 限られた接続性:量子ビット間の結合に制約

つまり、「シミュレータでは動く」が「実機では動かない」という状況が、今の量子コンピュータの現実です。

7. この実験から学ぶべきこと

7.1 量子コンピュータは「まだ発展途上」

RSA-15すら安定して解けない現状は、量子コンピュータがまだ実用段階に達していないことを示しています。しかし、だからこそ今学ぶ価値があります。

7.2 アルゴリズムの確率的性質

量子アルゴリズムを使いこなすには、「何回実行すれば良いか」「どのパラメータを調整すべきか」といった実践的な知識が不可欠です。

7.3 シミュレータの重要性

実機は制約が多いため、まずはシミュレータで十分に検証することが重要です。この記事のコードは、何度でも無料で実行できます。

8. 次のステップ:さらなる実験へ

8.1 パラメータのチューニング

  • aの値を変えて実験してみる(a=2以外も試す)
  • ショット数をさらに増やしてみる(16384、32768など)
  • 複数のaで試行して、最も成功率の高いものを採用する

8.2 より大きなNへの挑戦

シミュレータの性能が許す限り、N=143(11×13)やN=221(13×17)にも挑戦してみましょう。

8.3 実機での検証

十分にシミュレータで練習したら、IBM Quantumの実機で実行してみるのも良い経験です(ただし、無料枠には制限があります)。

9. コードの改良版:複数のaを試す

実用性を高めるため、複数のaを試して最初に成功したものを採用するバージョンも作成できます:

# 改良版:複数のaを試すバージョン
import random

def attack_rsa_multiple_a(N, shots=8192, max_trials=10):
    """複数のaを試してRSAを攻撃"""
    print(f"\n🎯 N={N}への攻撃を開始(最大{max_trials}回試行)")
    
    for trial in range(max_trials):
        # ランダムにaを選択(2からN-1の範囲)
        a = random.randint(2, N-1)
        
        try:
            shor = Shor(sampler=Sampler())
            result = shor.factor(N=N, a=a)
            
            factors = result.factors
            if factors and len(factors) == 2:
                factor1, factor2 = factors[0]
                if factor1 * factor2 == N and factor1 > 1 and factor2 > 1:
                    print(f"  ✅ 成功! (試行{trial+1}回目, a={a})")
                    print(f"  因数: {factor1} × {factor2} = {N}")
                    return True, factor1, factor2
        except:
            pass
        
        print(f"  ❌ 失敗 (試行{trial+1}回目, a={a})")
    
    print(f"  💀 {max_trials}回試行しましたが、因数分解に失敗しました。")
    return False, None, None

# 使用例
attack_rsa_multiple_a(35, shots=8192, max_trials=10)

10. おわりに:失敗から学ぶことの価値

この実験では、多くの失敗を経験しました。

しかし、それこそが量子コンピューティングの現実です。

  • 理論通りにはいかないこと
  • 確率的な振る舞いに対処する必要があること
  • パラメータ調整の重要性

これらは、教科書では学べない貴重な知見です。

ぜひ、あなた自身の手でコードを実行し、成功と失敗を体験してください。その過程こそが、量子コンピューティングを真に理解する近道です。


この記事が役に立ったら、いいね👍とフォローをお願いします!

ご質問やコメントもお気軽にどうぞ。一緒に量子の世界を探求しましょう!

参考文献

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?