每日算法 — 使用java实现Pollard Rho算法:大整数因数分解与素性测试

算法背景

整数因数分解是现代密码学的数学基础——RSA的安全性正是建立在大整数分解的计算困难性之上。Pollard’s Rho算法由John Pollard于1975年提出,是分解合数的经典概率算法,在数论研究、密码分析和信息安全领域具有重要地位。

配合Miller-Rabin素性测试,Pollard Rho可以在亚指数时间内分解大多数合数,是理解现代数论算法的绝佳入门案例。

核心思想

Pollard Rho基于生日悖论:在模 $n$ 的伪随机序列中,大约 $\sqrt{n}$ 步后会出现循环。利用Floyd判圈算法检测循环,若序列在模 $n$ 下碰撞,则其差与 $n$ 的最大公约数很可能给出 $n$ 的一个非平凡因子。

迭代函数通常取 $f(x) = (x^2 + c) \mod n$,$c$ 为随机常数。

Java实现:Miller-Rabin素性测试

import java.math.BigInteger;
import java.security.SecureRandom;

public class MillerRabin {

    private static final SecureRandom RANDOM = new SecureRandom();

    /**
     * Miller-Rabin确定性测试 (适用于64位整数)
     * 对于n < 3,317,044,064,679,887,385,961,981,测试前12个素数底即可确定
     */
    private static final long[] WITNESSES_64 = {
        2L, 3L, 5L, 7L, 11L, 13L, 17L, 19L, 23L, 29L, 31L, 37L
    };

    /**
     * 快速幂取模: 计算 (base^exp) % mod
     */
    public static long modPow(long base, long exp, long mod) {
        long result = 1;
        long b = base % mod;
        while (exp > 0) {
            if ((exp & 1) == 1) {
                result = (__int128(result) * b) % mod; // 注意溢出
            }
            b = (__int128(b) * b) % mod;
            exp >>= 1;
        }
        return result;
    }

    // 辅助: 模拟128位整数 (使用BigInteger)
    private static long __int128(long a) { return a; }

    /**
     * Miller-Rabin单轮测试
     * @return true 表示可能是素数, false 表示一定是合数
     */
    public static boolean millerRabinTest(long n, long witness) {
        if (n < 2) return false;
        if (n == 2 || n == 3) return true;
        if ((n & 1) == 0) return false;

        // 将 n-1 写成 d * 2^s 的形式
        long d = n - 1;
        int s = 0;
        while ((d & 1) == 0) {
            d >>= 1;
            s++;
        }

        long x = modPow(witness, d, n);
        if (x == 1 || x == n - 1) return true;

        for (int r = 1; r < s; r++) {
            x = modPow(x, 2, n);
            if (x == n - 1) return true;
            if (x == 1) return false;
        }

        return false;
    }

