0

Cấu trúc dữ liệu và Giải thuật Bài 148: Định lý Coppersmith (Coppersmith's Method / Small Roots of Polynomials) - Ứng dụng thuật toán LLL tấn công và phá vỡ các lỗ hổng của hệ mật RSA.

Trong lý thuyết số và mật mã học khóa công khai, bài toán tìm nghiệm của một phương trình đa thức modulo một hợp số tổng quát NN:

f(x)≡0(modN)f(x) \equiv 0 \pmod N

về bản chất là một bài toán NP-hard nếu không biết trước phép phân tích thừa số nguyên tố của N=p⋅qN = p \cdot q. Nếu ai đó có thể giải phương trình bậc hai x2≡a(modN)x^2 \equiv a \pmod N một cách hiệu quả, họ có thể phân tích thừa số NN ngay lập tức và phá vỡ hoàn toàn hệ mật RSA.

Tuy nhiên, vào năm 1996, Don Coppersmith đã công bố một định lý đột phá: Nếu đa thức f(x)f(x) bậc dd có một nghiệm nguyên "nhỏ" x0x_0 thỏa mãn ∣x0∣≤N1/d\vert{}x_0\vert{} \le N^{1/d}, thì nghiệm x0x_0 này có thể được tìm thấy chính xác trong thời gian đa thức.

Chìa khóa của phương pháp Coppersmith là biến đổi bài toán tìm nghiệm modulo NN thành bài toán tìm nghiệm trên vành số nguyên Z\mathbb{Z} thông qua cấu trúc lưới đa chiều và thuật toán giảm cơ sở lưới LLL (Bài 147). Phương pháp này đã trở thành công cụ nền tảng để phân tích độ an toàn và khai thác hàng loạt lỗ hổng chí mạng của hệ mật RSA.

1. Cơ sở Lý thuyết: Nghịch lý Tìm nghiệm Modulo và Chặn Coppersmith

Giả sử f(x)∈Z[x]f(x) \in \mathbb{Z}[x] là một đa thức đơn tử (monic polynomial - hệ số bậc cao nhất bằng 1) bậc dd:

f(x)=xd+ad−1xd−1+⋯+a1x+a0f(x) = x^d + a_{d-1}x^{d-1} + \dots + a_1 x + a_0

và NN là một số nguyên dương chưa biết phân tích thừa số.

Ta muốn tìm tất cả các nghiệm nguyên x0∈Zx_0 \in \mathbb{Z} sao cho:

f(x0)≡0(modN)vaˋ∣x0∣≤Xf(x_0) \equiv 0 \pmod N \quad \text{và} \quad \vert{}x_0\vert{} \le X

  • Nếu X≈NX \approx N, số lượng nghiệm có thể rất lớn và việc tìm kiếm tương đương với việc phân tích NN.

  • Định lý Coppersmith: Tồn tại thuật toán thời gian đa thức tìm được mọi nghiệm x0x_0 thỏa mãn ∣x0∣≤X\vert{}x_0\vert{} \le X với cận trên lý thuyết:

    X=N1/d−ϵX = N^{1/d - \epsilon}

    với độ phức tạp đa thức theo (d,1/ϵ,log⁡N)(d, 1/\epsilon, \log N).

2. Bổ đề Howgrave-Graham: Cầu nối từ ZN\mathbb{Z}_N sang Z\mathbb{Z}

Năm 1997, Nick Howgrave-Graham đã đơn giản hóa và hình thức hóa chứng minh ban đầu của Coppersmith bằng một bổ đề đại số trực quan.

Cho đa thức g(x)=∑i=0ncixi∈Z[x]g(x) = \sum_{i=0}^n c_i x^i \in \mathbb{Z}[x]. Định nghĩa chuẩn Euclidean của đa thức g(xX)g(x X) là:

∥g(xX)∥=∑i=0n(ciXi)2\Vert{}g(x X)\Vert{} = \sqrt{\sum_{i=0}^n (c_i X^i)^2}

Phát biểu Bổ đề Howgrave-Graham

Bổ đề: Cho g(x)∈Z[x]g(x) \in \mathbb{Z}[x] là đa thức có bậc không vượt quá nn. Giả sử tồn tại các số nguyên dương mm và XX sao cho:

  1. g(x0)≡0(modNm)g(x_0) \equiv 0 \pmod{N^m} với một số nguyên x0x_0 thỏa mãn ∣x0∣≤X\vert{}x_0\vert{} \le X.

  2. Chuẩn của đa thức thỏa mãn điều kiện nhỏ:

    ∥g(xX)∥<Nmn+1\Vert{}g(x X)\Vert{} < \frac{N^m}{\sqrt{n + 1}}

    Khi đó, x0x_0 là một nghiệm của g(x)g(x) chính xác trên vành số nguyên Z\mathbb{Z}, tức là:

    g(x0)=0g(x_0) = 0

Phác thảo chứng minh:

Áp dụng bất đẳng thức Cauchy-Schwarz:

∣g(x0)∣=∣∑i=0ncix0i∣≤∑i=0n∣ci∣Xi≤n+1⋅∥g(xX)∥\vert{}g(x_0)\vert{} = \left\vert{} \sum_{i=0}^n c_i x_0^i \right\vert{} \le \sum_{i=0}^n \vert{}c_i\vert{} X^i \le \sqrt{n + 1} \cdot \Vert{}g(x X)\Vert{}

Từ giả thiết ∥g(xX)∥<Nmn+1\Vert{}g(x X)\Vert{} < \frac{N^m}{\sqrt{n+1}}, ta suy ra:

∣g(x0)∣<n+1⋅Nmn+1=Nm\vert{}g(x_0)\vert{} < \sqrt{n + 1} \cdot \frac{N^m}{\sqrt{n + 1}} = N^m

Mặt khác, theo giả thiết 1, g(x0)g(x_0) là một bội số nguyên của NmN^m, nghĩa là g(x0)=k⋅Nmg(x_0) = k \cdot N^m với k∈Zk \in \mathbb{Z}.

Số nguyên duy nhất chia hết cho NmN^m mà có giá trị tuyệt đối strictly nhỏ hơn NmN^m chỉ có thể là 00 (k=0k = 0).

Do đó, g(x0)=0g(x_0) = 0 tuyệt đối trên Z\mathbb{Z}.

Ý nghĩa cốt tử: Tìm nghiệm của một đa thức trên Z\mathbb{Z} là bài toán dễ (dùng giải tích số, phương pháp chia đôi, hoặc thuật toán phân tích đa thức của Berlekamp / Cantor-Zassenhaus trong thời gian đa thức). Bổ đề Howgrave-Graham chỉ ra rằng: Chỉ cần ta tìm được một đa thức g(x)g(x) có hệ số đủ nhỏ, bài toán modulo phức tạp sẽ lập tức được giải quyết.

3. Thiết lập Không gian Lưới và Tận dụng Thuật toán LLL

Làm thế nào để tìm ra đa thức g(x)g(x) thỏa mãn đồng thời hai điều kiện của Howgrave-Graham? Câu trả lời là: Dựng một lưới chứa các đa thức nhận x0x_0 làm nghiệm modulo NmN^m, sau đó dùng thuật toán LLL để tìm vector ngắn nhất.

Xây dựng Họ Đa thức Phụ trợ

Với đa thức gốc f(x)f(x) bậc dd thỏa mãn f(x0)≡0(modN)f(x_0) \equiv 0 \pmod N:

  1. f(x0)k≡0(modNk)f(x_0)^k \equiv 0 \pmod{N^k}.

  2. Để thu được các đa thức có nghiệm modulo NmN^m, ta xét hệ đa thức:

    gi,k(x)=xi⋅Nm−k⋅f(x)k(0≤k≤m,  0≤i<d)g_{i, k}(x) = x^i \cdot N^{m - k} \cdot f(x)^k \quad (0 \le k \le m, \; 0 \le i < d)

    Mọi đa thức này đều có tính chất: gi,k(x0)g_{i, k}(x_0) chia hết cho NmN^m.

  3. Bất kỳ tổ hợp tuyến tính nguyên nào của các đa thức này:

    h(x)=∑ai,kgi,k(x)(ai,k∈Z)h(x) = \sum a_{i, k} g_{i, k}(x) \quad (a_{i, k} \in \mathbb{Z})

    cũng đều thỏa mãn h(x0)≡0(modNm)h(x_0) \equiv 0 \pmod{N^m}.

Áp dụng LLL để tìm Vector Ngắn

Mỗi đa thức p(x)=∑cjxjp(x) = \sum c_j x^j được ánh xạ thành một vector hệ số có trọng số:

vp=(c0,c1X,c2X2,…,cnXn)  ⟹  ∥vp∥=∥p(xX)∥v_p = (c_0, c_1 X, c_2 X^2, \dots, c_n X^n) \implies \Vert{}v_p\Vert{} = \Vert{}p(x X)\Vert{}

Tập hợp tất cả các vector vgi,kv_{g_{i, k}} tạo thành một hệ cơ sở của một lưới Λ\Lambda.

  • Kích thước ma trận cơ sở là M×MM \times M với M=m⋅dM = m \cdot d.

  • Ma trận cơ sở có dạng tam giác dưới, cho phép tính định thức lưới det⁡(Λ)\det(\Lambda) bằng tích các phần tử trên đường chéo chính.

  • Theo tính chất của thuật toán LLL (Bài 147), vector ngắn nhất b1b_1 tìm được thỏa mãn:

    ∥b1∥≤2(M−1)/4⋅(det⁡(Λ))1/M\Vert{}b_1\Vert{} \le 2^{(M-1)/4} \cdot (\det(\Lambda))^{1/M}

  • Đặt điều kiện để ∥b1∥\Vert{}b_1\Vert{} thỏa mãn chặn Howgrave-Graham ∥b1∥<NmM\Vert{}b_1\Vert{} < \frac{N^m}{\sqrt{M}}, giải bất phương trình theo XX khi m→∞m \to \infty, ta thu được chặn tối ưu của Coppersmith:

    X<N1/dX < N^{1/d}

   Đa thức f(x) mod N                  Hệ đa thức g_{i, k}(x) mod N^m
   (Khó tìm nghiệm)      =======>      (Không gian Lưới đa chiều Λ)
                                                    ||
                                                    || Thuật toán LLL
                                                    v
   Nghiệm x0 trên Z      <=======      Vector ngắn nhất b1
   (Giải bằng Newton)                  (Thỏa mãn chuẩn Howgrave-Graham)

4. Hiện thực hóa Cuộc tấn công Coppersmith bằng Golang

Xét trường hợp kinh điển nhất trong phân tích RSA: Stereotyped Message Attack với số mũ công khai nhỏ e=3e = 3.

  • Người gửi mã hóa bản tin: C≡(M+x0)3(modN)C \equiv (M + x_0)^3 \pmod N.

  • Kẻ tấn công biết cấu trúc tiền tố/hậu tố MM (bản tin mẫu dạng "User_ID_9999_Pass="), chỉ thiếu một phần nhỏ x0x_0 ở cuối.

  • Đa thức cần tìm nghiệm nhỏ: f(x)=(M+x)3−C≡0(modN)f(x) = (M + x)^3 - C \equiv 0 \pmod N.

  • Bậc d=3d = 3. Chặn nghiệm: ∣x0∣≤X≈N1/3\vert{}x_0\vert{} \le X \approx N^{1/3}.

Chương trình Golang dưới đây xây dựng lưới bậc 44 (m=1m=1) với hệ cơ sở:

g0(x)=N,g1(x)=N⋅x,g2(x)=N⋅x2,g3(x)=f(x)g_0(x) = N, \quad g_1(x) = N \cdot x, \quad g_2(x) = N \cdot x^2, \quad g_3(x) = f(x)

Áp dụng LLL để tìm đa thức h(x)h(x) có nghiệm trên Z\mathbb{Z}, sau đó giải nghiệm nguyên bằng phương pháp tìm kiếm nhị phân (Bisection/Integer Newton):

Go

package main

import (
	"fmt"
	"math"
	"math/big"
)

// Cấu trúc LLL đơn giản hóa cho lưới số thực/nguyên
type LLLSolver struct {
	d     int
	basis [][]float64
	delta float64
}

func NewLLLSolver(basis [][]float64, delta float64) *LLLSolver {
	d := len(basis)
	cp := make([][]float64, d)
	for i := range basis {
		cp[i] = make([]float64, len(basis[i]))
		copy(cp[i], basis[i])
	}
	return &LLLSolver{d: d, basis: cp, delta: delta}
}

func dot(u, v []float64) float64 {
	var s float64
	for i := range u {
		s += u[i] * v[i]
	}
	return s
}

func (l *LLLSolver) Reduce() [][]float64 {
	n := len(l.basis[0])
	bStar := make([][]float64, l.d)
	mu := make([][]float64, l.d)
	gamma := make([]float64, l.d)
	for i := 0; i < l.d; i++ {
		bStar[i] = make([]float64, n)
		mu[i] = make([]float64, l.d)
	}

	updateGSO := func() {
		for i := 0; i < l.d; i++ {
			copy(bStar[i], l.basis[i])
			for j := 0; j < i; j++ {
				if gamma[j] > 1e-11 {
					mu[i][j] = dot(l.basis[i], bStar[j]) / gamma[j]
				} else {
					mu[i][j] = 0
				}
				for k := 0; k < n; k++ {
					bStar[i][k] -= mu[i][j] * bStar[j][k]
				}
			}
			mu[i][i] = 1.0
			gamma[i] = dot(bStar[i], bStar[i])
		}
	}

	updateGSO()
	k := 1
	for k < l.d {
		// Size reduction
		for j := k - 1; j >= 0; j-- {
			if math.Abs(mu[k][j]) > 0.5 {
				q := math.Round(mu[k][j])
				for i := 0; i < n; i++ {
					l.basis[k][i] -= q * l.basis[j][i]
				}
				updateGSO()
			}
		}

		// Lovasz condition
		if gamma[k] < (l.delta-mu[k][k-1]*mu[k][k-1])*gamma[k-1] {
			l.basis[k], l.basis[k-1] = l.basis[k-1], l.basis[k]
			updateGSO()
			if k > 1 {
				k--
			}
		} else {
			k++
		}
	}
	return l.basis
}

// Đánh giá giá trị đa thức trên Z: h(x) = c3*x^3 + c2*x^2 + c1*x + c0
func evalPoly(coeffs []float64, x int64) float64 {
	var res float64 = 0
	p := 1.0
	for _, c := range coeffs {
		res += c * p
		p *= float64(x)
	}
	return res
}

// Tìm nghiệm nguyên x0 trong đoạn [0, X]
func findIntegerRoot(coeffs []float64, X int64) (int64, bool) {
	// Kiểm tra các giá trị lân cận hoặc dùng tìm kiếm tuyến tính/nhị phân
	for x := int64(0); x <= X; x++ {
		val := evalPoly(coeffs, x)
		if math.Abs(val) < 1e-3 {
			return x, true
		}
	}
	return -1, false
}

func main() {
	// Mô phỏng RSA với e = 3
	// Hai số nguyên tố p, q
	p := big.NewInt(1000003)
	q := big.NewInt(1000033)
	N := new(big.Int).Mul(p, q) // N ≈ 10^12

	// Bản tin M đã biết một phần, phần bí mật nhỏ x0 cần khôi phục
	// Giả sử m = Base + x0
	baseMsg := big.NewInt(500000)
	secretX := int64(1234) // Nghiệm bí mật nhỏ cần tìm (|x0| < N^(1/3) ≈ 10000)
	actualM := new(big.Int).Add(baseMsg, big.NewInt(secretX))

	// Bản mã C = actualM^3 mod N
	e := int64(3)
	C := new(big.Int).Exp(actualM, big.NewInt(e), N)

	// Đa thức f(x) = (baseMsg + x)^3 - C (mod N)
	// Khai triển: x^3 + 3*baseMsg*x^2 + 3*baseMsg^2*x + (baseMsg^3 - C)
	three := big.NewInt(3)
	base2 := new(big.Int).Mul(baseMsg, baseMsg)
	base3 := new(big.Int).Mul(base2, baseMsg)

	a3 := big.NewInt(1)
	a2 := new(big.Int).Mul(three, baseMsg)
	a1 := new(big.Int).Mul(three, base2)
	a0 := new(big.Int).Sub(base3, C)

	a2.Mod(a2, N)
	a1.Mod(a1, N)
	a0.Mod(a0, N)

	// Chặn trên của nghiệm: X <= N^(1/3)
	nFloat, _ := new(big.Float).SetInt(N).Float64()
	X := int64(math.Pow(nFloat, 1.0/3.0))

	fmt.Printf("=== MÔ PHỎNG TẤN CÔNG COPPERSMITH RSA (e = 3) ===\n")
	fmt.Printf("Modulus N = %s\n", N.String())
	fmt.Printf("Khóa công khai e = %d\n", e)
	fmt.Printf("Chặn nghiệm tìm kiếm X = N^(1/3) = %d\n", X)
	fmt.Printf("Nghiệm bí mật thực tế cần tìm: x0 = %d\n\n", secretX)

	// Dựng ma trận lưới Coppersmith (Bậc 4):
	// g0(x) = N
	// g1(x) = N * x
	// g2(x) = N * x^2
	// g3(x) = f(x) = x^3 + a2*x^2 + a1*x + a0
	// Vector biểu diễn: (c0, c1*X, c2*X^2, c3*X^3)
	xFloat := float64(X)
	x2 := xFloat * xFloat
	x3 := x2 * xFloat

	a0F, _ := new(big.Float).SetInt(a0).Float64()
	a1F, _ := new(big.Float).SetInt(a1).Float64()
	a2F, _ := new(big.Float).SetInt(a2).Float64()

	basis := [][]float64{
		{nFloat, 0, 0, 0},
		{0, nFloat * xFloat, 0, 0},
		{0, 0, nFloat * x2, 0},
		{a0F, a1F * xFloat, a2F * x2, 1.0 * x3},
	}

	// Chạy LLL để tìm vector ngắn nhất
	lll := NewLLLSolver(basis, 0.75)
	reduced := lll.Reduce()

	// Vector ngắn nhất là hàng đầu tiên của ma trận đã giảm
	shortestVec := reduced[0]

	// Khôi phục lại các hệ số thực của đa thức h(x) trên Z bằng cách chia lại cho X^i
	polyCoeffs := []float64{
		shortestVec[0],
		shortestVec[1] / xFloat,
		shortestVec[2] / x2,
		shortestVec[3] / x3,
	}

	fmt.Println("Đa thức ngắn nhất h(x) thu được từ LLL:")
	fmt.Printf("h(x) = (%.2e)*x^3 + (%.2e)*x^2 + (%.2e)*x + (%.2e)\n",
		polyCoeffs[3], polyCoeffs[2], polyCoeffs[1], polyCoeffs[0])

	// Tìm nghiệm nguyên của h(x) trên Z
	recoveredRoot, found := findIntegerRoot(polyCoeffs, X)
	if found {
		fmt.Printf("\n[THÀNH CÔNG] Đã khôi phục chính xác nghiệm bí mật: x0 = %d\n", recoveredRoot)
		recoveredMsg := new(big.Int).Add(baseMsg, big.NewInt(recoveredRoot))
		fmt.Printf("Bản rõ hoàn chỉnh đã giải mã: %s\n", recoveredMsg.String())
	} else {
		fmt.Println("\n[THẤT BẠI] Không tìm thấy nghiệm trong phạm vi!")
	}
}

5. Phân tích Độ phức tạp và Ranh giới Ngưỡng Chặn

Tham số Giá trị
Bậc đa thức gốc (dd) d=deg⁡(f)d = \deg(f)
Bậc lũy thừa lưới (mm) Tham số điều chỉnh độ chính xác (thường chọn m≈1dϵm \approx \frac{1}{d \epsilon})
Số chiều của lưới (MM) M=d⋅mM = d \cdot m
Độ phức tạp thuật toán LLL O(M6log⁡3N)O(M^6 \log^3 N) (sử dụng phép tính số nguyên lớn)
Thời gian giải nghiệm trên Z\mathbb{Z} O(M2)O(M^2) hoặc O(Mlog⁡2M)O(M \log^2 M)
Tổng thời gian toàn bộ pipeline Thời gian đa thức: O(poly(d,1/ϵ,log⁡N))O(\text{poly}(d, 1/\epsilon, \log N))

So sánh các Biến thể của Định lý Coppersmith

Bài toán Điều kiện nghiệm Kỹ thuật sử dụng Chặn kích thước nghiệm
Đa thức một biến modulo NN f(x0)≡0(modN)f(x_0) \equiv 0 \pmod N Coppersmith tiêu chuẩn $
Đa thức một biến modulo ước pp f(x0)≡0(modp)f(x_0) \equiv 0 \pmod p ($p N, p \ge N^\beta$) Coppersmith-Howgrave-Graham
Đa thức hai biến trên Z\mathbb{Z} f(x0,y0)=0f(x_0, y_0) = 0 Bivariate Coppersmith Cắt tỉa và tính hợp tử (Resultant)

6. Các Kịch bản Tấn công RSA Kinh điển bằng Coppersmith

  • Stereotyped Messages Attack (Bản tin rập khuôn): Khi người gửi mã hóa bản tin có định dạng cố định (chẳng hạn header giao thức chuẩn, timestamp hoặc nonce ngắn ở cuối), nếu số mũ ee nhỏ (ví dụ e=3e = 3), kẻ tấn công chỉ cần biết phần lớn bản tin và dùng Coppersmith giải mã phần còn lại mà không cần phân tích khóa NN.

  • Tấn công Franklin-Reiter Related Message: Nếu hai bản tin có mối liên hệ tuyến tính M2=a⋅M1+b(modN)M_2 = a \cdot M_1 + b \pmod N được mã hóa bởi cùng khóa công khai e=3e = 3, định lý Coppersmith cho phép khôi phục trọn vẹn cả hai bản rõ trong chớp mắt.

  • Tấn công Khóa bí mật nhỏ Boneh-Durfee (d<N0.292d < N^{0.292}): Từ phương trình khóa RSA e⋅d−k⋅ϕ(N)=1e \cdot d - k \cdot \phi(N) = 1, Dan Boneh và Glenn Durfee (1999) đã mô hình hóa thành đa thức hai biến f(k,x)f(k, x) modulo ee và dùng Coppersmith chứng minh rằng: Mọi khóa bí mật dd có kích thước d<N0.292d < N^{0.292} đều có thể bị bẻ khóa hoàn toàn trong thời gian đa thức. (Cải tiến vượt bậc so với chặn Wiener d<13N1/4d < \frac{1}{3} N^{1/4}).

  • Phân tích NN khi biết một phần số bit của pp (Partial Key Exposure / Factoring with Known Bits): Nếu kẻ tấn công thu thập được 1/41/4 số bit cao nhất (hoặc thấp nhất) của thừa số nguyên tố pp (thông qua tấn công kênh kề Side-Channel Attacks hoặc lỗi rò rỉ bộ nhớ Heartbleed), đặt p=P0+x0p = P_0 + x_0, đa thức f(x)=P0+xf(x) = P_0 + x modulo pp sẽ tìm ra x0x_0 và phân tích thành công NN.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.