This weekend was ASIS Quals weekend again. And just like last year they have quite a lot of nice crypto-related puzzles which are fun to solve (and not "the same as every ctf").
Actually Secured OTP Server is pretty much the same as the First OTP Server (actually it's a "fixed" version to enforce the intended attack). However the template phrase now starts with enough stars to prevent simple root.:
def gen_otps():
template_phrase = '*************** Welcome, dear customer, the secret passphrase for today is: '
OTP_1 = template_phrase + gen_passphrase(18)
OTP_2 = template_phrase + gen_passphrase(18)
otp_1 = bytes_to_long(OTP_1)
otp_2 = bytes_to_long(OTP_2)
nbit, e = 2048, 3
privkey = RSA.generate(nbit, e = e)
pubkey = privkey.publickey().exportKey()
n = getattr(privkey.key, 'n')
r = otp_2 - otp_1
if r < 0:
r = -r
IMP = n - r**(e**2)
if IMP > 0:
c_1 = pow(otp_1, e, n)
c_2 = pow(otp_2, e, n)
return pubkey, OTP_1[-18:], OTP_2[-18:], c_1, c_2
Now let A = template * 2^(18*8)
, B = passphrase
. This results in
OTP = A + B
. c
therefore is (A+B)^3 mod n == A^3 + 3A^2b + 3AB^2
+ B^3
. Notice that only B^3
is larger than N
and is statically
known. Therefore we can calculate A^3 // N
and add that to c
to
"undo" the modulo operation. With that it's only iroot
and
long_to_bytes
to the solution. Note that we're talking about OTP
and C
here. The code actually produced two OTP
and C
values but
you can use either one just fine.
#!/usr/bin/python3
import sys
from util import bytes_to_long
from gmpy2 import iroot
PREFIX = b'*************** Welcome, dear customer, the secret passphrase for today is: '
OTPbase = bytes_to_long(PREFIX + b'\x00' * 18)
N = 27990886688403106156886965929373472780889297823794580465068327683395428917362065615739951108259750066435069668684573174325731274170995250924795407965212988361462373732974161447634230854196410219114860784487233470335168426228481911440564783725621653286383831270780196463991259147093068328414348781344702123357674899863389442417020336086993549312395661361400479571900883022046732515264355119081391467082453786314312161949246102368333523674765325492285740191982756488086280405915565444751334123879989607088707099191056578977164106743480580290273650405587226976754077483115441525080890390557890622557458363028198676980513
WRAPPINGS = (OTPbase ** 3) // N
C = 13094996712007124344470117620331768168185106904388859938604066108465461324834973803666594501350900379061600358157727804618756203188081640756273094533547432660678049428176040512041763322083599542634138737945137753879630587019478835634179440093707008313841275705670232461560481682247853853414820158909864021171009368832781090330881410994954019971742796971725232022238997115648269445491368963695366241477101714073751712571563044945769609486276590337268791325927670563621008906770405196742606813034486998852494456372962791608053890663313231907163444106882221102735242733933067370757085585830451536661157788688695854436646
x = N * WRAPPINGS + C
val, _ = iroot(x, 3)
bstr = "%x" % int(val)
for i in range(0, len(bstr) // 2):
sys.stdout.write(chr(int(bstr[2*i:2*i+2], 16)))
print()