当你在处理离散对数问题时,发现暴力破解的方法效率太低了😩,这时候 BSGS(Baby-Step Giant-Step)算法 就像一把神奇的钥匙🔑,能帮你快速找到离散对数的解。这个算法也别戏称为“北上广深”算法。🤐

欢迎来到《密码学核心算法实战》的 BSGS 专题!这里没有纸上谈兵的理论空谈(真的不画大饼😉),只有一把把能直接撬动数据安全的精密齿轮⚙️。

BSGS算法 🌟

BSGS(Baby-Step Giant-Step)算法是一种用于求解离散对数问题的算法。给定质数 $p$,整数 $a$ 和 $b$,我们想要找到一个整数 $x$,使得 $a^x \equiv b \pmod{p}$(其中 $a$ 与 $p$ 互质)。

BSGS算法的原理 😵

根据群中生成元的性质,我们可以知道元素 $a$ 的阶(即 $a$ 的幂等于 1 的最小正整数)是 $p-1$,然后产生一种循环的效果。因此,离散对数问题的解 $x$ 必须满足 $0 \leq x \leq p-1$。

我们考虑先求出一部分 $a$ 的幂次模 $p$ 意义下的值,将它们存起来,然后使得剩下没有求值的部分能够想个办法利用已求值直接查出来。这就让我们联想到根号。

将 $x$ 表示为 $x = im - j$,其中 $m = \lceil \sqrt{p} \rceil$,$i$ 和 $j$ 是非负整数。这样我们可以将离散对数问题转化为以下形式:

小步(Baby Step):与计算所有 $b \cdot a^j \pmod{p}$ $(j \in [0,m))$,存入哈希表(键为值,值为 $j$)。每次都是乘 $a$,所以叫做小步。

大步(Giant Step):计算 $a^m \pmod{p}$,遍历 $i \in [1,m]$,计算 $(a^m)^i \pmod{p}$,检查是否在哈希表中存在对应的值,如果存在,则可以得到 $x = im - j$。每次都是乘 $a^m$,所及叫做大步。

$j$ 是小步的步数,范围为 $0 \leq j < m$,因为 $j$ 是拆分后的余项,必须小于块大小 $m$ (类似除法中的余数)。当然,如果 $j \geq m$,可以通过 $i$ 来调整,本质是上是多余的。

$i$ 是大步的步数,范围为 $1 \leq i \leq m$,我们分两步证明:

先证明:所有 $x \in [0, p-1]$ 都可以表示为 $x = im - j$,其中 $i \in [1, m]$,$j \in [0, m)$。

由于 $m = \lceil \sqrt{p} \rceil$,我们有 $m^2 \geq p$。对于任意 $x \in [0, p-1]$,我们可以将其除以 $m$ 得到商 $i$ 和余数 $j$,即 $x = im + j$。由于 $x < p \leq m^2$,我们有 $i < m$ 和 $j < m$。因此,所有的 $x$ 都可以表示为 $x = im - j$。

再证明: $i$ 不用超过 $m$。

若 $i > m$,则 $x = im - j > m^2 - j \geq m^2 - (m-1) = m^2 - m + 1$。由于 $m^2 \geq p$,我们有 $x > p - m + 1$。因此,如果 $i > m$,则 $x$ 将超过 $p-1$,这与 $x \in [0, p-1]$ 矛盾。因此,$i$ 不用超过 $m$。

BSGS算法的实现 🙃

下面是 BSGS 算法的一个简单实现:

import math

def bsgs(a, b, mod):
    # 前提断言:a 与 mod 必须互质,否则直接报错
    assert math.gcd(a, mod) == 1, "a 与 mod 不互质,基础 BSGS 无法求解"

    a = a % mod  # 先取模,防止底数过大
    b = b % mod  # 先取模,防止结果过大

    # 特殊情况:b ≡ 1 mod mod,直接返回 x=0
    if b == 1:
        return 0

    k = math.ceil(math.sqrt(mod))
    step_dict = dict()

    # 小步 (Baby Step) 
    # 计算: b * a^q mod mod,q ∈ [0, k)
    right_val = b  # 初始值: b * a^0
    for q in range(k):
        # 只存第一次出现的q(保证q最小,从而解x最小)
        if right_val not in step_dict:
            step_dict[right_val] = q
        right_val = right_val * a % mod

    # 大步 (Giant Step) 
    # 计算: (a^k)^p mod mod,p ∈ [1, k]
    ak = pow(a, k, mod)
    left_val = 1  # 初始值: (a^k)^0
    for p in range(1, k + 1):
        # 用递推代替重复幂运算
        left_val = left_val * ak % mod

        if left_val in step_dict:
            q = step_dict[left_val]
            x = k * p - q
            # 验证解的有效性(防止哈希碰撞或理论误差)
            if pow(a, x, mod) == b:
                return x

    return None  # 遍历结束未找到,无解

