密碼學 - Diffie-Hellman 金鑰交換演算法

在網際網路的世界裡,當兩個人(或兩台電腦)需要安全地通訊時,他們通常會使用加密技術。但問題來了:如果他們從未見過面,要如何商定一個只有他們兩人知道的加密金鑰,而又不讓竊聽者知道呢?這就是「Diffie-Hellman 金鑰交換」演算法要解決的核心問題。

Diffie-Hellman 演算法於 1976 年由 Whitfield Diffie 和 Martin Hellman 發表,是第一個實際應用的非對稱金鑰加密範例。它允許兩個從未謀面的通訊方,在一個完全公開、可能被竊聽的通訊渠道中,建立一個共享的秘密金鑰。

本文將探討 Diffie-Hellman 演算法的核心思想、運作原理,以及如何在 Python 中實作這個演算法。

核心思想:離散對數問題

這個演算法的安全性,建立在一個稱為「離散對數問題」(Discrete Logarithm Problem)的數學難題之上。

簡單來說,計算 Y ≡ g^x (mod p) 是相對容易的(其中 gxp 都是已知的數字)。但是,如果只知道結果 Y、以及 gp,想要從結果 Y、基底 gp,反推出祕密的次方數 x,在數學上是極度困難的,特別是當 p 是一個非常大的質數時。image size

運作原理

讓我們用一個經典的例子,假設有兩位通訊者,愛麗絲(Alice)和鮑伯(Bob),他們想要建立一個共享的秘密金鑰。

第 1 步:公開參數的約定

首先,愛麗絲和鮑伯需要公開地同意使用兩個數字:

  • 一個非常大的質數 p。(可以想成一個超大的時鐘)
  • 一個整數 g,稱為生成元(Generator)。(可以想成計算的起始數字)

這兩個數字 pg 是完全公開的,即使被竊聽者伊芙(Eve)知道也無妨。

第 2 步:各自生成私鑰

  • 愛麗絲秘密地選擇一個隨機整數 a,這個數字只有她自己知道,這就是她的私鑰。(心裡想的秘密次數)
  • 鮑伯也秘密地選擇一個隨機整數 b,這個數字只有他自己知道,這是他的私鑰

第 3 步:計算並交換公鑰

  • 愛麗絲計算她的公鑰 A
    A ≡ g^a (mod p)
  • 鮑伯計算他的公鑰 B
    B ≡ g^b (mod p)

接著,他們將計算出的公鑰 AB 透過公開渠道交換。現在,愛麗絲有鮑伯的公鑰 B,鮑伯有愛麗絲的公鑰 A,而竊聽者伊芙同時也可能取得了 AB

第 4 步:計算共享秘密

最神奇的一步來了。愛麗絲和鮑伯各自使用對方傳來的公鑰和自己的私鑰,進行計算:

  • 愛麗絲計算共享秘密 s:
    s = (B^a) mod p
  • 鮑伯計算共享秘密 s:
    s = (A^b) mod p

神奇的是,他們會得到完全相同的結果 s!

為什麼會一樣?

  • 愛麗絲的計算:她拿鮑伯的公鑰 B,再做 a 次方,最後除以 p 取餘數。這個過程的本質是 (g 的 b 次方) 的 a 次方,也就是 g 的 (b x a) 次方。
  • 鮑伯的計算:他拿愛麗絲的公鑰 A,再做 b 次方,最後除以 p 取餘數。這個過程的本質是 (g 的 a 次方) 的 b 次方,也就是 g 的 (a x b) 次方。

由於 g 的 (b x a) 次方和 g 的 (a x b) 次方,在除以 p 取完餘數後是相等的,所以他們成功地在不洩漏各自私鑰的情況下,得到了一個共享的秘密 s。

這個 s 就可以被用作後續對稱加密的密鑰。

Python 實作範例

下面的 Python 程式碼模擬了上述愛麗絲和鮑伯交換金鑰的完整過程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import random

