前几天参加了网鼎杯的比赛,第一道题就是简单粗暴的离散对数问题(DLP)。当时自己是用sagemath的discrete_log()函数的直接秒解的(另外第三题必须要用cmd打开flag才出来就很坑)。赛后发现这道离散对数题应该是用Pohlig–Hellman algorithm 做的,所以针对这次的题目学习了一遍该算法。
后面的内容都是围绕循环群的子群以及群和元素的阶展开的。
维基百科解释如下:
是一种特殊算法,在阶为光滑数的有限阿贝尔群上解决离散对数问题。
同时
大致流程如下:
(假设阶为 ϕ ( m ) \phi(m) ϕ(m))
首先,要解决的问题是
g x ≡ h m o d m a n d n = ϕ ( m ) = ∏ i = 1 r p i e i g^x\equiv h \ mod\ m\ and\ n=\phi(m)=\prod_{i=1}^r p_i ^ {e_i} gx≡h mod m and n=ϕ(m)=i=1∏rpiei
等式两边同乘上一个指数 n p i e i \frac{n}{p_i ^ {e_i}} piein,可以得到:
{ ( g x 1 ) n p 1 e 1 ≡ h n p 1 e 1 m o d m ( g x 2 ) n p 2 e 2 ≡ h n p 2 e 2 m o d m ( g x 3 ) n p 3 e 3 ≡ h n p 3 e 3 m o d m . . . ( g x r ) n p r e r ≡ h n p r e r m o d m \begin{cases} (g^{x_1})^\frac{n}{p_1 ^ {e_1}}\equiv h^\frac{n}{p_1 ^ {e_1}} \ mod\ m\\ (g^{x_2})^\frac{n}{p_2 ^ {e_2}}\equiv h^\frac{n}{p_2 ^ {e_2}} \ mod\ m\\ (g^{x_3})^\frac{n}{p_3 ^ {e_3}}\equiv h^\frac{n}{p_3 ^ {e_3}} \ mod\ m\\ ...\\ (g^{x_r})^\frac{n}{p_r ^ {e_r}}\equiv h^\frac{n}{p_r ^ {e_r}} \ mod\ m\\ \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎧(gx1)p1e1n≡hp1e1n mod m(gx2)p2e2n≡hp2e2n mod m(gx3)p3e3n≡hp3e3n mod m...(gxr)prern≡hprern mod m
对于每一条式子来说,通过遍历 x i ∈ { 0 , 1 , . . . , p i e i } x_i\in\{0,1,...,p_i ^ {e_i}\} xi∈{0,1,...,piei}一定可以计算出一个 x i x_i xi满足: ( g x i ) n p i e i ≡ h n p i e i m o d m (g^{x_i})^\frac{n}{p_i ^ {e_i}}\equiv h^\frac{n}{p_i ^ {e_i}} \ mod\ m (gxi)piein≡hpiein mod m
所以我们可以得到:
{ x ≡ x 1 m o d p 1 e 1 x ≡ x 2 m o d p 2 e 2 x ≡ x 3 m o d p 3 e 3 . . . x ≡ x r m o d p r e r \begin{cases} x\equiv x_1 \ \mod p_1 ^ {e_1}\\ x\equiv x_2 \ \mod p_2 ^ {e_2}\\ x\equiv x_3 \ \mod p_3 ^ {e_3}\\ ...\\ x\equiv x_r \ \mod p_r ^ {e_r}\\ \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧x≡x1 modp1e1x≡x2 modp2e2x≡x3 modp3e3...x≡xr modprer
最后用中国剩余定理可以算出最后的 x x x:
x ≡ x ′ m o d ∏ i = 1 r p i e i x \equiv x' \mod \prod_{i=1}^r p_i ^ {e_i} x≡x′modi=1∏rpiei
(为什么能算出这个 x i x_i xi?)
我们用群理论重新梳理一遍整个算法。
根据题目内容,我们可以构建生成元为 g g g ,阶为 ϕ ( m ) \phi(m) ϕ(m)(用n表示),模 m m m的有限循环群
群内元素为:
G = { 1 , g 1 , g 2 , . . . , g n } G=\{1,g^1,g^2,...,g^n\} G={1,g1,g2,...,gn}
(这里先讲一下,循环群的阶与生成元的阶是一样的,暂且认为阶为 n n n,因为根据欧拉定理 g ϕ ( m ) ≡ 1 m o d m g^{\phi(m)}\equiv1 \mod m gϕ(m)≡1modm,即使存在元素的阶t小于 n n n(即 g t ≡ 1 m o d m g^{t}\equiv1 \mod m gt≡1modm),那也是 n n n的因子,可以通过遍历因子的方式得到 t t t)
通过这个循环群我们可以知道 h h h是在该群中的,而且 x ∈ { 0 , 1 , . . , n − 1 } x\in\{0,1,..,n-1\} x∈{0,1,..,n−1}
那为什么要乘上指数呢?
其实对每个式子乘上指数是为了构造多个不同的该循环群的子群。
令 g i = g n p i e i g_i=g^\frac{n}{pi^{e_i}} gi=gpiein,可以构造一个以 g i g_i gi为生成元,阶为 p i e i p_i^{e_i} piei的循环群子群
元素为
< g i > = { 1 , g n p i e i , g n p i e i ∗ 2 , g n p i e i ∗ 3 , . . . , g n p i e i ∗ ( p i e i − 1 ) }
同时我们也构造 h i = h n p i e i = ( g x ) n p i e i m o d m h_i=h^{\frac{n}{pi^{e_i}}}=(g^{x})^{\frac{n}{pi^{e_i}}} \mod m hi=hpiein=(gx)pieinmodm(取模后 x x x变成了 x i x_i xi)
因此 h i ∈ < g i > h_i\in
同时我们要关注 g x ≡ h m o d m → ( g n p i e i ) x i ≡ h n p i e i m o d m g^x\equiv h \ mod\ m\rightarrow (g^{\frac{n}{p_i^{e_i}}})^{x_i}\equiv h^\frac{n}{p_i ^ {e_i}} \ mod\ m gx≡h mod m→(gpiein)xi≡hpiein mod m
实际上是
x ∈ [ 0 , n − 1 ] → x i ∈ [ 0 , p i e i − 1 ] x\in[0,n-1]\rightarrow x_i\in [0,p_i^{e_i}-1] x∈[0,n−1]→xi∈[0,piei−1]
也就是说,每个等式计算出来的 x i x_i xi是原来的 x x x在不同模下的结果。所以我们才能用CRT把原来的 x x x算出来。
特殊算法(当阶为素数幂prime power):
其实就是普遍算法,为了美观代码简化了一点,差异不大。但可以与 B S G S BSGS BSGS算法并用(其实我并不太清楚怎么并用), 时间复杂度降为 O ( e p ) O(e\sqrt{p}) O(ep)。
you_rise_me_up.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from Crypto.Util.number import *
import random
n = 2 ** 512
m = random.randint(2, n-1) | 1
c = pow(m, bytes_to_long(flag), n)
print 'm = ' + str(m)
print 'c = ' + str(c)
# m = 391190709124527428959489662565274039318305952172936859403855079581402770986890308469084735451207885386318986881041563704825943945069343345307381099559075
# c = 6665851394203214245856789450723658632520816791621796775909766895233000234023642878786025644953797995373211308485605397024123180085924117610802485972584499
离散对数问题, n n n为 2 512 2^{512} 2512,素数幂,可以使用Pohlig–Hellman algorithm。
首先找阶,因为生成元m的阶整除 ϕ ( n ) \phi(n) ϕ(n),所以我们可以通过遍历 2 2 2的幂来找到这个阶。
遍历之后发现阶为 2 510 2^{510} 2510,直接使用上述特殊算法就可得到结果。
(自己写的辣鸡代码)
def Prime_power_Pohlig_Hellman(g,h,n,p,e):
'''
phi(n)=p^e
g^(p^e)=1 mod n
g^x=h mod n
'''
for a in range(e):
if(pow(g,2**a,n)==1):
break
x_k=0
e=a
gm=pow(g,pow(p,e-1,n),n)
print gm
if(pow(g,p**e,n)!=1):
print 'error'
for k in range(e):
_x=pow(p,e,n)-x_k
if(pow(g,x_k,n)*pow(g,_x,n)%n !=1 ):
print 'error'
h_k=pow(pow(g,_x,n)*h,pow(p,e-1-k,n),n)
for d_k in range(p):
if(pow(gm,d_k,n)==h_k):
print k,d_k
break
x_k=x_k+d_k*pow(p,k,n)
return x_k
if __name__ =='__main__':
n=2**512
m = 391190709124527428959489662565274039318305952172936859403855079581402770986890308469084735451207885386318986881041563704825943945069343345307381099559075
c = 6665851394203214245856789450723658632520816791621796775909766895233000234023642878786025644953797995373211308485605397024123180085924117610802485972584499
print libnum.n2s(Prime_power_Pohlig_Hellman(m, c, 2**512, 2, 511) )
这篇博客的内容是我第一次使用新学的群理论来分析(尽力了),可能有讲得不好的地方,请多多包涵。
个人体会有两个,一是其实算法并没有那么难看懂,英语也没有那么难读懂,莫名的恐惧只是因为不了解。所以多看几遍就好了。
二是我觉得了解算法就是一种说服自己的过程,这个算法为什么要这样做?理论依据又是什么?学习算法的过程中,我一直在寻找理由说服自己,这个算法这样做肯定是有它的理由的。可能有点吹毛求疵的感觉,但我只是想纯粹的去理解算法。
三是自己还有很大的进步空间。