概述
光滑数 (Smooth number):指可以分解为小素数乘积的正整数
当p是N 的因数,并且p−1是光滑数,可以考虑使用Pollard's p-1
算法来分解N
当p是N的因数,并且p+1是光滑数,可以考虑使用Williams's p+1
算法来分解N
Pollard的p-1算法
算法核心
为了分解合数n,设数n的一个因子是p,若p − 1的每个因子q满足 q ≤ B q\le B q≤B(B是自己选的一个数,称为界),此时必有
( p − 1 ) ∣ B ! (p-1) | B! (p−1)∣B!
证:设 p − 1 = q 1 q 2 q 3 … q m p-1=q_{1}q_{2}q_{3}\dots q_{m} p−1=q1q2q3…qm,并且 q 1 , q 2 , q 3 , … , q m ≤ B q_{1},q_{2},q_{3},\dots ,q_{m} \le B q1,q2,q3,…,qm≤B,显然上式成立
Pollard的p − 1算法是直接计算 a ≡ 2 B ! ( m o d n ) a \equiv 2^{B!} \pmod{n} a≡2B!(modn)和 gcd ( a − 1 , n ) \gcd(a-1, n) gcd(a−1,n),这个最大公因子就是n的一个非平凡因子。
证:由于p | n,所以有
a ≡ 2 B ! ( m o d p ) a \equiv 2^{B!} \pmod{p} a≡2B!(modp)
又根据Fermat定理,
2 p − 1 ≡ 1 ( m o d p ) 2^{p-1} \equiv 1 \pmod{p} 2p−1≡1(modp)
因为 ( p − 1 ) ∣ B ! (p-1) | B! (p−1)∣B!,所以
a ≡ 2 B ! ≡ 1 ( m o d p ) a \equiv 2^{B!}\equiv 1 \pmod{p} a≡2B!≡1(modp)
即 p|(a-1),于是p就是a − 1和n的公因子。最大公因子当然是其中一个
算法评价
所以只要B取值合理,可以在多项式时间计算出结果。但是B必须满足大于p − 1的所有因子,如果p − 1的因子很大,选择小的B会造成算法求解失败,选择足够大的B虽然会增加算法成功的概率,但那样的话算法的复杂度不一定比试除法好。
算法实现
def Pollard(B, n):
a = pow(2, math.factorial(B), n)
return math.gcd(a-1, n)
Williams的p+1算法
算法思想略(太难了,看不懂)
这两种光滑都可以用python里的第三方库primefac求解,自行pip install
pollardrho_brent(n)
Brent’s improvement on Pollard’s rho algorithm. Returns
n
ifn
is prime; otherwise, we keep chugging until we find a factor ofn
strictly between1
andn
.
pollard_pm1(n, B1=100, B2=1000)
Pollard’s p+1 algorithm, two-phase version. Returns
n
ifn
is prime; otherwise, we keep chugging until we find a factor ofn
strictly between1
andn
.
williams_pp1(n)
Williams’ p+1 algorithm. Returns
n
ifn
is prime; otherwise, we keep chugging until we find a factor ofn
strictly between1
andn
.
例题
import gmpy2
from libnum import n2s,s2n
from Crypto.Util.number import getPrime
import random
def gen_prime(digit):
primes = []
pri = 1
while(len(primes)<100):
pri = gmpy2.next_prime(pri)
primes.append(int(pri))
while True:
count = 2
while count < 2**digit:
count *= random.choice(primes)
count += 1
if(gmpy2.is_prime(count)):
return count
def gen_prime_2(digit):
primes = []
pri = 1
while(len(primes)<100):
pri = gmpy2.next_prime(pri)
primes.append(int(pri))
while True:
count = 2
while count < 2**digit:
count *= random.choice(primes)
count -= 1
if(gmpy2.is_prime(count)):
return count
def generate(start, modulus, a, b, c, d):
arr = []
arr.append(start)
arr.append(start+1)
arr.append(start+2)
for i in range(10**100):
arr.append((a * arr[-3] + b * arr[-2] + c * arr[-1] + d) % modulus)
return arr
p1 = gen_prime(256)
q1 = getPrime(256)
p2 = gen_prime_2(256)
q2 = getPrime(256)
assert p1 > q1
assert p2 > q2
n1 = p1 * q1
n2 = p2 * q2
print "n1 = %s" % str(n1)
print "n2 = %s" % str(n2)
r = getPrime(512)
print "r = %s" % str(r)
flag = "flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"
m = s2n(flag)
arr = generate(m, r, p1, q1, p2, q2)
print "arr[10**100] = %s" % str(arr[10**100])
print "arr[10**100+1] = %s" % str(arr[10**100+1])
print "arr[10**100+2] = %s" % str(arr[10**100+2])
# n1 = 1122627862697321019530282965736391850755580895936802291161309915792429961624747356094273651528053737694375752383507509008511083571424513544351844231796981247
# n2 = 42452228756074949430200119187674072800166259263276225653674599426683428744745000585507174701894408343398957593869403921976682474759688236534918012868451437
# r = 12562551311997602982227662257453842057851021749009292708783465660065261139703971906109320639580310598023202634257624805719964182261882169860035285540999137
# arr[10**100] = 4838353389408955215917845462224851356983185785715321739245030612236910363177888113470205298612968999913978808363082599570155255107799227941046736259627961
# arr[10**100+1] = 10699223576890057648654189340104320126576219437282628103579325446469238279292850193524138389756436672641376651989022132710127197616146958216805814995598472
# arr[10**100+2] = 10255743354332378416989087696439685123717830836779737961024112448965245238772080092701344614830923313888435306261206177140560579750917219609679498132161402
三个考点:
- Pollard的p-1算法
- Williams的p+1算法
- 矩阵快速幂的运用(generate函数里的线性递推有pow(10,100)次,一个个递推是出不来的)
flag{9d3c102a-4fbc-9920-2da2-6b1e047cab37}
sagemath中的矩阵指数运算自带快速幂,附sagemath脚本:
x = #倒数第三项
y = #倒数第二项
z = #最后一项
a =
b =
c =
d =
r =
mt=matrix(Zmod(r),4,4)
mt[0]=[c,b,a,d]
mt[1]=[1,0,0,0]
mt[2]=[0,1,0,0]
mt[3]=[0,0,0,1]
mn=matrix(Zmod(r),4,1,[z,y,x,1])
X=(mt^(pow(10,100))).solve_right(mn)
print(X)