扩展BSGS算法 🥰

当 $a$ 和 $mod$ 不互质时,基础的 BSGS 算法无法直接求解。这时候我们可以使用扩展 BSGS 算法来处理这种情况。

我们把题目所给的这个式子搬出来:

我们不妨设 $gcd(a, p) = d$,如果 $d$ 不整除 $b$,则方程无解;如果 $d$ 整除 $b$,我们可以将方程两边同时除以 $d$,得到:

但是即使这样,我们也不能直接使用 BSGS 算法来求解,因为 $\frac{a}{d}$ 和 $\frac{p}{d}$ 可能仍然不互质。我们需要继续进行约简,直到 $\frac{a}{d}$ 和 $\frac{p}{d}$ 互质为止,即:

这个时候,我们再处理一下:

这样我们就得到了一个新的离散对数问题,其中底数和模数已经互质了,我们就可以使用 BSGS 算法来求解了。

扩展BSGS算法的实现 🤪

下面是这个算法的python简单实现:

def extended_bsgs(a, b, mod):
    # 保存原始值(关键!用于小解验证)
    orig_a = a
    orig_b = b
    orig_mod = mod

    a = a % mod
    b = b % mod

    # 特殊情况:b ≡ 1 mod mod,x=0
    if b == 1:
        return 0
    # 特殊情况:mod=1,所有 x 都满足,返回最小非负解 0
    if mod == 1:
        return 0

    k = 0
    C = 1  # 记录左边累积的系数

    while True:
        d = math.gcd(a, mod)
        if d == 1:
            break  # 已经互质,停止提取

        # 如果 b 不是 d 的倍数,无解
        if b % d != 0:
            return None

        # 提取公因数(注意:a 不除 d!之前的 a//=d 是错误的)
        # 正确逻辑:a 保持原值,只除 mod 和 b
        b = b // d
        mod = mod // d
        k += 1
        # 累积系数:C = C * (a//d) mod mod 
        C = (C * (orig_a // d)) % mod

    # 现在 a(orig_a)与 mod 互质,转化方程:C * orig_a^(x-k) ≡ b (mod mod)
    # 求 C 的逆元
    try:
        inv_C = pow(C, -1, mod)
    except ValueError:
        return None  # 逆元不存在,理论上不会发生

    B = (b * inv_C) % mod
    # 调用 BSGS 时用原始 a,不是提取后的 a
    y = bsgs(orig_a, B, mod)

    if y is None:
        return None
    x = y + k

    # 验证小解:x < k 的情况(用原始值验证)
    for i in range(k):
        if pow(orig_a, i, orig_mod) == orig_b:
            return i

    # 验证最终解是否满足原方程
    if pow(orig_a, x, orig_mod) == orig_b:
        return x
    return None

SageMath 偷懒 🤓👆

有同学说:“博主,博主,你的算法确实很厉害,但是还是太吃操作了,有没有更加简单无脑的用法?”👻
有的,兄弟有的,这样的算法在 SageMath 中早就已经被封装好了🤫,我们直接调用就行了:

from sage.all import discrete_log

# discrete_log中内置了BSGS算法,当然更加完善
r = discrete_log(b, a, mod)
# 但是需要注意:如果 a 和 mod 不互质,discrete_log 可能会抛出异常,这时候我们需要先处理一下,或者直接使用 extended_bsgs 来求解。

怎么样,是不是非常简单?🤣👉🤡

上一章:密码学算法 - Miller-Rabin素数检验 👈
下一章:密码学算法 - BSGS算法二 👈
回到开始:关于我 👈

相关链接:
密码学数学基础 - 群论基础 👈