在上一篇文章当中花了较大篇幅对群进行了介绍,通过循环群就可以对本文所提到的离散对数问题(DLP) 进行解释。
首先来看离散对数问题的一般定义:
定义1. 离散对数问题(DLP)
给定一个阶为nnn的群GGG,群操作为∘\circ∘,有一生成元α∈G\alpha \in Gα∈G以及一元素β∈G\beta \in Gβ∈G,找到一个满足1≤x≤n1 \le x \le n1≤x≤n的整数xxx,满足:
β=α∘α∘⋯∘α⏟x次=αx, \beta = \underbrace{\alpha \circ \alpha \circ \dots \circ \alpha}_{x次} = \alpha^{x}, β=x次α∘α∘⋯∘α=αx,
也可表示为:
x=logαβ. x = \rm{\log}_{\alpha}{\beta}. x=logαβ.
DLP可被用来构造单向函数,单向函数指的是假设有一个函数FFF,已知输入xxx计算y=F(x)y=F(x)y=F(x)是计算简单的,而已知输出yyy计算x=F−1(y)x=F^{-1}(y)x=F−1(y)在计算上是困难的。带入到DLP中,我们可以得到正向问题为:β=F(x)=αx\beta = F(x)=\alpha^xβ=F(x)=αx;而逆向问题为:x=F−1(β)=logαβx = F^{-1}(\beta) = \rm{\log}_{\alpha}{\beta}x=F−1(β)=logαβ。如果选择了合适的群,那么正向计算出β\betaβ很容易,而逆向计算出α\alphaα是个很困难的问题。
为了方便大家对DLP的理解,这里先介绍基于群Zp∗\mathbb{Z}_p^*Zp∗的DLP,其中ppp是一个素数。在上一篇文章中介绍过,Zp∗\mathbb{Z}_p^*Zp∗是由小于nnn且与nnn互素的正整数所构成的集合,Zp∗\mathbb{Z}_p^*Zp∗对于模qqq的乘法构成一个阿贝尔有限循环群。如果参数足够大的话,在群(Zp∗,⋅)(\mathbb{Z}_p^{*},\cdot)(Zp∗,⋅)中计算离散对数是个非常困难的问题。
示例1
考虑群Z47∗\mathbb{Z}_{47}^*Z47∗内的DLP,其中该群生成元为α=5\alpha = 5α=5,对于β=41\beta = 41β=41的DLP可以被表示为:
x≡log541 mod 47.
x \equiv \log_5{41}~\rm{mod}~47.
x≡log541 mod 47.
找到xxx的唯一办法就是暴力搜索,即尝试所有可能的xxx值,最终得到x=15x=15x=15。但即使是用这么小的数字,找到xxx也不是一件容易事。
在实际中,为了安全性(主要为了防止Pohlig-Hellman攻击),群的阶数最好是素数,对于上面提到的Zp∗\mathbb{Z}_p^*Zp∗,其阶为p−1p-1p−1,显然不是素数,所以人们常会选择Zp∗\mathbb{Z}_p^*Zp∗的子群中阶为素数的子群来建立DLP,而不是Zp∗\mathbb{Z}_p^*Zp∗本身。
示例2
Z47∗\mathbb{Z}_{47}^*Z47∗的阶为46,根据前一篇文章中的子群性质,可知Z47∗\mathbb{Z}_{47}^*Z47∗的子群的阶只能为1、2、23,由于ord(2)=23ord(2)=23ord(2)=23,所以α=2\alpha = 2α=2是Z47∗\mathbb{Z}_{47}^*Z47∗的有23个元素的子群HHH的生成元。我们找到一个元素β∈H\beta \in Hβ∈H,其中β=36\beta = 36β=36,建立DLP:找到一个正整数xxx(1≤x≤231 \le x \le 231≤x≤23)使得
36≡2x mod 47.
36 \equiv 2^{x}~\rm{mod}~47.
36≡2x mod 47.
利用暴力搜索可以得到x=17。
需要注意的一点是,并不是在所有循环群中的DLP都是困难的,这样的循环群就不能被用于构造DLP难题,DLP也不会是一个单向函数。
示例3
考虑整数模素数加法群,例如(Z11,+)(\mathbb{Z}_{11}, +)(Z11,+)是一个生成元为α=2\alpha = 2α=2的有限循环群,下表能够展示出α\alphaα生成整个群的过程:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|
αi\alpha^iαi | 2 | 4 | 6 | 8 | 10 | 1 | 3 | 5 | 7 | 9 | 0 |
现在我们设β=3\beta = 3β=3,建立DLP:找到一个整数xxx(1≤x≤111 \le x \le 111≤x≤11)使得
2+2+⋯+2⏟x次≡3 mod 11
\underbrace{2+2+\dots+2}_{x次} \equiv 3~\rm{mod}~11
x次2+2+⋯+2≡3 mod 11
即
x⋅2≡3 mod 11.
x\cdot2 \equiv 3~\rm{mod}~11.
x⋅2≡3 mod 11.
虽然该群中的运算是模11加法,但是α\alphaα、β\betaβ、xxx之间的关系却可以被模11乘法表示,那么为了求解xxx,可以简单地对α\alphaα求逆元:
x≡2−1⋅3 mod 11
x \equiv 2^{-1}\cdot3 ~\rm{mod}~11
x≡2−1⋅3 mod 11
求逆元的算法并不复杂,可以根据扩展欧几里得算法(后续文章会提到)计算出2−1≡6 mod 112^{-1}\equiv 6 ~\rm{mod}~112−1≡6 mod 11,然后就能得到:
x≡7 mod 11.
x\equiv 7~\rm{mod}~11.
x≡7 mod 11.
从上面的表格就能验证出这个结果是正确的。
上面示例3的结果可以被推广到nnn为任意值且元素α\alphaα、β∈Zn\beta \in \mathbb{Z}_nβ∈Zn的任何群(Zn,+)(\mathbb{Z}_{n}, +)(Zn,+)中,因此可以得到结论在Zn\mathbb{Z}_nZn中计算推广的DLP会非常简单。
介绍完反例以后,下面列举一些密码学中推荐使用的一些离散对数问题:
- 素数域Zq\mathbb{Z}_{q}Zq的乘法群或其子群,例如古典Diffie-Hellman密钥交换、ElGamal或数字签名算法(DSA)都用到了这个群;
- 椭圆曲线构成的循环群,后续文章会介绍到,它们如今逐渐占据了密码学主流位置;
- 伽罗瓦域GF(2m)GF(2^m)GF(2m)上的乘法群或其子群,和1的使用完全一致,但并不常用,因为针对其的攻击要比针对1的攻击更加强大,因此在提供相同安全等级的情况下基于GF(2m)GF(2^m)GF(2m)的DLP要求参数长度比1更长。
下面介绍一种非暴力搜索的解决离散对数问题的算法,名为Shanks’ Baby-Step Giant-Step方法,或简称为BSGS。BSGS是一个时间与内存平衡的方法,它通过额外的存储来减少蛮力搜索的时间。
BSGS算法的思想是将群GGG中离散对数x=logαβx=\log_{\alpha}{\beta}x=logαβ重写为
x=xgm+xb, 0≤xg,xb<m.
x = x_gm+x_b,~0 \le x_g, x_b < m.
x=xgm+xb, 0≤xg,xb<m.
通常情况下mmm的大小选择为群的阶的平方根,即m=⌈∣G∣⌉m=\lceil \sqrt{|G|} \rceilm=⌈∣G∣⌉。下面可以将离散对数进行变形:
β=αxgm+xbβ(α−m)xg=αxb
\begin{aligned}
\beta &= \alpha^{x_gm+x_b} \\
\beta (\alpha^{-m})^{x_g} &= \alpha^{x_b}
\end{aligned}
ββ(α−m)xg=αxgm+xb=αxb
算法的核心思想就是找到上面方程的解(xg,xb)(x_g, x_b)(xg,xb),进而就能得到离散对数问题的解xxx。该算法分为两阶段,即baby-step和giant-step。
Baby-Step
在此阶段,要计算并存储所有αxb\alpha ^ {x_b}αxb的值,其中0≤xb<m0 \le x_b < m0≤xb<m。这一步需要mmm个群操作,并需要存储mmm个群元素。
Giant-Step
在此阶段,算法检查0≤xg<m0 \le x_g < m0≤xg<m范围内所有的xgx_gxg,并判断baby-step阶段计算的一些易存储的项αxb\alpha ^ {x_b}αxb是否满足以下条件:
β(α−m)xg=?αxb
\beta (\alpha^{-m})^{x_g} \overset{?}{=} \alpha^{x_b}
β(α−m)xg=?αxb
如果上面等式成立,那么就意味着找到了一个解(xg0,xb0)(x_{g0}, x_{b0})(xg0,xb0)满足上面的等式,这样离散对数问题的解xxx就可以表示为
x=xg0m+xb0.
x =x_{g0}m + x_{b0}.
x=xg0m+xb0.
BSGS算法需要O(∣G∣)\mathcal{O}(\sqrt{|G|})O(∣G∣)计算复杂度和相同大小的存储复杂度,因此一般为了追求2802^{80}280的攻击复杂度,群的阶至少为∣G∣≥2160|G|\ge2^{160}∣G∣≥2160,因此在G=Zp∗G=\mathbb{Z}_p^*G=Zp∗中,素数ppp的长度应该有160bit。
下面是用Python3对BSGS算法的代码实现,关键步骤在代码中都有注释:
from math import sqrt, ceil
def bsgs_alg(alpha: int, beta: int, p: int) -> int:
# 求出m
m = ceil(sqrt(p - 1))
# 初始化一个baby数组,存放baby-step结果
baby = []
x_g, x_b = -1, -1
# baby-step
baby.append(1)
for i in range(1, m):
baby.append((baby[i - 1] * alpha) % p)
# 对生成元求逆
alpha_inv = (alpha ** (p - 2)) % p
# 求alpha逆元的m次幂
alpha_pow = (alpha_inv ** m) % p
# giant-step
product = beta % p
for j in range(m):
try:
# 查询baby表中是否有匹配的
x_b = baby.index(product)
x_g = j
break
except ValueError:
product *= alpha_pow
product %= p
if x_b == -1:
return x_b
else:
return x_g * m + x_b
写在最后
欢迎大家关注我的微信公众号,本人会不定期发一些有关密码学、区块链技术、编程等相关文章,欢迎志同道合的朋友来一起交流呀!