[Crypto] Serifin
This is a puzzle from the ASIS CTF Final 2019.
The first crypto puzzle we solved was Serifin. We were given , a product of two primes, and where is our message. This is a RSA-like construction, and our first goal was to factorize
N = 420908150499931060459278096327098138187098413066337803068086719915371572799398579907099206882673150969295710355168269114763450250269978036896492091647087033643409285987088104286084134380067603342891743645230429893458468679597440933612118398950431574177624142313058494887351382310900902645184808573011083971351;
c = 78643169701772559588799235367819734778096402374604527417084323620408059019575192358078539818358733737255857476385895538384775148891045101302925145675409962992412316886938945993724412615232830803246511441681246452297825709122570818987869680882524715843237380910432586361889181947636507663665579725822511143923;
In general, this is hard. Fortunately, we were given the method where and were constructed.
def serifin(a, l):
S, s = a, a
while True:
S += float(a)/float(l)
if S - s < .0001:
return int(S) + 1
else:
s, a = S, float(a)/float(l)
def genPrime(nbit):
while True:
p = getPrime(512)
if p % 9 == 1 and p % 27 >= 2:
q = gmpy2.next_prime(serifin(p, 3) + serifin(p, 9) + serifin(p, 27))
if q % 9 == 1 and q % 27 >= 2:
return int(p), int(q)
We made a wrong step initially, and assumed Then, we can obtain from by
As we can perform a binary search as follows. We guess a and compute If we need to increase Similarly, if we need to decrease
Did you spot our bug? We neglected the floating point rounding of Python. Fortunately, the idea that is generated from and increases if and only if increases still hold. Thus, we can perform the following binary search.
using Pkg; Pkg.add("Primes");
using Primes
# Conversion of Serifin to Julia
function Serifin(a, l)
S = s = a
while true
S += convert(Float64, a)/convert(Float64, l)
S = convert(Float64, S)
S - s < .0001 && return convert(BigInt, floor(S)) + 1
s, a = S, convert(Float64, a)/convert(Float64, l)
end
end
# Compute q_of(p) given p
q_of(p) = nextprime(Serifin(p,3) + Serifin(p, 9) + Serifin(p, 27))
function BinSearch(N)
Low = big(1)
# As we are searching for a prime factor of N, one of them is at most sqrt(N)
High = big(convert(BigInt, floor(sqrt(N))))
while High - Low > 1 # The answer lies in [Low, High)
p0 = (Low + High) ÷ 2
N0 = p0*q_of(p0)
if N0 > N
High = p0
else
Low = p0
end
end
return Low
end
p = BinSearch(N); q = N ÷ p;
println("p = $p")
println("q = $q")
Now we have obtained and our final step is to find The standard procedure is to compute the following constants.
Next, we compute such that yielding
where the last equality is due to Fermat's Little Theorem. We do the same thing for and use Chinese Remainder Theorem to reconstruct We run into a slight problem here! The values and do not exist as the greatest common divisors Fortunately, not all hope is lost. We can attempt to factorise in
Pkg.add("Nemo")
using Nemo
F, _ = FiniteField(p, 1, "a") # Creates a Finite Field of p^1
R, x = PolynomialRing(F, "x")
PossibleMp = [p - parse(BigInt, string(evaluate(i, 0))) for (i, _) in Nemo.factor(x^3 - c)]
This gives us three possibilities for and we can do similarly for
F, _ = FiniteField(q, 1, "a")
R, x = PolynomialRing(F, "x")
PossibleMq = [q - parse(BigInt, string(evaluate(i, 0))) for (i, _) in Nemo.factor(x^3 - c)]
Finally, we can reassemble all 9 possible and decipher our message.
function long_to_bytes(Val)
Val == 0 && return ""
return long_to_bytes(Val ÷ 256) * Char(Val % 256)
end
for Mp in PossibleMp
for Mq in PossibleMq
M = (Mp*q*invmod(q, p) + Mq*p*invmod(p, q)) % N
Str = long_to_bytes(M)
occursin("ASIS", Str) && println(match(r"ASIS{.*}",Str).match)
end
end