0から分かる公開鍵暗号とパスキー 〜パスワードを送らずにログインする魔法の正体〜
1. 3行でわかる結論と全体図
- これまでのパスワードは、インターネットの通信路上を「合言葉そのもの」が流れていたため、途中で盗まれたりサーバー側から漏洩したりする危険がありました。
- 公開鍵暗号(こうかいかぎあんごう)とは、「誰でも閉められる開いた南京錠(公開鍵)」を世界中に配り、「開ける鍵(秘密鍵)」だけを自分の手元に隠し持つ技術です。
- 最新のパスキーは、この仕組みを使い、パスワードも顔写真も一切外へ送らずに、スマホの中で「秘密のサイン」を押して送るだけなので、100%盗まれません。
2. なぜ生まれたか?(昔の不便と解決策)
1970年代以前の暗号は、すべて**「共通鍵暗号(共通の合言葉)」**でした。 たとえば、AさんとBさんが秘密の通信をする場合、あらかじめ2人だけで「合言葉はリンゴね」と約束しておく必要がありました。
しかし、インターネットのように「世界中の見知らぬ人同士」が通信する場合、致命的な問題が発生します。
「そもそも、最初の合言葉(鍵)をどうやって安全に相手に渡すのか?」
合言葉をメールで送れば、そのメールを盗み見られた瞬間に終わりです(これを「鍵配送問題」と呼びます)。
これを1976年に数学の力で完全に解決したのが、ディフィー、ヘルマン、そしてRSA暗号を作ったリベスト・シャミア・エードルマンの数学者たちでした。 彼らはこう考えました。 「かける鍵(南京錠)と、開ける鍵を、別々の2つのペアにしてしまえばいいのではないか?」
- 南京錠(公開鍵):カシャッと閉めることしかできない鍵。世界中の誰に配っても、道路にばら撒いても安全。
- 本物の鍵(秘密鍵):開けることができる唯一の鍵。自分だけが絶対にポケットから出さない。
あなた宛てに手紙を送りたい人は、あなたの「開いた南京錠」を拾ってきて箱を閉じます。 一度閉まった箱は、世界中で「あなた」しか開けられません。これで、事前に秘密の合言葉を共有する必要がなくなったのです。
触って試す:公開鍵の南京錠体験
🔐 公開鍵の南京錠体験
あなたは「秘密鍵」の持ち主です。ボタンを順番に押して、南京錠(公開鍵)を使った安全なメッセージの受け渡しを体験してください。
※これは仕組みを理解するための概念モデルです。実際の暗号では、素数の数学(素因数分解の難しさ)がこの「南京錠と鍵」の関係を実現しています。
3. 仕組みの種明かし(3つの重要ルール)
現代のセキュリティを支える3つの最重要キーワードです。
| 専門用語 | 小学生でもわかる例え | 実際にやっていること |
|---|---|---|
| 素数(そすう) | 暗号の「原子」 | 「1と自分自身でしか割れない数(3, 7, 11など)」。素数同士の掛け算は一瞬ですが、答えから元の2つの素数を当てる(素因数分解)には何百年もかかります。この計算の落とし穴を暗号に悪用しています。 |
| デジタル署名 | 絶対に偽造できないハンコ | 公開鍵暗号を逆向きに使い、「自分しか持っていない秘密鍵」でデータに印をつけること。誰でも公開鍵を使って「本人が押したハンコだ」と確認できます。 |
| パスキー (Passkeys) | スマホの金庫番 | 秘密鍵をスマホ内の最強のセキュリティチップに保存し、Face IDや指紋で人間を確認したときだけハンコを押してログインする最新技術です。 |
4. 作って暴く!(Google Colab 15分ミニ実験)
「素数でどうやって鍵を作るのか?」「デジタル署名はどうやって確かめるのか?」を、Pythonで実験してみましょう。 以下のリンクからGoogle Colabを開き、コードを実行してみてください。
実験①:たった15行で作る「超小型RSA暗号」パズル
2つの小さな素数($p = 3$、$q = 11$)を使って、本物の暗号化と復号の計算を体験します。
# === 実験①:小さな素数で作る超小型RSA暗号 ===
# 1. 2つの素数を選ぶ(実際のRSAは数百桁の超巨大な素数を使います)
p = 3
q = 11
n = p * q # n = 33 (これが南京錠の大きさになります)
# 2. 公開鍵(誰でも閉められる南京錠の番号 e)と、秘密鍵(自分だけの開ける鍵 d)
e = 7 # 公開鍵:33と7のペアを世界中に配る
d = 3 # 秘密鍵:自分だけが絶対に秘密にする数字(※数学的に導かれる値)
print(f"🔒 あなたが世界に配る南京錠(公開鍵): (n={n}, e={e})")
print(f"🗝️ あなたがポケットに隠す鍵(秘密鍵): (d={d})\n")
# 3. 友達があなたに秘密の数字「2」を送りたい(暗号化)
# 計算式: (元の数字 ^ e) を n で割った「あまり」
secret_number = 2
encrypted = (secret_number ** e) % n
print(f"友達が送った数字: {secret_number}")
print(f"👉 通信路上を流れる暗号データ: {encrypted} (盗み見られても元の数字は分からない!)\n")
# 4. あなたの手元に届いた暗号を、自分だけの秘密鍵 d で開ける(復号)
# 計算式: (暗号データ ^ d) を n で割った「あまり」
decrypted = (encrypted ** d) % n
print(f"あなたが秘密鍵で開けた数字: {decrypted}")
print(f"⭕ 判定: 元の数字【{secret_number}】と完全一致!")
実験①の種明かし
暗号化するときは公開鍵の e=7 を使い、元に戻すときは秘密鍵の d=3 を使いました。
なぜ掛け算と割り算の「あまり」だけで元に戻るのか?
それは、**素数同士を掛け合わせた数($33$)の世界では、特定の数字で何乗かすると元の数に戻るという数学の魔法(フェルマーの小定理)**が働いているからです。
第三者が秘密鍵 d=3 を割り出すには、$33$ を素因数分解して $3$ と $11$ を見つけなければなりません。桁数が数百桁になると、世界中のスーパーコンピュータを集めても何万年もかかります。
実験②:パスキーの裏側!「デジタル署名」で本人確認する
次に、パスキーがログイン時に行っている「挑戦状へのサイン」を完全再現します。
# === 実験②:パスキー(Face ID)が裏で行うデジタル署名実験 ===
import hashlib
# 1. サーバーから送られてきた「1回限りの挑戦状(ランダムな文字)」
challenge = "login_request_timestamp_20261103_xyz"
print(f"1. サーバーからの挑戦状: {challenge}")
# 2. あなたのスマホが「秘密鍵」を使って挑戦状にサイン(署名)する
# (※実験のためシンプルなハッシュと秘密鍵の結合でサインをシミュレート)
my_private_key = "MY_ULTRA_SECRET_KEY_999"
signature = hashlib.sha256((challenge + my_private_key).encode()).hexdigest()
print(f"2. スマホが作成した電子サイン: {signature[:20]}... (サインのみ送信)")
# 3. サーバー側が「事前にもらっていた公開鍵」でサインを検証する
# サーバーは「正しい秘密鍵で作られたサインか」を数式でチェックする
print("\n--- 🏢 サーバー側の検証 ---")
# ⭕ 正しいサインが届いた場合
expected_signature = hashlib.sha256((challenge + my_private_key).encode()).hexdigest()
if signature == expected_signature:
print("🎉 【検証成功】本物のスマホからのサインです!ログインを許可します!")
# ❌ 悪人がサインを偽造しようとした場合
fake_signature = hashlib.sha256((challenge + "FAKE_KEY").encode()).hexdigest()
if fake_signature != expected_signature:
print("🚨 【拒絶】サインが一致しません!不正アクセスをブロックしました!")
5. 現実世界での悪用と限界(次回への伏線)
公開鍵暗号とパスキーによって、私たちは「パスワードの漏洩」という数十年来の恐怖からようやく解放されつつあります。 しかし、ここで新たな疑問が生まれます。
「ログインするときはパスキーで本人確認できた。でも、次のページを開くたびに、毎回Face IDで認証し直さなければいけないのか?」
もちろん、そんな面倒なことはしていません。 一度ログインに成功すると、ブラウザは**「この人はたった今、鍵を開けた本人ですよ」という証明書(通行手形)**をこっそりポケットにしまいます。
この「Webサイトがログイン状態を覚えておくための合言葉のチケット」こそが、次回・第7回のテーマである**「Cookie(クッキー)とセッション」です。 実は、現代のWebサイトで最も狙われているのは、パスワードではなくこの「通行手形の横取り」**なのです。