文章

BSGS算法

BSGS算法

目录

  1. 离散对数问题(DLP)简介
  2. BSGS算法
  3. 结合Pohlig–Hellman 攻击
  4. EXBSGS算法
  5. BSGS在lcg中的应用

一、DLP简介

离散对数的定义与对数类似:

取有原根的正整数模数m,设其一个原根为g。由原根的定义,我们知道对满足$(a,m)=1$的整数a,必定存在唯一的整数k,$0\leq k<\varphi(m)$ ,使得

\[g^{k}\equiv a \ \ mod \ \ m\]

我们称这个k为以g为底,模m的离散对数,记作$k=ind_{g}a$ .

关于离散对数的性质:

  1. $ind_{g}1=0,ind_{g}g=1$
  2. $ind_{g}(ab)\equiv ind_{g}a+ind_{g}b \ \ mod \ \ \varphi(m)$
  3. $ind_{g}a^{n}\equiv n ind_{g}a\ \ mod \ \ \varphi(m)$
  4. 若$g_{1}$也是模m的原根,则$ind_{g}a\equiv ind_{g_{1}}a\cdot ind_{g}g_{1}\ \ mod \ \ \varphi(m)$
  5. $a\equiv b \ \ mod \ \ m \iff ind_{g}a=ind_{g}b$

关于给定的g和a,求唯一的指数k使$g^{k}\equiv a \ \ mod \ \ m$ ,这就是离散对数问题(DLP).

目前还不存在多项式时间内求解离散对数问题的算法。

为什么要使用原根?因为原根是 $\mathbb{Z}^{\times}_{m}$ 的生成元,所以原根的次幂,与${ 1,2,\dots,p-1 }$中的数字一一对应,这样才能进行加密和解密。

二、BSGS算法

大步小步算法(Baby-step Giant-step),简称BSGS。是一种能较快速求解离散对数问题的算法,可以在$O(\sqrt{ m })$ 的时间内求解

\[a^{x}\equiv b \ \ mod \ \ m\]

其中$(a,m)=1$.

算法描述

将指数写成$x=\lceil \sqrt{ m } \rceil i-j$ 的形式,其中$0\leq i,j\leq \lceil \sqrt{ m } \rceil$ ,则

\(a^{\lceil \sqrt{ m } \rceil i-j} \equiv b \ \ mod \ \ m\) \(a^{\lceil \sqrt{ m } \rceil i} \equiv ba^{j} \ \ mod \ \ m\)

算法分为大步和小步两步

小步:枚举 j,计算所有$ba^{j}$ ,并用哈希表存下来

大步:逐一枚举 i,计算$a^{\lceil \sqrt{ m } \rceil i}$ ,若其出现在哈希表中,则得到这样的x。

输出$x=\lceil \sqrt{ m } \rceil i-j$ .

注意到 i , j 均小于$\lceil \sqrt{ m } \rceil$ 所以时间复杂度为$O(\sqrt{ m })$

为什么算法需要$(a,m)=1$ 呢?这是因为我们得到的 i , j 满足 $a^{\lceil \sqrt{ m } \rceil i} \equiv ba^{j} \ \ mod \ \ m$ ,若想让它同时满足$a^{\lceil \sqrt{ m } \rceil i-j} \equiv b \ \ mod \ \ m$ ,需要两边同时除以 $a^{j}$ .

算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def bsgs(g, h, m):
    order = phi(m)
    step = math.isqrt(order) + 1

    g_inv = pow(g, -1, m)
    g_neg_step = pow(g_inv, step, m)

    table = {}
    beta = 1
    for j in range(step):
        table[beta] = j
        beta = (beta * g) % m

    gamma = h
    for i in range(step):
        if gamma in table:
            j = table[gamma]
            return i * step + j
        gamma = (gamma * g_neg_step) % m

    return None

三、结合Pohlig–Hellman 攻击

当模数光滑时,可以对模数做分解以降低算法的复杂度。

先来回顾一下Pohlig–Hellman 攻击,设群 $G$ 的阶n有完全素因子分解: \(n = \prod_{i=1}^{k} p_i^{e_i}\)

