# Pollard's p − 1 algorithm ## Overview * Thuật toán này được John Pollard vào năm 1974 * Thuật toán này mạnh trong việc phân tích thừa số nguyên tố của một **"smooth number"** là số khi ta phân tích ra thành các số nguyên tố nhỏ. ## Base Concepts Cho $n$ là một hợp số có thừa số nguyên tố là $p$. Theo định lý **Fermat's little**. Với mọi số nguyên $a$ nguyên tố cùng nhau với $n$. Ta có: $$a^{K(p-1)} \equiv 1 \pmod p \Rightarrow gcd(a^{K(p-1)}-1, n) = p$$ * Ý tưởng làm cho số mũ là một bội của $(p-1)$ bằng cách biến nó thành một số với rất nhiều thừa số nguyên tố. Nói chung chúng ta lấy tích của tất cả các lũy thừa nguyên tố nhỏ hơn một giới hạn B nào đó. * Bắt đầu với một số nguyên $x$ và liên tục thay thế nó bằng $x^w \bmod n$ khi $w$ chạy qua những lũy thừa nguyên tố đó. * Kiểm tra ở mỗi giai đoạn xem $gcd(x-1, n)$ nếu như nó khác 1 thì ta nhận đó là một thừa số nguyên tố của $n$. ## Algorithm * **Input:** một số nguyên n * **Output:** danh sách các thừa số nguyên tố của n hoặc thất bại. * Chọn một giới hạn $B$ (các thừa số nguyên tố sẽ nhỏ hơn B). * Xác định $M = factorial(B)$ * Chọn một số nguyên dương $a$ nguyên tố cùng nhau với $n$, nếu như $n$ lẻ thì ta luôn chọn $a = 2$. * Tính toán $gcd(a^{M} -1 , n)$ * Nếu: * $1<g<n$: thì $g$ là một thừa số nguyên tố. * $g = 1$ chọn $B$ lớn hơn và thử lại. * $g = n$ chọn $B$ nhỏ hơn và thử lại. Dưới đây là một đoạn script khởi tạo thuật toán: ```python= from gmpy2 import fac from math import gcd from Crypto.Util.number import * from sympy import true n = 1403421 a = 2 B = 100 while True: if isPrime(n): print(f"[+] p factor : {n}") print("[+] Finished") break M = fac(B) tmp = pow(a, M, n) - 1 g = gcd(tmp, n) #print(B) if g == 1: B += 1 elif g == n: B -= 1 else: print(f"[+] p factor : {g}") n = n//g ``` ``` [+] p factor : 2073 [+] p factor : 677 [+] Finished ``` Như vậy với việc xác định được bound thì ta có thể dễ dàng factor được "smooth number". ## Example Giờ ta sẽ đến với một challenge Crypto như sau: Ta có source code: ```python= #!/usr/bin/python from binascii import hexlify from gmpy2 import * import math import os import sys if sys.version_info < (3, 9): math.gcd = gcd math.lcm = lcm _DEBUG = False FLAG = open('flag.txt').read().strip() FLAG = mpz(hexlify(FLAG.encode()), 16) SEED = mpz(hexlify(os.urandom(32)).decode(), 16) STATE = random_state(SEED) def get_prime(state, bits): return next_prime(mpz_urandomb(state, bits) | (1 << (bits - 1))) def get_smooth_prime(state, bits, smoothness=16): p = mpz(2) p_factors = [p] while p.bit_length() < bits - 2 * smoothness: factor = get_prime(state, smoothness) p_factors.append(factor) p *= factor bitcnt = (bits - p.bit_length()) // 2 while True: prime1 = get_prime(state, bitcnt) prime2 = get_prime(state, bitcnt) tmpp = p * prime1 * prime2 if tmpp.bit_length() < bits: bitcnt += 1 continue if tmpp.bit_length() > bits: bitcnt -= 1 continue if is_prime(tmpp + 1): p_factors.append(prime1) p_factors.append(prime2) p = tmpp + 1 break p_factors.sort() return (p, p_factors) e = 0x10001 while True: p, p_factors = get_smooth_prime(STATE, 1024, 16) if len(p_factors) != len(set(p_factors)): continue # Smoothness should be different or some might encounter issues. q, q_factors = get_smooth_prime(STATE, 1024, 17) if len(q_factors) != len(set(q_factors)): continue factors = p_factors + q_factors if e not in factors: break if _DEBUG: import sys sys.stderr.write(f'p = {p.digits(16)}\n\n') sys.stderr.write(f'p_factors = [\n') for factor in p_factors: sys.stderr.write(f' {factor.digits(16)},\n') sys.stderr.write(f']\n\n') sys.stderr.write(f'q = {q.digits(16)}\n\n') sys.stderr.write(f'q_factors = [\n') for factor in q_factors: sys.stderr.write(f' {factor.digits(16)},\n') sys.stderr.write(f']\n\n') n = p * q m = math.lcm(p - 1, q - 1) d = pow(e, -1, m) c = pow(FLAG, e, n) print(f'n = {n.digits(16)}') print(f'c = {c.digits(16)}') ``` **output.txt** ``` n = 4f7aa864f662a42a92220e372f5ff25a142aef26106a0dbdf573a66594966ac5dd03848745bb6a80402cad7ac6f2bf93f9ed840edd9c157dfd5d265ce2403e155a29666df8f9b98167ad2452e5a63fd0b7b14ffe966db60c6e2c65b0f602f5c22eb030c0335187759909abd4df622118c23463bcc42650e0a7761257452bf40069ca50dbe0c922d8823a9dcc4231b3952d31d1e977cb520528c6a450405f2a2ee6134db8c61ceb4478a647b0469712cc4f3d1369ef3dfd3d876a2c77bac5a149ccf3723a6e8c3ba1deb0675f25def8da9de2b3ac8b3e38d5ac5c9736b9af087b3fc53450136428e07d58fbc00f6609a4cc14eb0a13a7e76056a241256e03e95d c = e41a61908eb48b85dc78975c288e62a271b1f237fdc958162727d2930b9af850e908137655c5955a078ff1aa63f5509fbaf79d179d24d209a061c36e0709437b8d2641f41d354bdea062084ea3be8637ed1c4bd8cf63d16c942976dd9d6188fc5e419afae17493d7cdb93d84052637d15e7fa1f852f4f5d786c86bfd024df0dfcf8431e7230cfbbce76a1835b178020ef839af42c377706918a50aac56f79285d743f4a177425eb00eaeb2bebe99343911ab653fe64bb61e140153b113f8554fe29561756fafc7460683d59dd3ee50eb48b718443b9f49e663b6dd02b0a15297468ec30a4f487e328103cdbc59d1d66fc4f03ef75ae45d6ce2035fdfaeb86b7 ``` Đọc source code ở trên ta nhận thấy các **"smooth prime"** được tạo từ các số nguyên tố 16 bit và 17 bit nên ta dễ dàng tìm được Bound của nó là $B_p = 2^{16} + 1$ và $B_q = 2^{17} + 1$ Ta sẽ sử dụng thuật toán **Pollard's p-1** để factor N. Tại vì N chỉ có 2 thừa số nguyên tố nên ta chỉ cần sử dụng một Bound thì đã factor ra được $p$ và $q$. ![image](https://hackmd.io/_uploads/r10ib6rLC.png) Khi đã factor được thì mọi chuyện trở nên dễ dàng rồi. :monkey: ---END---