Pohlig-Hellman算法解决DLP问题及Python实现
I n s t r u c t i o n Instruction Instruction
Pohlig-Hellman算法适用于求解特定条件下的DLP问题(离散对数问题),以数学表达该问题即是
a
x
≡
b
(
m
o
d
p
)
a^x\equiv b\pmod p
ax≡b(modp)
其中
x
x
x即是所需要求得的数,已知
a
,
b
,
p
a,b,p
a,b,p;该算法应用的特定条件是指
p
−
1
p-1
p−1是光滑数(光滑数即是该数的质因数都很小,其每个质因数大小在该算法中对应着算法复杂度)
该算法的原理及运行过程如下,需要假设其模数 p − 1 p-1 p−1是光滑数
假设模
p
p
p的最小本原元为
g
g
g,那么可以用本原元来表示在模
p
p
p意义下的
a
a
a和
b
b
b
a
≡
g
u
(
m
o
d
p
)
b
≡
g
v
(
m
o
d
p
)
a\equiv g^{u} \pmod p\\ b\equiv g^{v} \pmod p
a≡gu(modp)b≡gv(modp)
代回到初始问题上得到
a
x
≡
b
(
m
o
d
p
)
⇒
g
u
x
≡
g
v
(
m
o
d
p
)
a^x\equiv b\pmod p \\ \Rightarrow g^{ux} \equiv g^{v} \pmod p
ax≡b(modp)⇒gux≡gv(modp)
从上式根据数论定理得到
u
x
≡
v
(
m
o
d
p
−
1
)
ux\equiv v\pmod {p-1}
ux≡v(modp−1)
这样我们可以用
u
,
v
u,v
u,v来表示出
x
x
x
x
≡
v
⋅
u
−
1
(
m
o
d
p
−
1
)
x \equiv v\cdot u^{-1} \pmod {p-1}
x≡v⋅u−1(modp−1)
到这里我们将问题转换到了如何得到
v
,
u
v,u
v,u的大小
回到用本原元表示 a , b a,b a,b的同余式上,
- 以解 g u ≡ a ( m o d p ) g^u \equiv a\pmod p gu≡a(modp)为例,
我们已知
a
,
g
,
p
a,g,p
a,g,p;根据算法做出的假设,
p
−
1
p-1
p−1可以被多个小质因数分解,那么
p
−
1
=
p
1
q
1
⋅
p
2
q
2
⋅
p
3
q
3
⋯
p
n
q
n
p-1 = p_1^{q_1} \cdot p_2 ^{q_2}\cdot p_3 ^{q_3} \cdots p_n^{q_n}
p−1=p1q1⋅p2q2⋅p3q3⋯pnqn
其中
q
i
q_i
qi表示的是质因数的指数;我们将目标
u
u
u用每个素因子的大小作为进制表示,如以
p
1
p_1
p1进制表示(进制表示如果不熟悉的话可以参照二进制的表示方式)
u
=
c
0
+
c
1
p
1
+
c
2
p
1
2
+
c
3
p
1
3
+
⋯
+
c
q
1
−
1
p
1
q
1
−
1
+
⋯
+
c
k
p
1
k
u=c_0+c_1p_1+c_2p_1^2+c_3p_1^3+\dots + c_{q_1-1}p_{1}^{q_1-1} + \cdots + c_{k} p_1^{k}
u=c0+c1p1+c2p12+c3p13+⋯+cq1−1p1q1−1+⋯+ckp1k
其中
c
i
c_i
ci均小于
p
1
p_1
p1,变量
k
k
k意味着可以到无限大;同理我们也将
u
u
u以
p
2
,
p
3
,
⋯
,
p
n
p_2,p_3,\cdots,p_n
p2,p3,⋯,pn进制表示,这样就得到
n
n
n个的等式;但是这样的进制表示并没有实质作用,因为我们很可能为了表示
u
u
u,而将
k
k
k拉到很大的数值,具体大小我们无从得知;所以我们用取模来解决这个问题,将等式两侧的大小变到我们能掌握的大小
我们在每个等式中取模
p
i
q
i
{p_i^{q_i}}
piqi,将其转换为同余式,以
p
1
p_1
p1进制为例
u
≡
c
0
+
c
1
p
1
+
c
2
p
1
2
+
c
3
p
1
3
+
⋯
+
c
q
1
−
1
p
1
q
1
−
1
(
m
o
d
p
1
q
1
)
u\equiv c_0+c_1p_1+c_2p_1^2+c_3p_1^3+\dots + c_{q_1-1}p_{1}^{q_1-1}\pmod {p_1^{q_1}}
u≡c0+c1p1+c2p12+c3p13+⋯+cq1−1p1q1−1(modp1q1)
那么将原来的
n
n
n个等式转换为
n
n
n个同余式后,联立起来得到同余式组
{
u
≡
c
0
+
c
1
p
1
+
c
2
p
1
2
+
c
3
p
1
3
+
⋯
+
c
q
1
−
1
p
1
q
1
−
1
(
m
o
d
p
1
q
1
)
u
≡
c
0
′
+
c
1
′
p
2
+
c
2
′
p
2
2
+
c
3
′
p
2
3
+
⋯
+
c
q
2
−
1
′
p
2
q
2
−
1
(
m
o
d
p
2
q
2
)
u
≡
c
0
′
′
+
c
1
′
′
p
3
+
c
2
′
′
p
3
2
+
c
3
′
′
p
3
3
+
⋯
+
c
q
3
−
1
′
′
p
3
q
3
−
1
(
m
o
d
p
3
q
3
)
⋯
u
≡
c
0
′
′
′
+
c
1
′
′
′
p
n
+
c
2
′
′
′
p
n
2
+
c
3
′
′
′
p
n
3
+
⋯
+
c
q
n
−
1
′
′
′
p
n
q
n
−
1
(
m
o
d
p
n
q
n
)
\left\{ \begin{aligned} & u\equiv c_0+c_1p_1+c_2p_1^2+c_3p_1^3+\dots + c_{q_1-1}p_{1}^{q_1-1} \pmod {p_1^{q_1}} \\ & u\equiv c_0'+c_1'p_2+c_2'p_2^2+c_3'p_2^3+\dots + c_{q_2-1}'p_2^{q_2-1}\pmod {p_2^{q_2}} \\ & u\equiv c_0''+c_1''p_3+c_2''p_3^2+c_3''p_3^3+\dots + c_{q_3-1}''p_3^{q_3-1}\pmod {p_3^{q_3}} \\ & \cdots \\ & u\equiv c_0'''+c_1'''p_n+c_2'''p_n^2+c_3'''p_n^3+\dots + c_{q_n-1}'''p_n^{q_n-1} \pmod {p_n^{q_n}} \end{aligned} \right.
⎩
⎨
⎧u≡c0+c1p1+c2p12+c3p13+⋯+cq1−1p1q1−1(modp1q1)u≡c0′+c1′p2+c2′p22+c3′p23+⋯+cq2−1′p2q2−1(modp2q2)u≡c0′′+c1′′p3+c2′′p32+c3′′p33+⋯+cq3−1′′p3q3−1(modp3q3)⋯u≡c0′′′+c1′′′pn+c2′′′pn2+c3′′′pn3+⋯+cqn−1′′′pnqn−1(modpnqn)
分别对每个同余式求解
c
i
c_i
ci得到满足单个同余式的
u
u
u,再对整个同余式组使用中国剩余定理得到最终满足所有同余式的解
u
u
u
- 以解 u ≡ c 0 + c 1 p 1 + c 2 p 1 2 + c 3 p 1 3 + ⋯ + c q 1 − 1 p 1 q 1 − 1 ( m o d p 1 q 1 ) u\equiv c_0+c_1p_1+c_2p_1^2+c_3p_1^3+\dots + c_{q_1-1}p_{1}^{q_1-1} \pmod {p_1^{q_1}} u≡c0+c1p1+c2p12+c3p13+⋯+cq1−1p1q1−1(modp1q1)为例,
接下来为了将
p
1
p_1
p1进制表示的
u
u
u应用于求解过程,我们构造
(
g
u
)
p
−
1
p
1
r
≡
a
p
−
1
p
1
r
(
m
o
d
p
)
(g^u)^{\frac{p-1}{p_1^r}}\equiv a^{\frac{p-1}{p_1^r}} \pmod p
(gu)p1rp−1≡ap1rp−1(modp)
其中
r
r
r是我们设置的变量,后面会对其进行赋值;该式的模数应该也可以换作这里的
p
1
q
1
p_1^{q_1}
p1q1或者是其他满足条件的数
将
u
u
u替换成
p
1
p_1
p1进制表示,得到
(
g
c
0
+
c
1
p
1
+
c
2
p
1
2
+
c
3
p
1
3
+
⋯
+
c
q
1
−
1
p
p
1
q
1
−
1
)
p
−
1
p
1
r
≡
a
p
−
1
p
1
r
(
m
o
d
p
)
(g^{c_0+c_1p_1+c_2p_1^2+c_3p_1^3+\dots + c_{q_1-1}p_{p_1}^{q_1-1}})^{\frac{p-1}{p_1^r}}\equiv a^{\frac{p-1}{p_1^r}} \pmod p \\
(gc0+c1p1+c2p12+c3p13+⋯+cq1−1pp1q1−1)p1rp−1≡ap1rp−1(modp)
我们令
r
r
r为
1
1
1,得到
g
c
0
p
−
1
p
1
⋅
g
c
1
(
p
−
1
)
⋅
g
c
2
(
p
−
1
)
p
1
⋯
g
c
q
1
−
1
(
p
−
1
)
p
1
q
1
−
2
≡
a
p
−
1
p
1
(
m
o
d
p
)
g^{c_0\frac{p-1}{p_1}} \cdot g^{c_1(p-1)}\cdot g^{c_2(p-1)p_1} \cdots g^{c_{q_1-1}(p-1)p_1^{q_1-2}} \equiv a^{\frac{p-1}{p_1}} \pmod p
gc0p1p−1⋅gc1(p−1)⋅gc2(p−1)p1⋯gcq1−1(p−1)p1q1−2≡ap1p−1(modp)
由费马小定理
g
p
−
1
≡
1
(
m
o
d
p
)
g^{p-1}\equiv 1 \pmod p
gp−1≡1(modp),上式等价于
g
c
0
p
−
1
p
1
≡
a
p
−
1
p
1
(
m
o
d
p
)
g^{c_0\frac{p-1}{p_1}}\equiv a ^ {\frac{p-1}{p_1}} \pmod p
gc0p1p−1≡ap1p−1(modp)
此时
c
0
c_0
c0的大小范围已知为
(
0
,
p
1
−
1
)
(0,p_1-1)
(0,p1−1)之间,其余变量大小已知,我们爆破
c
0
c_0
c0大小直到满足该同余式;这里就对应了为什么该算法应用条件需要
p
−
1
p-1
p−1有较小的质因数
这样就可以得到 c 0 c_0 c0,但是我们要知道所有 c i c_i ci的值来表示出在模 p 1 q 1 {p_1^{q_1}} p1q1意义下的 u u u
我们通过更改
r
r
r的值,并应用费马小定理来达到求解
c
i
c_i
ci的目的,如我们再令
r
r
r为
2
2
2,得到
g
c
0
p
−
1
p
1
2
⋅
g
c
1
p
−
1
p
1
⋅
g
c
2
(
p
−
1
)
⋯
g
c
q
1
−
1
(
p
−
1
)
p
1
q
1
−
3
≡
a
p
−
1
p
1
2
(
m
o
d
p
)
g^{c_0\frac{p-1}{p_1^2}} \cdot g^{c_1\frac{p-1}{p_1}}\cdot g^{c_2(p-1)} \cdots g^{c_{q_1-1}(p-1)p_1^{q_1-3}} \equiv a^{\frac{p-1}{p_1^2}} \pmod p
gc0p12p−1⋅gc1p1p−1⋅gc2(p−1)⋯gcq1−1(p−1)p1q1−3≡ap12p−1(modp)
由费马小定理
g
p
−
1
≡
1
(
m
o
d
p
)
g^{p-1}\equiv 1 \pmod p
gp−1≡1(modp),上式等价于
g
c
0
p
−
1
p
1
2
⋅
g
c
1
p
−
1
p
1
≡
a
p
−
1
p
1
2
(
m
o
d
p
)
g^{c_0\frac{p-1}{p_1^2}} \cdot g^{c_1\frac{p-1}{p_1}}\equiv a ^ {\frac{p-1}{p_1^2}} \pmod p
gc0p12p−1⋅gc1p1p−1≡ap12p−1(modp)
其中同余式左侧第一项实际上我们已知,那么只用爆破
c
1
c_1
c1的大小即可;其他
c
i
c_i
ci以此类推
当我们求出整个同余式组里的满足各个同余式模意义下的 u u u,使用孙子定理即可得到满足原始问题 g u ≡ a ( m o d p ) g^u \equiv a\pmod p gu≡a(modp)的 u u u
那么用相同的方法再求解出 g v ≡ b ( m o d p ) g^v \equiv b\pmod p gv≡b(modp)的 v v v即可,代回同余式 x ≡ v ⋅ u − 1 ( m o d p − 1 ) x \equiv v\cdot u^{-1} \pmod {p-1} x≡v⋅u−1(modp−1)求得 x x x
看完整个流程,可能会想为什么不直接对 a x ≡ b ( m o d p ) a^x\equiv b \pmod p ax≡b(modp)使用之后将 x x x转化为 p − 1 p-1 p−1的素因子的进制等等操作,这样就可以直接得到 x x x,而不用拐弯抹角地去求 u , v u,v u,v,再来求 x x x;这是因为该算法只针对原根起效,也就是说,如果 a a a是原根,那么这样操作是可以的;但如果 a a a不是原根,就无法求得正确结果,其具体原因可以见 R e f e r e n c e Reference Reference部分的文章
P y t h o n I m p l e m e n t a t i o n Python\ Implementation Python Implementation
from Crypto.Util.number import *
from sympy.polys.galoistools import gf_crt
from sympy.polys.domains import ZZ
import gmpy2
import random
def Pohlig_Hellman(g, h, p):
# 模数分解
p_1 = p - 1
d, factors = 2, []
while d*d <= p_1:
while (p_1 % d) == 0:
factors.append(d)
p_1 //= d
d += 1
if p_1 > 1:
factors.append(p)
factors = [[i, factors.count(i)] for i in set(factors)]
# 求每个素因子进制下的c_i
x = []
for factor in factors:
c_i_list = []
for i in range(factor[1]):
if i != 0:
beta = (beta * pow(g, -(c_i_list[-1] * (factor[0] ** (i - 1))), p)) % p
else:
beta = h
e1 = pow(beta, (p-1) // (factor[0] ** (i + 1)), p)
e2 = pow(g, (p-1) // factor[0], p)
for c_i in (range(factor[0])):
if pow(e2, c_i, p) == e1:
c_i_list.append(c_i)
break
x.append(c_i_list)
# 将模p_i意义下的p_i进制表示转换为模p_i意义下的十进制
system = []
for i, factor in enumerate(factors):
res = 0
for j, x_j in enumerate(x[i]):
res += x_j * (factor[0] ** j)
res = res % (factor[0] ** factor[1])
system.append(res)
# 中国剩余定理
factors = [factors[i][0] ** factors[i][1] for i in range(len(factors))]
result = gf_crt(system, factors, ZZ)
return result
if __name__ == "__main__":
p = 7863166752583943287208453249445887802885958578827520225154826621191353388988908983484279021978114049838254701703424499688950361788140197906625796305008451719
a = random.randint(0, 2 ** 512)
x = random.randint(0, 2 ** 256)
b = pow(a, x, p)
print("real_x = {}".format(x))
res = Pohlig_Hellman(a, b, p)
print("x1 = {}".format(res))
# sage: p = 7863166752583943287208453249445887802885958578827520225154826621191353388988908983484279021978114049838254701703424499688950361788140197906625796305008451719
# sage: primitive_root(p)
# 13
g = 13 # p的本原元为13
u = Pohlig_Hellman(g, a, p)
v = Pohlig_Hellman(g, b, p)
try:
x = gmpy2.invert(u, p - 1) * v % (p - 1)
print("x2 = {}".format(x))
except: # u 和 p-1 可能不互素,导致之间没有逆元;该情况将u除以最大公因数使得两者之间互素即可,最后结果除以相应的数即可
i = 0
gcd = gmpy2.gcd(u, p - 1)
while True:
if gmpy2.gcd(u, p - 1) != 1:
u = u // gmpy2.gcd(u, p - 1)
i += 1
else:
break
assert gmpy2.gcd(u, p - 1) == 1
x = (gmpy2.invert(u // gmpy2.gcd(u, p - 1), p - 1) * v) % (p - 1) // (gcd ** i)
print("x = {}".format(x))
"""
a ^ x \equiv b (mod p) 之中a不是原根
real_x = 17475167858715014509948693871106677723065342839264165381680037187400995034209
x1 = 3931583376291971643604226624722943901442979289413760112577413310595676694494454509217307369704071534867821221958389972909818020158235480633350085553499260068
x = 17475167858715014509948693871106677723065342839264165381680037187400995034209
"""
"""
a ^ x \equiv b (mod p) 之中a是原根
real_x = 59538927048508916825466804038603833711975305674466217174924215808245351055236
x1 = 59538927048508916825466804038603833711975305674466217174924215808245351055236
x2 = 59538927048508916825466804038603833711975305674466217174924215808245351055236
"""
R e f e r e n c e Reference Reference
(10条消息) Pohlig-Hellman算法求解离散对数问题_国科大网安二班的博客-CSDN博客_pohlig-hellman
Pohlig_hellman/Pohlig_hellman.py at master · Sarapuce/Pohlig_hellman (github.com)