    /**
     * 确定性Miller-Rabin测试 (64位整数)
     */
    public static boolean isPrime(long n) {
        if (n < 2) return false;
        for (long p : new long[]{2,3,5,7,11,13,17,19,23,29,31,37}) {
            if (n % p == 0) return n == p;
        }
        for (long witness : WITNESSES_64) {
            if (witness >= n) continue;
            if (!millerRabinTest(n, witness)) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        long[] testNumbers = {2, 17, 91, 104729, 1000000007L, 1000000000039L};
        for (long n : testNumbers) {
            System.out.println(n + " is " + (isPrime(n) ? "prime" : "composite"));
        }
    }
}

Java实现:Pollard Rho因数分解

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class PollardRho {

    private static final Random RANDOM = new SecureRandom();
    private static final BigInteger TWO = BigInteger.valueOf(2);

    /**
     * 使用BigInteger的Pollard Rho算法
     * 迭代函数: f(x) = x^2 + c (mod n)
     */
    public static BigInteger pollardsRho(BigInteger n) {
        if (n.mod(TWO).equals(BigInteger.ZERO)) {
            return TWO; // 偶数直接返回2
        }

        if (n.isProbablePrime(20)) {
            return n; // 素数直接返回
        }

        while (true) {
            BigInteger c = new BigInteger(n.bitLength(), RANDOM);
            BigInteger x = new BigInteger(n.bitLength(), RANDOM).mod(n);
            BigInteger y = x;
            BigInteger d = BigInteger.ONE;

            while (d.equals(BigInteger.ONE)) {
                // Floyd判圈: x走一步, y走两步
                x = f(x, c, n);
                y = f(f(y, c, n), c, n);

                // d = gcd(|x-y|, n)
                d = x.subtract(y).abs().gcd(n);
            }

            if (!d.equals(n)) {
                return d; // 找到非平凡因子
            }
            // 若d==n,换随机参数重试
        }
    }

    /** 迭代函数 f(x) = x^2 + c (mod n) */
    private static BigInteger f(BigInteger x, BigInteger c, BigInteger n) {
        return x.multiply(x).mod(n).add(c).mod(n);
    }

    /**
     * 递归分解n的所有素因子
     */
    public static void factor(BigInteger n, List<BigInteger> factors) {
        if (n.equals(BigInteger.ONE)) return;

        if (n.isProbablePrime(20)) {
            factors.add(n);
            return;
        }

        BigInteger divisor = pollardsRho(n);
        factor(divisor, factors);
        factor(n.divide(divisor), factors);
    }

    /**
     * 分解并返回排序后的素因子列表 (含重复)
     */
    public static List<BigInteger> factorize(BigInteger n) {
        List<BigInteger> factors = new ArrayList<>();
        factor(n, factors);
        Collections.sort(factors);
        return factors;
    }

    public static void main(String[] args) {
        // 测试分解
        BigInteger[] testNumbers = {
            new BigInteger("91"),                    // 7 * 13
            new BigInteger("8051"),                  // 83 * 97
            new BigInteger("10403"),                 // 101 * 103
            new BigInteger("1000000007"),            // 素数
            new BigInteger("10403").multiply(new BigInteger("8051")),
        };

        for (BigInteger n : testNumbers) {
            System.out.println("\nFactoring: " + n);
            List<BigInteger> factors = factorize(n);
            System.out.println("Factors: " + factors);

            // 验证
            BigInteger product = BigInteger.ONE;
            for (BigInteger f : factors) product = product.multiply(f);
            System.out.println("Verification: " + product.equals(n));
        }
    }
}

Java实现:64位优化版本

public class PollardRho64 {

    private static final java.util.Random RAND = new java.util.Random();

    /**
     * 64位Pollard Rho (使用long运算)
     * 适用于n < 2^63 的合数分解
     */
    public static long pollardsRho(long n) {
        if ((n & 1) == 0) return 2;
        if (n % 3 == 0) return 3;

        long c = Math.abs(RAND.nextLong()) % (n - 1) + 1;
        long x = Math.abs(RAND.nextLong()) % (n - 2) + 2;
        long y = x;
        long d = 1;

        while (d == 1) {
            x = (mulMod(x, x, n) + c) % n;
            y = (mulMod(y, y, n) + c) % n;
            y = (mulMod(y, y, n) + c) % n;

            long diff = x > y ? x - y : y - x;
            d = gcd(diff, n);
        }

        return d == n ? pollardsRho(n) : d;
    }

    /** 安全乘法取模: (a*b) % mod, 防止溢出 */
    public static long mulMod(long a, long b, long mod) {
        long result = 0;
        a %= mod;
        while (b > 0) {
            if ((b & 1) == 1) {
                result = (result + a) % mod;
            }
            a = (a << 1) % mod;
            b >>= 1;
        }
        return result;
    }

    /** 欧几里得算法求GCD */
    public static long gcd(long a, long b) {
        while (b != 0) {
            long temp = a % b;
            a = b;
            b = temp;
        }
        return a;
    }

