Rating:

We’re given the 3 python scripts. Here’s the one of the script called curvy_decryptor.py where the main flow of this challenge is located.

#!/usr/bin/env python3
import os
import sys
import string
from Crypto.Util import number
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Cipher import AES
from binascii import hexlify

from ec import *
from utils import *
from secret import flag1, flag2

#P-256 parameters
p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
a = -3
b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
n = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
curve = EllipticCurve(p,a,b, order = n)
G = ECPoint(curve, 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)

d_a = bytes_to_long(os.urandom(32))
P_a = G * d_a

printable = [ord(char.encode()) for char in string.printable]

def encrypt(msg : bytes, pubkey : ECPoint):
    x = bytes_to_long(msg)
    y = modular_sqrt(x**3 + a*x + b, p)
    m = ECPoint(curve, x, y)
    d_b = number.getRandomRange(0,n)
    return (G * d_b, m + (pubkey * d_b))

def decrypt(B : ECPoint, c : ECPoint, d_a : int):
    if B.inf or c.inf: return b''
    return long_to_bytes((c - (B * d_a)).x)

def loop():
    print('I will decrypt anythin as long as it does not talk about flags.')
    balance = 1024
    while True:
        print('B:', end = '')
        sys.stdout.flush()
        B_input = sys.stdin.buffer.readline().strip().decode()
        print('c:', end = '')
        sys.stdout.flush()
        c_input = sys.stdin.buffer.readline().strip().decode()
        B = ECPoint(curve, *[int(_) for _ in B_input.split(',')])
        c = ECPoint(curve, *[int(_) for _ in c_input.split(',')])
        msg = decrypt(B, c, d_a)
        if b'ENO' in msg:
            balance = -1
        else:
            balance -= 1 + len([c for c in msg if c in printable])
        if balance >= 0:
            print(hexlify(msg))
            print('balance left: %d' % balance)
        else:
            print('You cannot afford any more decryptions.')
            return

if __name__ == '__main__':
    print('My public key is:')
    print(P_a)
    print('Good luck decrypting this cipher.')
    B,c = encrypt(flag1, P_a)
    print(B)
    print(c)
    key = long_to_bytes((d_a >> (8*16)) ^ (d_a & 0xffffffffffffffffffffffffffffffff))
    enc = AES.new(key, AES.MODE_ECB)
    cipher = enc.encrypt(flag2)
    print(hexlify(cipher).decode())
    try:
        loop()
    except Exception as err:
        print(repr(err))

This script implements the Elgamal encryption based on Elliptic Curve. It uses private key d_a to decrypt any encrypted message and public key P_a where P_a = G * d_a to encrypt any message.

TL;DR

  • P-256 curve is used
  • The encryption will encrypt message m into B and c where B = G * d_b and c = m + (P_a * d_b)
  • The decryption will take two inputs B and c from user, then recover m by computing c - (B * d_a)
  • We can prove this scheme is true since c - (B * d_a) = m + (P_a * d_b) - (G * d_b * d_a) = m + (G * d_a * d_b) - (G * d_b * d_a) = m
  • We can’t decrypt the encrypted flag1 directly since the program will check whether there’s “ENO” string or not in the decrypted message

Since we have a full control over B and c, we can trick the decryption process by trying to decrypt the message m + G so that the string “ENO” doesn’t appear in the decryption result. As we have the decrypted result (which is the x-coordinate of point m + G), then we can calculate the original m locally by subtracting it with G.

My public key is:
Point(35328020660011696586564709258679785381786343512657089812378287082952917934716, 50416609790690128272655082973486394880002358124206268101298526556963890817882)
Good luck decrypting this cipher.
Point(104154835773696245508276030859477464297007825271904213255158699262376517719952, 53449655463778645126537272461287855887128030500959951790357090736353714142083)
Point(52168834033775429783455814818719333757981140192623818913235122708297420734798, 104323109031322302473535719172928547306740421293266175085426829976948991264626)
4d5cc7368d8ec8481b44309ee0e0bcc7
I will decrypt anythin as long as it does not talk about flags.
B:

Let’s take the P_a, B, and c values to our solver script.

from fastecdsa.curve import P256
from fastecdsa.point import Point

G = P256.G

P = Point(35328020660011696586564709258679785381786343512657089812378287082952917934716, 50416609790690128272655082973486394880002358124206268101298526556963890817882)
B = Point(104154835773696245508276030859477464297007825271904213255158699262376517719952, 53449655463778645126537272461287855887128030500959951790357090736353714142083)
c = Point(52168834033775429783455814818719333757981140192623818913235122708297420734798, 104323109031322302473535719172928547306740421293266175085426829976948991264626)

new_c = c + G

print(f"{int(B.x)}, {int(B.y)}")
print(f"{int(new_c.x)}, {int(new_c.y)}")
$ python3 solve.py 
104154835773696245508276030859477464297007825271904213255158699262376517719952, 53449655463778645126537272461287855887128030500959951790357090736353714142083
97394679619587788933333201089550117417489418909990818455148320248855936249801, 82185379848596875687249291483240443665288425087915452032458758698163910630490
My public key is:
Point(35328020660011696586564709258679785381786343512657089812378287082952917934716, 50416609790690128272655082973486394880002358124206268101298526556963890817882)
Good luck decrypting this cipher.
Point(104154835773696245508276030859477464297007825271904213255158699262376517719952, 53449655463778645126537272461287855887128030500959951790357090736353714142083)
Point(52168834033775429783455814818719333757981140192623818913235122708297420734798, 104323109031322302473535719172928547306740421293266175085426829976948991264626)
4d5cc7368d8ec8481b44309ee0e0bcc7
I will decrypt anythin as long as it does not talk about flags.
B:104154835773696245508276030859477464297007825271904213255158699262376517719952, 53449655463778645126537272461287855887128030500959951790357090736353714142083
c:97394679619587788933333201089550117417489418909990818455148320248855936249801, 82185379848596875687249291483240443665288425087915452032458758698163910630490
b'cc5ceac68ba0e89194b84049ccab1d1a5a6b0825801ec3f2a2b9e8fc871548aa'
balance left: 1016
B:
from fastecdsa.curve import P256
from fastecdsa.point import Point
from utils import *
from Crypto.Util.number import *

x = 0xcc5ceac68ba0e89194b84049ccab1d1a5a6b0825801ec3f2a2b9e8fc871548aa
y = modular_sqrt(x**3 + P256.a*x + P256.b, P256.p)

m = Point(x, y)
m -= P256.G

print(m.x)
print(long_to_bytes(m.x))
$ python3 solve.py 
478331744150452007169656202614580135667533560163676060093785159488648061
b'ENO{ElGam4l_1s_mult1pl1cativ3}'
Original writeup (https://hackmd.io/@vidner/nullcon-sksd#Curvy-Decryptor-cry).