AlpacaHack 2025
RSAMPC
import os
from Crypto.Util.number import getRandomRange, getPrime, bytes_to_long
FLAG = os.environ.get("FLAG", "fakeflag").encode()
def additive_share(a):
t0, t1 = getRandomRange(-2**512, 2**512), getRandomRange(-2**512, 2**512)
t2 = a-t0-t1
return t0, t1, t2
def replicated_share(a):
t = additive_share(a)
return [(t[i], t[(i+1)%3]) for i in range(3)]
def multiply_shares(sa, sb):
def mul(t, u):
return t[0]*u[0]+t[0]*u[1]+t[1]*u[0]
r = additive_share(0)
z = [mul(sa[i], sb[i])+r[i] for i in range(3)]
w = [(z[i], z[(i+1)%3]) for i in range(3)]
return w
def reconstruct(s):
return s[0][0] + s[0][1] + s[1][1]
p = getPrime(512)
q = getPrime(512)
sp = replicated_share(p)
sq = replicated_share(q)
print("your share of p:", sp[0])
print("your share of q:", sq[0])
spq = multiply_shares(sp, sq)
print("your share of pq:", spq[0])
n = reconstruct(spq)
assert n == p*q
print("n:", n)
e = 0x10001
c = pow(bytes_to_long(FLAG + os.urandom(127-len(FLAG))), e, n)
print("e:", e)
print("c:", c)
your share of p: (-384164070196680113629973964276599320736606300184523772854135294036334447818682200607218877531386512793858125339877828582394197679795576991953411880314517, 178776721087372919385257940734429604253240493277094581482580652949038337321961407291832241379559936948198042043881180916670462219794291885959730598632423)
your share of q: (-10504102453855211730773548202462643334445368588122773952797120588540073173181223269420294976331168878842123082669069593895980908615299058089156709125348617, 3324659724832936014805633502878093035237335054058544453532695059432217891926271390882999445452501190449380595220556388508799059755133895886341486877191502)
your share of pq: (880194945859095512548778390949753106113259354062743403885130575509194611686622871911550689148439940097472063798899034574466553154127726867674397008987477001207806315461004286936941315001029394217039765579529660629019466179402060549350587729722354909331590051509695082365313846996923469825646557408789955494, 40388351148875096689764230410867470980240794826105168292967479483809364773078955483003274901375600951153408618729650715655666480989756454152565386666760509805904377793675351489295406907138019316039841793386393194481700178651652081449097569147179108704523020190287922457859082133424057955783092523665228634328)
n: 122269467950798077326822634108968850809243750508493781647505745002863843379348700424238562022365315227978807541070854658246091147872559714237246479088170538196473585543281713624525798244748333546435600544573727499127916535316599284592352755786055339638261774730837681190375466416924715653324305527245715836447
e: 65537
c: 100976267335628681910815317357700490412039872278731196009735781349258998302355802361980783540754919888894607253589239383351290237447746132667260747986281172840910605287343986031579879857474734142154881821962810929745626899955618676413832332521656625264015203959361696843594006345498340544121922011105950850715
Observe the additive_share function is called 3 times, creating 6 total unknowns which I’ll call x0, x1, y0, y1, z0, z1.
And of course, p and q are also unknown.
The reconstruction function also seems useless for us solvers.
def additive_share(a, t0, t1):
#t0, t1 = getRandomRange(-2**512, 2**512), getRandomRange(-2**512, 2**512)
t2 = a-t0-t1
return t0, t1, t2
def replicated_share(a, t0, t1):
t = additive_share(a, t0, t1)
return [(t[i], t[(i+1)%3]) for i in range(3)]
def multiply_shares(sa, sb):
def mul(t, u):
return t[0]*u[0]+t[0]*u[1]+t[1]*u[0]
r = additive_share(0, z0, z1)
z = [mul(sa[i], sb[i])+r[i] for i in range(3)]
w = [(z[i], z[(i+1)%3]) for i in range(3)]
return w
var('p q')
var('x0 x1 y0 y1 z0 z1')
sp = replicated_share(p, x0, x1)
sq = replicated_share(q, y0, y1)
print("your share of p:", sp[0])
print("your share of q:", sq[0])
spq = multiply_shares(sp, sq)
print("your share of pq:", spq[0])
That just leaves us with this information
your share of p: (x0, x1)
your share of q: (y0, y1)
your share of pq: (x0*y0 + x1*y0 + x0*y1 + z0, (q - y0 - y1)*x1 + (p - x0 - x1)*y1 + x1*y1 + z1)
We’re given x0, x1, y0 and y1. We can also solve z0 easily from the first equation:
assert z0 == spq[0][0] - (x0*y0 + x1*y0 + x0*y1 )
Then we’re just left with the second equation.
assert spq[0][1] == q*x1 - x1*y0 + p*y1 - x0*y1 - x1*y1 + z1
Multiply by p to get rid of q:
assert p*spq[0][1] == n*x1 - p*x1*y0 + p**2 * y1 - p*x0*y1 - p*x1*y1 + z1*p
And now if we just ignore z1 (eg let it be 0) we can solve for p and get a very very close approximation.
Final solver:
x0, x1 = (-384164070196680113629973964276599320736606300184523772854135294036334447818682200607218877531386512793858125339877828582394197679795576991953411880314517, 178776721087372919385257940734429604253240493277094581482580652949038337321961407291832241379559936948198042043881180916670462219794291885959730598632423)
y0, y1 = (-10504102453855211730773548202462643334445368588122773952797120588540073173181223269420294976331168878842123082669069593895980908615299058089156709125348617, 3324659724832936014805633502878093035237335054058544453532695059432217891926271390882999445452501190449380595220556388508799059755133895886341486877191502)
spq0 = (880194945859095512548778390949753106113259354062743403885130575509194611686622871911550689148439940097472063798899034574466553154127726867674397008987477001207806315461004286936941315001029394217039765579529660629019466179402060549350587729722354909331590051509695082365313846996923469825646557408789955494, 40388351148875096689764230410867470980240794826105168292967479483809364773078955483003274901375600951153408618729650715655666480989756454152565386666760509805904377793675351489295406907138019316039841793386393194481700178651652081449097569147179108704523020190287922457859082133424057955783092523665228634328)
n = 122269467950798077326822634108968850809243750508493781647505745002863843379348700424238562022365315227978807541070854658246091147872559714237246479088170538196473585543281713624525798244748333546435600544573727499127916535316599284592352755786055339638261774730837681190375466416924715653324305527245715836447
e = 65537
c = 100976267335628681910815317357700490412039872278731196009735781349258998302355802361980783540754919888894607253589239383351290237447746132667260747986281172840910605287343986031579879857474734142154881821962810929745626899955618676413832332521656625264015203959361696843594006345498340544121922011105950850715
z0 = spq0[0] - (x0*y0 + x1*y0 + x0*y1 )
PR.<p> = PolynomialRing(RealField(1000))
f = n*x1 - p*x1*y0 + p**2 * y1 - p*x0*y1 - p*x1*y1 - p*spq0[1]
for approx_p in [int(i) for i, _ in f.roots()]:
for p in range(approx_p-100, approx_p+100):
if n%p == 0:
q = n//p
flag = pow(c, pow(e, -1, (p-1)*(q-1)), n)
print(bytes.fromhex(f'{flag:x}'))
# Alpaca{4t7en710n_7o_sm4ll_param37er5!}
addprimes
import os
import signal
from sage.misc.banner import require_version
from Crypto.Util.number import getPrime, bytes_to_long
assert require_version(10), "This challenge requires SageMath version 10 or above"
signal.alarm(30)
FLAG = os.environ.get("FLAG", "Alpaca{*** FAKEFLAG ***}").encode()
assert FLAG.startswith(b"Alpaca{")
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 37
print("n:", n)
print("e:", e)
c = int(input("ciphertext: "))
assert 1 < c < n-1
pari.addprimes(p)
m = mod(c, n).nth_root(e)
print("plaintext:", m)
padded_flag = FLAG + os.urandom(127-len(FLAG))
print("encrypted flag:", pow(bytes_to_long(padded_flag), e, n))
We choose some c, receive mod(c, n).nth_root(e)
, and must use this to get p or q.
My idea is choose c = x**e for some random x.
Then there’s a chance we receive some m such that m ≡ x mod p.
In that case, we can take p = gcd(m-x, n) :)
So I ran this for a while
from os import environ
environ['TERM'] = 'konsole'
from pwn import remote
from math import gcd
def main():
e = 37
x = 2 # random
c = x**e
while True:
io = remote('34.170.146.252', 20209)
n = int(io.recvline().split()[-1])
print(f'{n = }')
io.recvline()
io.sendline(str(c).encode())
m = int(io.recvline().split()[-1])
p = gcd(m-x, n)
print(f'{p = }')
print(1<p<n)
ct = int(io.recvline().split()[-1])
print(f'{ct = }')
io.close()
if 1<p<n:
return
main()
and found:
n = 114783708399698960108264738054025145565535931640960999123387272969093599238187181373730111997294459975870565267518303558948000404485473308361774135465316252509197324620785772722459158341889447987572103958648599953204583497777731923408413905249543515516308414354177122583425216386764347082254251512033916977109
p = 11449730474587971262122289098680230045722988528466053486814133748804539665648353523162475259451265043814468677887855417914321306551770172524009610111075847
ct = 8172678213140054371240953996928112834530795207936773004164588627685749400433771007465676410907652762068158858567560176952250679582262129011487575337588475342755491452494415526321332283145276807676165921618511466237798151055970302819981173222405864150917608363173174596196189642400056796077399966472137429900
and now just decrypting:
from Crypto.Util.number import *
e = 37
pari.addprimes(p)
for flag in Zmod(n)(ct).nth_root(e, all=True):
flag = long_to_bytes(int(flag))
if b'Alpaca' in flag:
print(flag)
# Alpaca{k3ym0on's_favori7e_73chn1que!_x.com/kymn_/status/1527738058744791042}
Author’s writeup: https://chocorusk.hatenablog.com/entry/2025/01/26/180123