https://www.math.u-bordeaux.fr/~gcastagn/publi/crypto_quad.pdf
https://www.researchgate.net/publication/26623030_A_New_Computation_Algorithm_for_a_Cryptosystem_Based_on_Lucas_Functions
最近透過qwb瞭解到了這個新東西,順手進一步加深了對於LUCAS序列的理解。
典型例題
UMass CTF 2021 - Weird RSA
import random
from Crypto.Util.number import isPrime
m, Q = """REDACTED""", 1
def genPrimes(size):
base = random.getrandbits(size // 2) << size // 2
base = base | (1 << 1023) | (1 << 1022) | 1
while True:
temp = base | random.getrandbits(size // 4)
if isPrime(temp):
p = temp
break
while True:
temp = base | random.getrandbits(size // 4)
if isPrime(temp):
q = temp
break
return (p, q)
def pow(m, e, n):
return v(e)
def v(n):
if n == 0:
return 2
if n == 1:
return m
return (m*v(n-1) - Q*v(n-2)) % N
p, q = genPrimes(1024)
N = p * q
e = 0x10001
print("N:", N)
print("c:", pow(m, e, N))
"""
N: 18378141703504870053256589621469911325593449136456168833252297256858537217774550712713558376586907139191035169090694633962713086351032581652760861668116820553602617805166170038411635411122411322217633088733925562474573155702958062785336418656834129389796123636312497589092777440651253803216182746548802100609496930688436148522617770670087143010376380205698834648595913982981670535389045333406092868158446779681106756879563374434867509327405933798082589697167457848396375382835193219251999626538126258606572805220878283429607438382521692951006432650132816122705167004219371235964716616826653226062550260270958038670427
c: 14470740653145070679700019966554818534890999807830802232451906444910279478539396448114592242906623394239703347815141824698585119347592990685923384931479024856262941313458084648914561375377956072245149926143782368239175037299219241806241533201175001088200209202522586119648246842120571566051381821899459346757935757111233323915022287370687524912870425787594648397524189694991735372527387329346198018567010117587531474035014342584491831714256980975368294579192077738910916486139823489975038981139084864837358039928972730135031064241393391678984872799573965150169368237298603189344477806873779325227557835790957023000991
"""
LUC-RSA Solution
So how does it work? Well to encrypt (as we’ve seen) we calculate the e-th order Lucas sequence with P=m and Q=1. Then, to decrypt we calculate the d-th order Lucas sequence with P=c and Q=1. Huh, sounds quite neat. Our next step is to find the private exponent d, however there’s a catch. We cannot use our familiar
那麼它是如何工作的呢?好吧,為了加密(正如我們所看到的),我們計算 P=m 和 Q=1 的第 e 階盧卡斯序列。然後,為了解密,我們計算 P=c 和 Q=1 的第 d 階盧卡斯序列。呵呵,聽起來很整潔。我們的下一步是找到私有指數 d,但有一個問題。我們不能使用我們熟悉的
d = ~e % phi(N),
instead we use 取而代之的是,我們使用
d = ~e % LCM( (p +- 1), (q +- 1) ).
Now we end up with four possible decryption keys. We could easily try them all out, but we can also find the proper form from
現在我們最終得到了四個可能的解密金鑰。我們可以很容易地嘗試它們,但我們也可以從中找到合適的形式
d = ~e % LCM( (p - LS(D/p)), (q - LS(D/q)) )
where LS is the Legendre symbol and D = C^2 - 4 the discriminant (abc-formula, anyone?). Finally, just to speed things up, we implement a more efficient encryption function (provided by the chall’s author: Soul). Now we can go and get ourselves a nice flag 😃.
其中 LS 是勒讓德符號,D = C^2 - 4 是判別式(abc 公式,有人嗎?最後,為了加快速度,我們實現了一個更有效的加密功能(由 chall 的作者 Soul 提供)。現在我們可以去給自己買一面漂亮的旗幟:)了。
import gmpy2
from Crypto.Util.number import long_to_bytes
import sys
# Increase the Python recursion limit
sys.setrecursionlimit(5000)
# RSA Parameters
N = 18378141703504870053256589621469911325593449136456168833252297256858537217774550712713558376586907139191035169090694633962713086351032581652760861668116820553602617805166170038411635411122411322217633088733925562474573155702958062785336418656834129389796123636312497589092777440651253803216182746548802100609496930688436148522617770670087143010376380205698834648595913982981670535389045333406092868158446779681106756879563374434867509327405933798082589697167457848396375382835193219251999626538126258606572805220878283429607438382521692951006432650132816122705167004219371235964716616826653226062550260270958038670427
C = 14470740653145070679700019966554818534890999807830802232451906444910279478539396448114592242906623394239703347815141824698585119347592990685923384931479024856262941313458084648914561375377956072245149926143782368239175037299219241806241533201175001088200209202522586119648246842120571566051381821899459346757935757111233323915022287370687524912870425787594648397524189694991735372527387329346198018567010117587531474035014342584491831714256980975368294579192077738910916486139823489975038981139084864837358039928972730135031064241393391678984872799573965150169368237298603189344477806873779325227557835790957023000991
E = 0x10001
# I used Fermat's factorisation method to get p and q using SAGE
P = 135566004969921829046861317679102794894163252891621004552763069255612086965641424719754859767153782381891044077537624735662301899417143962916558791489710971298124937969427903581890089403413545652984524659790357002447801666607195021452029447206533810446315939039775701027454771450154054624400219767469987538497
Q = 135566004969921829046861317679102794894163252891621004552763069255612086965641424719754859767153782381891044077537624735662301899417143962916558791489710971298124937969427903581890089403413545652984524659790357002447801666607195021441224446867180097273643121640903324702747770969633717818870639347019154977691
# LUCRSA Cryptosystem (based on second order Lucas sequence (!))
# See https://www.researchgate.net/publication/26623030_A_New_Computation_Algorithm_for_a_Cryptosystem_Based_on_Lucas_Functions
D = C**2 - 4
LS_P = gmpy2.legendre(D,P)
LS_Q = gmpy2.legendre(D,Q)
# Get that decryption bread
d = gmpy2.invert(E, gmpy2.lcm(P-LS_P, Q-LS_Q))
# If you can, prevent v_dict{} from initialising again as it'll save you time for future computations :)
v_dict = {}
# Function I got from Soul, what a nice guy!
def v(n):
if n == 0:
return 2
if n == 1:
return m
if n in v_dict.keys():
return v_dict[n]
if(n % 2 == 0):
ret = (pow(v(n // 2), 2, N) - 2 * pow(Q, n, N)) % N
else:
ret = (m * pow(v(n // 2), 2, N) - Q * v(n // 2) * v((n // 2) - 1) - m * pow(Q, n, N)) % N
v_dict[n] = ret
return ret
m = C; Q = 1
print('Capturing the flag...')
print()
flag = v(d)
print('Got it!')
print(long_to_bytes(flag))
hfctf2022 RRSSAA(https://ctf.njupt.edu.cn/archives/740#RRSSAA)
n = p2*q2
https://www.math.u-bordeaux.fr/~gcastagn/publi/crypto_quad.pdf
from sage.rings.finite_rings.integer_mod import lucas
import gmpy2
from Crypto.Util.number import long_to_bytes
import sys
sys.setrecursionlimit(5000)
N = 59969098213446598961510550233718258878862148298191323654672950330070587404726715299685997489142290693126366408044603303463518341243526241117556011994804902686998166238333549719269703453450958140262475942580009981324936992976252832887660977703209225426388975233018602730303262439218292062822981478737257836581
E = 970698965238639683403205181589498135440069660016843488485401994654202837058754446853559143754852628922125327583411039117445415303888796067576548626904070971514824878024057391507617988385537930417136322298476467215300995795105008488692961624917433064070351961856959734368784774555385603000155569897078026670993484466622344106374637350023474339105113172687604783395923403613555236693496567851779400707953027457705617050061193750124237055690801725151098972239120476113241310088089420901051617493693842562637896252448161948655455277146925913049354086353328749354876619287042077221173795354616472050669799421983520421287
C = 2757297249371055260112176788534868300821961060153993508569437878576838431569949051806118959108641317578931985550844206475198216543139472405873345269094341570473142756599117266569746703013099627523306340748466413993624965897996985230542275127290795414763432332819334757831671028121489964563214463689614865416498886490980692515184662350519034273510244222407505570929178897273048405431658365659592815446583970229985655015539079874797518564867199632672678818617933927005198847206019475149998468493858071672920824599672525667187482558622701227716212254925837398813278836428805193481064316937182435285668656233017810444672
P = 7743971733771153102128801312798743998017713722732925283466018690899116898707556486947918196848489007935614742583856884731087798825462330340492923214926391
Q = 7743971733771153105036156209981171560215008954284943420880584133648389139833517283670475349302080701240378945438911146974137885250527042074631329729385091
assert P*Q == N
D = C**2 - 4
LS_P = gmpy2.legendre(D,P)
LS_Q = gmpy2.legendre(D,Q)
d = gmpy2.invert(E, gmpy2.lcm(P-LS_P, Q-LS_Q))
inv_q, inv_p = inverse_mod(P, Q), inverse_mod(Q, P)
rp, rq = lucas(k=inverse_mod(E, P-LS_P), P=C, Q=1, n=P)[0], lucas(k=inverse_mod(E, Q-LS_Q), P=C, Q=1, n=Q)[0]
r = crt(int(rp),int(rq),P,Q)
vp=lucas(k=E, P=r, Q=1, n=P*P)[0]
tmp_p = C * inverse_mod(int(vp),P*P) %(P*P)
tmp_p = int(tmp_p - 1) // P
mp = int(tmp_p * inv_p % P)
vq=lucas(k=E, P=r, Q=1, n=Q*Q)[0]
tmp_q = C * inverse_mod(int(vq), Q*Q)%(Q*Q)
tmp_q = int(tmp_q - 1) // Q
mq = int(tmp_q * inv_q % Q)
flag = crt(mp,mq,P,Q)
print(long_to_bytes(flag))
官方板子:
from gmpy2 import next_prime, iroot
from Crypto.Util.number import getPrime, inverse, GCD, bytes_to_long, long_to_bytes
from sage.all import *
def attack2(N, e, m, t, X, Y):
PR = PolynomialRing(QQ, 'x,y', 2, order='lex')
x, y = PR.gens()
A = -(N-1)**2
F = x * y**2 + A * x + 1
G_polys = []
# G_{k,i_1,i_2}(x,y) = x^{i_1-k}y_{i_2-2k}f(x,y)^{k}e^{m-k}
for k in range(m + 1):
for i_1 in range(k, m+1):
for i_2 in [2*k, 2*k + 1]:
G_polys.append(x**(i_1-k) * y**(i_2-2*k) * F**k * e**(m-k))
H_polys = []
# y_shift H_{k,i_1,i_2}(x,y) = y^{i_2-2k} f(x,y)^k e^{m-k}
for k in range(m + 1):
for i_2 in range(2*k+2, 2*k+t+1):
H_polys.append(y**(i_2-2*k) * F**k * e**(m-k))
polys = G_polys + H_polys
monomials = []
for poly in polys:
monomials.append(poly.lm())
dims1 = len(polys)
dims2 = len(monomials)
MM = matrix(QQ, dims1, dims2)
for idx, poly in enumerate(polys):
for idx_, monomial in enumerate(monomials):
if monomial in poly.monomials():
MM[idx, idx_] = poly.monomial_coefficient(monomial) * monomial(X, Y)
B = MM.LLL()
found_polynomials = False
for pol1_idx in range(B.nrows()):
for pol2_idx in range(pol1_idx + 1, B.nrows()):
P = PolynomialRing(QQ, 'a,b', 2)
a, b = P.gens()
pol1 = pol2 = 0
for idx_, monomial in enumerate(monomials):
pol1 += monomial(a,b) * B[pol1_idx, idx_] / monomial(X, Y)
pol2 += monomial(a,b) * B[pol2_idx, idx_] / monomial(X, Y)
# resultant
rr = pol1.resultant(pol2)
# are these good polynomials?
if rr.is_zero() or rr.monomials() == [1]:
continue
else:
print(f"found them, using vectors {pol1_idx}, {pol2_idx}")
found_polynomials = True
break
if found_polynomials:
break
if not found_polynomials:
print("no independant vectors could be found. This should very rarely happen...")
PRq = PolynomialRing(QQ, 'z')
z = PRq.gen()
rr = rr(z, z)
soly = rr.roots()[0][0]
ppol = pol1(z, soly)
solx = ppol.roots()[0][0]
return solx, soly
def seq(r, k, m):
v = vector(Zmod(m), [r, 2])
if k >= 2:
M = Matrix(Zmod(m), [[r, -1], [1, 0]])
v = (M**(k-1)) * v
ret = v[0] if k != 0 else v[1]
return int(ret)
def legendre_symbol(a, p):
""" Compute the Legendre symbol a|p using
Euler's criterion. p is a prime, a is
relatively prime to p (if p divides
a, then a|p = 0)
Returns 1 if a has a square root modulo
p, -1 otherwise.
"""
ls = pow(2,(p-1)//2,p)
return -1 if ls == p - 1 else ls
def decrypt(c, e, p, q):
d_p = {1: int(pow(e, -1, p-1)), -1: int(pow(e, -1, p+1))}
d_q = {1: int(pow(e, -1, q-1)), -1: int(pow(e, -1, q+1))}
inv_q = int(pow(p, -1, q))
inv_p = int(pow(q, -1, p))
i_p = legendre_symbol(c**2-4, p)
i_q = legendre_symbol(c**2-4, q)
r_p = seq(c, d_p[i_p], p)
r_q = seq(c, d_q[i_q], q)
r = CRT([r_p, r_q], [p, q])
v_rp = seq(r, e, p**2)
t_p = int((c * pow(v_rp, -1, p**2)) % p**2)
s_p = (t_p - 1) // p
v_rq = seq(r, e, q**2)
t_q = int((c * pow(v_rq, -1, q**2)) % q**2)
s_q = (t_q - 1) // q
m_p = (s_p * inv_p) % p
m_q = (s_q * inv_q) % q
m = CRT([m_p, m_q], [p, q])
return m
if __name__ == '__main__':
n = 59969098213446598961510550233718258878862148298191323654672950330070587404726715299685997489142290693126366408044603303463518341243526241117556011994804902686998166238333549719269703453450958140262475942580009981324936992976252832887660977703209225426388975233018602730303262439218292062822981478737257836581
e = 970698965238639683403205181589498135440069660016843488485401994654202837058754446853559143754852628922125327583411039117445415303888796067576548626904070971514824878024057391507617988385537930417136322298476467215300995795105008488692961624917433064070351961856959734368784774555385603000155569897078026670993484466622344106374637350023474339105113172687604783395923403613555236693496567851779400707953027457705617050061193750124237055690801725151098972239120476113241310088089420901051617493693842562637896252448161948655455277146925913049354086353328749354876619287042077221173795354616472050669799421983520421287
c = 2757297249371055260112176788534868300821961060153993508569437878576838431569949051806118959108641317578931985550844206475198216543139472405873345269094341570473142756599117266569746703013099627523306340748466413993624965897996985230542275127290795414763432332819334757831671028121489964563214463689614865416498886490980692515184662350519034273510244222407505570929178897273048405431658365659592815446583970229985655015539079874797518564867199632672678818617933927005198847206019475149998468493858071672920824599672525667187482558622701227716212254925837398813278836428805193481064316937182435285668656233017810444672
alpha = ZZ(e).nbits() / ZZ(n).nbits()
beta = 0.44
nbits = 1024
delta = 0.63
X = 2 ** int(nbits*(alpha+delta-2)+3)
Y = 2 ** int(nbits*beta+3)
x, y = map(int, attack2(n, e, 8, 12, X, Y))
p_minus_q = y
p_plus_q = iroot(p_minus_q**2 + 4 * n, 2)[0]
p = (p_minus_q + p_plus_q) // 2
q = n // p
assert p * q == n
phi = (p**2 - 1) * (q**2 - 1)
d = inverse(e, phi)
m = decrypt(c, e, p, q)
print(long_to_bytes(m))