离散对数问题:已知 $g,h\in G$,求 $x$ 使 $h=g^x$,其中 $0\le x < n$。

Pohlig–Hellman的做法是分别求出 \(x \bmod p_i^{e_i},\quad i=1,\dots,k\) 然后用中国剩余定理合成 $x \bmod n$。
每个模 $p_i^{e_i}$ 的求解过程又被进一步降解为多个模 $p_i$ 的小离散对数问题,这些模 $p_i$ 的小 DLP 就可以用 BSGS 快速求解。

攻击的详细步骤

2.1 计算每个素因子幂对应的生成元和目标元素

对于每个因子 $q = p_i^{e_i}$:

  • 令 $g_q = g^{n/q}$,$h_q = h^{n/q}$。
  • 此时 $g_q$ 的阶恰好为 $q$,且 $h_q = g_q^{x_q}$,其中 $x_q \equiv x \pmod{q}$(因为 $g_q$ 的阶为 $q$)。

于是问题化为求 $x_q$ 满足 $h_q = g_q^{x_q}$,$0\le x_q < q$。

2.2 若 $e_i = 1$(即 $q=p_i$ 为素数)

此时直接用 BSGS求 $x_q$。子群的规模为 $p_i$,BSGS 的复杂度为 $O(\sqrt{p_i})$。

2.3 若 $e_i > 1$(素数次幂情形)

需要用 Hensel 提升 将模 $p_i^{e_i}$ 的问题降解为多个模 $p_i$ 的 BSGS 求解。以 $q = p^e$ 为例:

设 $x \bmod p^e$ 可表示为 $p$ 进制展开: \(x \equiv x_0 + x_1 p + x_2 p^2 + \dots + x_{e-1} p^{e-1} \pmod{p^e}\) 其中每个 $x_j \in {0,\dots,p-1}$。

  1. 求 $x_0$(即 $x \bmod p$):
    令 $g_0 = g^{n/p}$(阶 $p$),$h_0 = h^{n/p}$。
    则 $h_0 = g_0^{x_0}$。用 BSGS 解出 $x_0$。

  2. 求 $x_1$ (即 $x-x_{0}\equiv x_{1}p \ \ mod \ \ p^2$):
    构造 $h_1 = (h \cdot g^{-x_0})^{\frac{n}{p^2}}$,$g_{1}=g^{\frac{n}{p}}$,

    $mod\ \ p^2$ 下,有

    \[g_{1}^{x_{1}}=g^{\frac{n}{p^2}x_{1}p}=g^{\frac{n}{p^2}(x-x_{0})}=(h \cdot g^{-x_0})^{\frac{n}{p^2}}\]

    可得 $h_1 = g_1^{x_1}$。用 BSGS 解出 $x_1$。

  3. 依此类推,每次剥离已求出的低位,将问题降维成模 $p$ 的 DLP,最终得到完整的 $x \bmod p^e$。

2.4 中国剩余定理合成

求出所有 $x_i \equiv x \pmod{p_i^{e_i}}$ 后,使用 CRT 得到: \(x \equiv x_i \pmod{p_i^{e_i}},\; \forall i \quad\Longrightarrow\quad x \bmod n.\)

四、扩展BSGS

算法描述

当$(a,m)\neq1$时,要想办法将其变得互素。

具体地,设 $d_1=(a,m)$。如果 $d_1 \nmid b$,则原方程无解。否则我们把方程同时除以 $d_1$,得到

\[\frac{a}{d_1}\cdot a^{x-1}\equiv \frac{b}{d_1}\pmod{\frac{m}{d_1}}\]

如果 $a$ 和 $\frac{m}{d_1}$ 仍不互质就再除,设 $d_2=(a,\frac{m}{d_1})$。如果 $d_2 \nmid \frac{b}{d_1}$,则方程无解;否则同时除以 $d_2$ 得到

\[\frac{a^2}{d_1d_2}\cdot a^{x-2}\equiv \frac{b}{d_1d_2}\pmod{\frac{m}{d_1d_2}}\]

同理,这样不停的判断下去,直到 $(a, \frac{m}{d_1d_2\cdots d_k})=1$。

记 $D=\prod_{i=1}^{k}d_i$,于是方程就变成了这样:

