ProSign 3
This is my secure timestamp signing server. Only if you can produce a signature for "unlock" can you learn more.
Connect at nc socket.cryptohack.org 13381
challenge.py
import hashlib
from Crypto.Util.number import bytes_to_long, long_to_bytes
from ecdsa.ecdsa import Public_key, Private_key, Signature, generator_192
from utils import listener
from datetime import datetime
from random import randrange
FLAG = "crypto{?????????????????????????}"
g = generator_192
n = g.order()
class Challenge():
def __init__(self):
self.before_input = "Welcome to ProSign 3. You can sign_time or verify.\n"
secret = randrange(1, n)
self.pubkey = Public_key(g, g * secret)
self.privkey = Private_key(self.pubkey, secret)
def sha1(self, data):
sha1_hash = hashlib.sha1()
sha1_hash.update(data)
return sha1_hash.digest()
def sign_time(self):
now = datetime.now()
m, n = int(now.strftime("%m")), int(now.strftime("%S"))
current = f"{m}:{n}"
msg = f"Current time is {current}"
hsh = self.sha1(msg.encode())
sig = self.privkey.sign(bytes_to_long(hsh), randrange(1, n))
return {"msg": msg, "r": hex(sig.r), "s": hex(sig.s)}
def verify(self, msg, sig_r, sig_s):
hsh = bytes_to_long(self.sha1(msg.encode()))
sig_r = int(sig_r, 16)
sig_s = int(sig_s, 16)
sig = Signature(sig_r, sig_s)
if self.pubkey.verifies(hsh, sig):
return True
else:
return False
def challenge(self, your_input):
if 'option' not in your_input:
return {"error": "You must send an option to this server"}
elif your_input['option'] == 'sign_time':
signature = self.sign_time()
return signature
elif your_input['option'] == 'verify':
msg = your_input['msg']
r = your_input['r']
s = your_input['s']
verified = self.verify(msg, r, s)
if verified:
if msg == "unlock":
self.exit = True
return {"flag": FLAG}
return {"result": "Message verified"}
else:
return {"result": "Bad signature"}
else:
return {"error": "Decoding fail"}
listener.start_server(port=13381)
Solution
As we can see in line 26 in sign_time function value of n changed after 1 sign,so the value of random number generated(k) in line 30 will be in range(1-60).So will can retrieve secret from it.
- s = (H(m) + r.secret)/k
- secret = (s.k - H(m))/r
solve.py
import hashlib
from Crypto.Util.number import bytes_to_long
from ecdsa.ecdsa import Public_key, Private_key, Signature, generator_192
import json
from pwn import remote
def sha1(data): return bytes_to_long(hashlib.sha1(data.encode()).digest())
g = generator_192
n = g.order()
io = remote("socket.cryptohack.org", "13381")
io.recvline()
io.sendline(b'{"option":"sign_time"}')
sig = json.loads(io.recvline().decode())
msg,r,s = sig['msg'],int(sig['r'],16),int(sig['s'],16)
for k in range(1,60):
hsh = sha1(msg)
secret = ((s*k - hsh) * pow(r, -1, n)) % n
pub = Public_key(g, g * secret)
priv = Private_key(pub, secret)
if pub.verifies(hsh, Signature(r, s)):
break
msg = "unlock"
hsh = sha1(msg)
sig = priv.sign(hsh, 420)
tosend = {"option":"verify","msg": msg, "r": hex(sig.r), "s": hex(sig.s)}
io.sendline(json.dumps(tosend).encode())
flag = json.loads(io.recv().decode())["flag"]
print(flag)
After running the code you will get flag crypto{ECDSA_700_345y_70_5cr3wup}