Baby-Step Giant-Step Algorithm
1. Discrete Logarithm problem:
目的:求x的值,也就是generator相乘的次数。在prime很大的时候求解困难,因为brute-force的时间复杂度是O(prime)
应用:在DH key exchange中,中间人如果需要从已知条件阿尔法(generator), A(public key), p(prime)
破解获得两方的密钥a,b,就需要解决DLP问题。
2. Baby-Step Giant-Step Algorithm
是一种利用空间来有效减少DLP时间复杂度的计算,时间复杂度为O(m * log(m)), m = ceil(sqrt(p))
,空间复杂度为O(sqrt(p))
。由于时间复杂度是sqrt级别的下降,为了保证安全性prime的bits一定要长
思路:分解x = mq + r, m = sqrt(p)
, 使用hashtable减少重复计算。也就是已知target, g
,计算g^r ≡ target * g^(-mq) (mod p)
(然后所有计算出来的数字都要mod p)。其中r为baby step ,q为giant step,原因是b是逐一递增,q在hashtable中是m倍递增(是这样解释的8?)
- 计算m = sqrt(p)
- babystep: for r in range(m),在hashtable中记录{g^r mod p : r},注意可以使用之前已经计算过的值
- 计算gm = g^(-m),根据模反定义,也就是计算g^(m)的模反元素。使用pow和egcd计算inverse
- giantstep: for q in range(m), 计算
index = gm^q mod p
,并在hashtable中查找是否有对应的key,如果有直接返回x = mq + r
,如果没有,在前一步计算的基础上再乘以一个gm,和babystep中复用一个思路。
代码大概是这样的8,不知道可不可以用位运算代替模运算,但是查了一下只有这个规律:当%和&互换:a % 2^n = a & (2^n - 1)
,然而p是prime
import math
import time
# find the inverse of a in GF(p) with xGCD. gf_inv(31, 3**6) = 2
def gf_inv(p, a):
r1 = p
r2 = a
c1 = 0
c2 = 1
while r1 > 1:
q = r1 // r2
r = r1 % r2
c = c1 - c2 * q
r1 = r2
c1 = c2
r2 = r
c2 = c
return c1 % p
# compute a * b in GF(p)
def gf_mul(p, a, b):
return a * b % p
# compute a ** b in GF(p)
def gf_pow(p, a, b):
if b == 0:
return 1
if b == 1:
return a % p
v = gf_pow(p, a, b // 2)
if b % 2 == 0:
return v * v % p
else:
return v * v * a % p
# compute a + b in GF(p)
def gf_add(p, a, b):
return (a + b) % p
# The basic idea of this algorithm is to check if target * g ^(-mq) == g^r, return x = (r + mq)
def bsgs(p, gen, target):
# 1. compute m
m = math.ceil(math.sqrt(p))
# 2. compute g^r
# create a dictionary list_gr to store the index {gr:r}
gr_list = {}
gr = 1
for r in range(m):
gr_list[gr] = r
gr = gen * gr % p # reuse previous result of gr, or gf_mul(p, gen, gr)
# 3. compute g^(-m) which is the inverse of g^m
gm_inv = gf_inv(p, gf_pow(p, gen, m))
# 4. compute hgmq: target * (g^-m)^q
hgmq = target
for q in range(m):
if hgmq in gr_list:
return q * m + gr_list[hgmq]
else:
hgmq = hgmq * gm_inv % p # reuse precomputed hgmq, or hgmq = gf_mul(p, hgmq, gm)
return "No result"
def brute_force(p, gen, target):
x = 1
product = gen
while product != target:
x += 1
product = gf_mul(p, product, gen)
return x
def measure_time(func, *args):
p, gen, target = args
start = time.time()
x = func(p, gen, target)
time_elapsed = time.time() - start
print("secret key: {}, runtime: {:<15f}".format(x, time_elapsed))
def main():
p_list = [31, 7919, 688889, 51539607551, 108086391056891903]
gen_list = [3, 7, 13, 19, 19]
target_list = [6, 854, 490760, 43511947506, 18733402164927303]
print("Brute force algorithm:")
for i in range(5):
if i > 2:
print("secret key: N/A, runtime: N/A")
else:
measure_time(brute_force, p_list[i], gen_list[i], target_list[i])
print("Baby-Step Giant-Step algorithm:")
for i in range(5):
measure_time(bsgs, p_list[i], gen_list[i], target_list[i])
if __name__ == '__main__':
main()
# example output
# Brute force algorithm:
# secret key: 25, runtime: 0.000020
# secret key: 234, runtime: 0.000169
# secret key: 676545, runtime: 0.308789
# secret key: N/A, runtime: N/A
# secret key: N/A, runtime: N/A
# Baby-Step Giant-Step algorithm:
# secret key: 25, runtime: 0.000035
# secret key: 234, runtime: 0.000049
# secret key: 676545, runtime: 0.000525
# secret key: 584050353, runtime: 0.124305
# secret key: 947234702, runtime: 677.787557
# Brute force algorithm:
# secret key: 25, runtime: 0.000017
# secret key: 234, runtime: 0.000138
# secret key: 676545, runtime: 0.292695
# secret key: N/A, runtime: N/A
# secret key: N/A, runtime: N/A
# Baby-Step Giant-Step algorithm:
# secret key: 25, runtime: 0.000063
# secret key: 234, runtime: 0.000060
# secret key: 676545, runtime: 0.000528
# secret key: 584050353, runtime: 0.126814
# secret key: 947234702, runtime: 687.591964
# check complexity
# https://cp-algorithms.com/algebra/discrete-log.html
# egcd.
3. Some small functions
- python计算bit length,可以用来计算prime的二进制长度,从而判断基本的时间复杂度
>>> a = 108086391056891903
>>> a.bit_length()
57
>>> len(bin(108086391056891903)[2:])
57
- 计算a的modular multiplicative inverse时,当p是质数时,根据fermat's little theorem的公式
a^(p - 1) ≡ 1 (mod p)
,在python中可以使用pow(a, p - 2, p)
,在本例中为(3^6)^29 * (3^6) ≡ 1 (mod 31)
得到pow(3**6, 29, 31)
,因为(3^6)^29
是3^6
的inverse
其他
写位运算还是比较难,很难理解
inverse和egcd的理解