\[\frac{a^k}{D}\cdot a^{x-k}\equiv \frac{b}{D}\pmod{\frac{m}{D}}\]

由于 $(a, \frac{m}{D})=1$,于是推出 $(\frac{a^k}{D}, \frac{m}{D})=1$。这样 $\frac{a^k}{D}$ 就有逆元了,于是把它丢到方程右边,这就是一个普通的 BSGS 问题了,于是求解 $x-k$ 后再加上 $k$ 就是原方程的解。

注意,不排除解小于等于 $k$ 的情况,所以在消因子之前做一下 $O(k)$ 枚举,直接验证 $a^i\equiv b\pmod m$,这样就能避免这种情况。

算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from math import gcd, isqrt
import sys

def exbsgs(a, b, m):
    a %= m
    b %= m
    if m == 1:
        return 0
    if b == 1:
        return 0
    k = 0
    d = 1
    while True:
        g = gcd(a, m)
        if g == 1:
            break
        if b % g:
            return -1
        b //= g
        m //= g
        d = d * (a // g) % m
        k += 1
        if d == b:
            return k
    t = b * pow(d, -1, m) % m
    ans = bsgs(a, t, m)
    if ans == -1:
        return -1
    return ans + k

for line in sys.stdin:
    if not line.strip():
        continue
    a, b, m = map(int, line.split())
    print(exbsgs(a, b, m))

五、BSGS在lcg中的运用

lcg(线性同余生成器)指的是由

\[\begin{align} &X_{0}=seed \\ &X_{n+1}=aX_{n}+b \ \ mod\ \ m \end{align}\]

生成的一系列随机数

现在来看这样一个问题:

[!question] 求最小的正整数$N$ , 使得$11\dots1(N个1)\equiv 114514 \ \ mod \ \ 10000000000099$

这样的数显然可以由lcg生成:

\[\begin{align} &X_{0}=0 \\ &X_{n+1}=10X_{n}+1 \ \ mod\ \ p \end{align}\]

事实上,这个问题可以转换成离散对数问题:

将$X_{n}$展开得

\[X_{n}=a^{n}X_{0}+b(1+a+\dots a^{n-1}) \ \ mod \ \ p\] \[X_{n}=a^{n}X_{0}+b\frac{1-a^{n}}{1-a} \ \ mod \ \ p\] \[(a-1)X_{n}+b=a^{n}(X_{0}(a-1)+b)\ \ mod \ \ p\]

显然,$(X_{0}(a-1)+b,p)=1$, 令$t=((a-1)X_{n}+b)(X_{0}(a-1)+b)^{-1} \ \ mod \ \ p$

\[a^n=t \ \ mod \ \ p\]

变为求解n的离散对数问题。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import math

def bsgs(a, X, m):
    M = math.isqrt(m) + 1
    inv_a = pow(a, -1, m)          
    baby = {}
    cur = X
    for j in range(M):
        if cur not in baby:        
            baby[cur] = j
        cur = (cur * inv_a) % m
    stride = pow(a, M, m)
    cur = 1
    for i in range(M + 1):
        if cur in baby:
            j = baby[cur]
            return i * M + j
        cur = (cur * stride) % m
    return -1

def solve():
    m = 10000000000099
    a = 10
    b = 1
    x1 = 1
    t = 114514
    D = (x1 * (a - 1) + b) % m     
    C = (t  * (a - 1) + b) % m     
    inv_D = pow(D, -1, m)
    X = (C * inv_D) % m
    if X == 0:
        print(-1)
        return
    k = bsgs(10, X, m)
    if k == -1:
        print(-1)
    else:
        N = k + 1
        print(f"N = {N}")
if __name__ == "__main__":
    solve()

参考:

  1. 应用密码学-离散对数问题(Discrete logarithm Problem)-知乎
  2. 离散对数 - OI Wiki
  3. 离散对数之BSGS(扩展BSGS)与Pohlig-Hellman(知识点整理+板子总结)_Code92007-腾讯云开发者社区
  4. P3306 [SDOI2013] 随机数生成器 - 洛谷
本文由作者按照 CC BY 4.0 进行授权