def is_prime(n, k=5):
"""
使用 Miller-Rabin 素性檢定來判斷一個數是否為質數。
"""
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0:
return False

# 將 n-1 寫成 2^r * d 的形式
d = n - 1
r = 0
while d % 2 == 0:
d //= 2
r += 1

# 進行 k 次檢定
for _ in range(k):
a = random.randint(2, n - 2)
x = pow(a, d, n)
if x == 1 or x == n - 1:
continue
for _ in range(r - 1):
x = pow(x, 2, n)
if x == n - 1:
break
else:
return False
return True

def generate_large_prime(bits=64):
"""
生成一個指定位元數的大質數。
"""
while True:
p = random.getrandbits(bits)
# 確保是奇數且位元數正確
p |= (1 << bits - 1) | 1
if is_prime(p):
return p

# --- 1. 公開參數的約定 ---
# 在真實世界中,這些數字會非常大 (例如 2048 位元)
# 為了方便展示,我們使用較小的數字
# p 必須是一個質數
p = generate_large_prime(32)
# g 是一個生成元 (這裡我們簡單選擇一個小的數字)
g = 5

print(f"--- 公開參數 ---")
print(f"大質數 (p): {p}")
print(f"生成元 (g): {g}n")

# --- 2. 各自生成私鑰 ---
# 愛麗絲 (Alice) 選擇一個秘密數字 a
a = random.randint(2, p - 2)
print(f"愛麗絲的私鑰 (a): {a}")

# 鮑伯 (Bob) 選擇一個秘密數字 b
b = random.randint(2, p - 2)
print(f"鮑伯的私鑰 (b): {b}n")

# --- 3. 計算並交換公鑰 ---
# 愛麗絲計算她的公鑰 A 並公開
# A = (g 的 a 次方) 再除以 p 取餘數
A = pow(g, a, p)
print(f"愛麗絲的公鑰 (A),她會將此公開: {A}")

# 鮑伯計算他的公鑰 B 並公開
# B = (g 的 b 次方) 再除以 p 取餘數
B = pow(g, b, p)
print(f"鮑伯的公鑰 (B),他會將此公開: {B}n")

# --- 4. 計算共享秘密 ---
# 愛麗絲使用鮑伯的公鑰 B 和自己的私鑰 a 來計算共享秘密
# s_alice = (B 的 a 次方) 再除以 p 取餘數
s_alice = pow(B, a, p)
print(f"愛麗絲計算出的共享秘密 (s): {s_alice}")

# 鮑伯使用愛麗絲的公鑰 A 和自己的私鑰 b 來計算共享秘密
# s_bob = (A 的 b 次方) 再除以 p 取餘數
s_bob = pow(A, b, p)
print(f"鮑伯計算出的共享秘密 (s): {s_bob}n")

# --- 驗證 ---
if s_alice == s_bob:
print("✅ 成功!愛麗絲和鮑伯得到了相同的共享秘密。")
print(f"他們現在可以使用 {s_alice} 作為對稱加密的金鑰。")
else:
print("❌ 失敗!共享秘密不相同。")

安全性何在?

竊聽者伊芙雖然在公開渠道上取得了 pgAB,但她面臨的是離散對數難題。她無法輕易地從 A ≡ g^a (mod p) 這個公開資訊中,反推出愛麗絲的私鑰 a。同理,也無法從 B 反推出 b。沒有 ab,她就無法計算出最終的共享秘密 s

限制

值得注意的是,基礎的 Diffie-Hellman 演算法本身無法防禦「中間人攻擊」(Man-in-the-Middle Attack)。攻擊者可以冒充鮑伯與愛麗絲通訊,再冒充愛麗絲與鮑伯通訊,分別與他們建立不同的秘密金鑰。為了解決這個問題,在實際應用中,Diffie-Hellman 通常會與數位簽章等驗證機制結合使用,以確保通訊對象的身份是可信的。

參考文件

  • Wikipedia - Diffie-Hellman key exchange
  • AN OVERVIEW OF PUBLIC KEY CRYPTOGRAPHY

也許你也會想看看