    /**
     * 完整分解n的所有素因子
     */
    public static void factorAll(long n, java.util.List<Long> factors) {
        if (n == 1) return;
        if (isPrime(n)) {
            factors.add(n);
            return;
        }
        long d = pollardsRho(n);
        factorAll(d, factors);
        factorAll(n / d, factors);
    }

    /** 简单素性测试 */
    private static boolean isPrime(long n) {
        if (n < 2) return false;
        if (n == 2 || n == 3) return true;
        if ((n & 1) == 0) return false;
        for (long i = 3; i * i <= n; i += 2) {
            if (n % i == 0) return false;
        }
        return true;
    }

    public static void main(String[] args) {
        long[] tests = {91, 8051, 10403, 1000003L * 1000033L};
        for (long n : tests) {
            java.util.List<Long> factors = new java.util.ArrayList<>();
            factorAll(n, factors);
            java.util.Collections.sort(factors);
            System.out.println(n + " = " + factors);
        }
    }
}

应用:RSA模数分解演示

public class RSABreakDemo {

    /**
     * 演示如何使用Pollard Rho分解RSA模数
     * 注意: 仅适用于小密钥,真实RSA-2048无法分解
     */
    public static void main(String[] args) {
        // 生成两个小素数 (演示用,真实RSA使用1024位以上大素数)
        BigInteger p = new BigInteger("104729");
        BigInteger q = new BigInteger("104743");
        BigInteger n = p.multiply(q);
        BigInteger phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));

        System.out.println("RSA Demo Key:");
        System.out.println("n = " + n);
        System.out.println("p = " + p + " (secret)");
        System.out.println("q = " + q + " (secret)");

        // 使用Pollard Rho分解n
        System.out.println("\nAttacking n with Pollard Rho...");
        long start = System.currentTimeMillis();
        BigInteger factor = PollardRho.pollardsRho(n);
        long elapsed = System.currentTimeMillis() - start;

        System.out.println("Found factor: " + factor + " in " + elapsed + "ms");
        System.out.println("Other factor: " + n.divide(factor));

        // 一旦分解n,即可计算私钥d
        BigInteger e = new BigInteger("65537");
        BigInteger d = e.modInverse(phi);
        System.out.println("\nRecovered private key d = " + d);
    }
}

算法对比与选型

算法 时间复杂度 适用场景 特点
试除法 $O(\sqrt{n})$ n < $10^{12}$ 最简单,确定性强
Pollard Rho $O(n^{1/4})$ $10^{12}$ < n < $10^{20}$ 概率算法,实现简单
ECM (椭圆曲线) 亚指数 中等大小(20-60位) 对中等素因子高效
GNFS (数域筛) $O(e^{(\log n)^{1/3}})$ > $10^{100}$ 目前最快,用于RSA-512等
QS (二次筛) 亚指数 80-100位 GNFS的过渡方案

复杂度分析

指标 复杂度
Pollard Rho期望时间 $O(n^{1/4})$ 次模运算
Floyd判圈空间 $O(1)$
Miller-Rabin单轮 $O(\log n)$ 次模乘
完整因子分解(递归) $O(n^{1/4} \cdot \log n)$

密码学意义

  1. 密钥生成: RSA要求选择足够大的素数,使Pollard Rho不可行
  2. 安全边界: RSA-2048的模数约617位,远超Pollard Rho能力
  3. 侧信道攻击: 实际攻击常利用实现漏洞,而非纯数学分解
  4. 量子威胁: Shor算法可在多项式时间内分解,推动后量子密码研究

进一步优化方向

  1. Brent判圈法: 替代Floyd判圈,减少模运算次数
  2. 参数选择: 动态调整多项式参数$c$,提高成功率
  3. 并行化: 多线程尝试不同参数,首个成功者返回
  4. ECM集成: 对找到的小因子先用ECM验证

Pollard Rho算法以优雅的数学思想和简洁的实现,成为因数分解领域的经典之作。理解其原理不仅能应对算法竞赛中的数论问题,更为深入现代密码学奠定了坚实基础。