在 Day 28 中,我們鋪設好了抽象代數的舞台——有限域 GF(p) 與 群論(Group Theory)。我們知道了密碼學需要在無捨入誤差、封閉且滿足算術公理的離散空間中進行運算。
本章將正式讓舞者登場:我們將在有限域 GF(p) 上繪製出橢圓曲線(Elliptic Curve),學習如何在曲線上定義幾何「點加法」與「點倍加」,並徹底推導出現代非對稱密碼學的核心運算公式——Q = kG(公開金鑰 = 私密純量 X 基準點)。
在密碼學中,最常用的是短 Weierstrass 方程式(Short Weierstrass Form):
y^2 ≡ x^3 + ax + b (mod p)
其中 a, b ∈ GF(p) 為決定曲線形狀的公開參數,p 為定義有限域的大質數。
為確保曲線平滑且沒有交叉點或尖點(Cusp),參數必須滿足以下判別式限制:
4a^3 + 27b^2 ≠ 0 (mod p)
此條件保證了方程式的右半部 x^3 + ax + b = 0 沒有重根,進而確保曲線上每一點都能繪製出唯一的切線。
我們在曲線上定義一種二元運算 "+"(點加法)。注意:這不是將點的 x, y 座標直接相加,而是基於「割線與切線」的幾何交點所導出的同餘運算。

P + O = P, P + (-P) = O
給定兩點 P = (x_1, y_1) 與 Q = (x_2, y_2):
λ ≡ \frac{y_2 - y_1}{x_2 - x_1} ≡ (y_2 - y_1)(x_2 - x_1)^{-1} (mod p)
x_3 ≡ λ^2 - x_1 - x_2 (mod p)
y_3 ≡ λ(x_1 - x_3) - y_1 (mod p)
λ ≡ \frac{3x_1^2 + a}{2y_1} ≡ (3x_1^2 + a)(2y_1)^{-1} (mod p)
x_3 ≡ λ^2 - 2x_1 (mod p)
y_3 ≡ λ(x_1 - x_3) - y_1 (mod p)
設有限域 GF(17),選定橢圓曲線:
y^2 ≡ x^3 + 2x + 2 (mod 17 ) , (a = 2, b = 2, p = 17)
驗證點 P = (5, 1) 與 Q = (6, 3) 是否在曲線上:
求解 R = P + Q = (x_3, y_3):
λ ≡ (3 - 1) * (6 - 5)^{-1} ≡ 2 * 1^{-1} ≡ 2 (mod 17 )
x_3 ≡ λ^2 - x_1 - x_2 ≡ 2^2 - 5 - 6 = 4 - 11 = -7 ≡ 10 (mod 17 )
y_3 ≡ λ(x_1 - x_3) - y_1 ≡ 2(5 - 10) - 1 = 2(-5) - 1 = -11 ≡ 6 (mod 17 )
推導結果:P(5,1) + Q(6,3) = R(10, 6) (mod 17 )。
有了點加法與點倍加,我們就可以定義 ECC 中最重要的單向函數——純量乘法(Scalar Multiplication):
Q = kG = G + G + ... + G (k 次)
其中:
若逐次累加 G 共 k 次,演算法複雜度為 O(k)。當 k 為 256-bit 大數時(約 10^{77}),古典電腦耗盡宇宙壽命也無法算完。
工程上採用 Double-and-Add 演算法,將 k 轉為二進位展開,時間複雜度降至 O(log k)(最多只需 256 次點倍加與 256 次點加法):
以 k = 13_{10} = 1101_2 為例:
13G = (1 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0)G = 8G + 4G + G
[ Double-and-Add 運算軌跡 (k = 13 = 1101₂) ]
Bit 3 (1) ──> 初始點 G
Bit 2 (1) ──> Double(2G) ──> Add(2G + G = 3G)
Bit 1 (0) ──> Double(6G) ──> (Bit為0, 不執行Add)
Bit 0 (1) ──> Double(12G) ──> Add(12G + G = 13G) ──> 最終結果 Q
資安警示(時序側信道攻擊):上述標準 Double-and-Add 中,
Add運算僅在位元為1時執行。攻擊者可透過監測 CPU 執行時間或功耗波動(Power Analysis)反推 k 的位元值。生產級密碼庫(如 OpenSSL、Bouncy Castle)必須採用常數時間(Constant-Time) 的 Montgomery Ladder 演算法。
在標準 Java JCA 中,我們可以使用原生 KeyPairGenerator 指定 NIST 標準曲線 secp256r1(即 P-256),生成 ECC 金鑰對並導出其數學參數:
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import java.util.Arrays;
public class EccKeyPairProductionExample {
private static final String EC_ALGORITHM = "EC";
private static final String CURVE_NAME = "secp256r1"; // NIST P-256 (256-bit)
public static void main(String[] args) throws Exception {
System.out.println("正在生成 ECC KeyPair (曲線規格: " + CURVE_NAME + ")...");
// 1. 初始化 EC 金鑰產生器
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(EC_ALGORITHM);
ECGenParameterSpec ecSpec = new ECGenParameterSpec(CURVE_NAME);
keyPairGen.initialize(ecSpec);
// 2. 執行純量乘法 Q = k * G
KeyPair keyPair = keyPairGen.generateKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privKey = keyPair.getPrivate();
// 3. 提取 ECC 公鑰點座標 Q = (x, y)
if (pubKey instanceof ECPublicKey ecPubKey) {
ECPoint qPoint = ecPubKey.getW();
System.out.println("\n--- ECC 公鑰 (Public Point Q) ---");
System.out.println("X 座標 (Hex): " + qPoint.getAffineX().toString(16));
System.out.println("Y 座標 (Hex): " + qPoint.getAffineY().toString(16));
}
// 4. 提取 ECC 私鑰純量 k
if (privKey instanceof ECPrivateKey ecPrivKey) {
System.out.println("\n--- ECC 私鑰 (Private Scalar k) ---");
System.out.println("私密純量 S (Bit Length): " + ecPrivKey.getS().bitLength() + " bits");
System.out.println("私鑰參數 S 必須嚴密保管,嚴禁洩露或寫入 Log 中!");
}
System.out.println("\n 生產級 ECC KeyPair 生成成功!");
}
}