The Merkle-Hellman cryptosystem is broken, but what if I use it over elliptic curves?
This is the challenge code:
#!sagefrom sage.all import EllipticCurve, GFfrom Crypto.Util.number import bytes_to_longflag =b'SSMCTF{REDACTED}'#secp256k1p =0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2Fa =0b =7E =EllipticCurve(GF(p),(a,b))A = [E.random_point()for _ inrange(42)]enc = []m =bin(bytes_to_long(flag))[2:]m = m.zfill(len(m)//42*42+42)# makes the number of bits a multiple of 42for i inrange(0,len(m),42): buffer =Nonefor j, bit inenumerate(m[i:i+42]):# the buffer is the summation of points wherein j has a 1 bitif bit =='1':ifnot buffer: buffer = A[j]else: buffer += A[j] enc.append(buffer)A = [i.xy()for i in A]enc = [i.xy()for i in enc]print(f'{A = }')print(f'{enc = }')
Output:
It does the following:
Create a secp256k1 curve
Create an array A containing 42 random points on the curve
Converts the flag from a bytestring to a bitstring whose length is a multiple of 42
For each chunk of 42 bits in the flag, if bit i is 1 then add the point A[i] to buffer. Otherwise do not add the point.
Once all bits in the chunk are done, append the resultant point into enc
Give A and enc .
We can basically think of this as a knapsack cryptosystem, where each random point in A is either added or not added into buffer for every chunk.
We can start by trying to solve the problem for an individual chunk of 42 bits.
It would take too much time to calculate all possible chunk values since there are 242 possible resultant points (because there are 42 random points in A, and each is either used or not used).
However, we can use a meet-in-the-middle approach:
For the first 21 points in A, we calculate all 221 possible results of point addition and have some dictionary d1 storing point : bitstring mappings where a given point can be produced by the associated bitstring. Note that 221=2097152 operations can be completed in a reasonable amount of time.
We do the same for the last 21 points in A for dictionary d2.
For every chunk we need to solve for, we have the encrypted point p. We iterate through the 221 entries in d1 . Then for each point D in d1, we calculate x = p - D. If x is present in d2 then we know that p = D + x. This would imply that the point we are using from d1 and the point we are using from d2 exactly add up to our point p, which means that the bitstring we encrypted is d1[D] + d2[x].
Repeating this for all chunks in enc, we can retrieve the flag.
Below is my solve script (takes a few minutes to complete):