#!/usr/bin/env python3.4 
import socket
import json
import struct
import sys
from Crypto.Cipher import DES
import base64

"""
Network Security Hacking Challenges 2014

Challenge: Enter your name into our database.
If you prefer to stay pseudonymous, you may instead enter your "tag".
Please use the same name/tag throughout the semester.

Rules:
1) Be fair! Do not DOS our systems.
2) Please try to enter your name/tag only once


Database at
$ nc netsec.net.in.tum.de 30005
"""
#send/recv based on http://stackoverflow.com/questions/17667903/python-socket-receive-large-amount-of-data

'''
As you may have noticed we changed the format our client and server talk to each other.
Instead of a linebased protocol we now talk to each other using json to transmit dictionarys.
You can simply use send_json(socket, dictionary) and recv_json(socket) to send your dictionarys to the server.
Example format:
{"value_a":2,value_b:"HalloWelt"}

Json allows us to send this as string over the network and transform it back into an dictionary on the server.
Same holds for the other direction.
For now we use only dictionarys, while other options are theoretical possible.
'''

#read next message from socket (first 4 bytes are the length of the next message ("payload length"))    
def recv_msg(socket):
    raw_msglen = recvall(socket, 2) 
    if not raw_msglen:
        return None
    msglen = struct.unpack('>H', raw_msglen)[0]
    # Read the message data
    return recvall(socket, msglen)

#Helper function to recv n bytes or return None if EOF is hit
def recvall(socket, n):
    data = b''
    while len(data) < n:
        packet = socket.recv(n - len(data))
        if not packet:
            return None
        data += packet
    return data
        
#Helper function to receive a JSON formated string from the given socket and return it (most likely a dictionary)
def recv_json(socket):
    received = recv_msg(socket)
    if not received:
        return None
    else:
        received=received.decode() 

    try:
        received = json.loads(received) 

    except Exception as e:
        print("Something went wrong when interpreting the received string as JSON!\nCheck format: %s"% received)
        print("Exception: %s"%e)
        return None   
    return received 

#send a message over the socket, leading with 4 byte message size header
def sendMessage(socket, msg):
    if len(msg)>65536:
        print("Maximum allowed length for messages is 65kB!")        
        sys.exit()
    msg = struct.pack('>H', len(msg)) + msg.encode() #max 65 kb
    socket.sendall(msg)

#Helper function to send a object (most likely a dictionary in our case) as JSON String
def send_json(socket,obj):
    try:
        sendMessage(socket,json.dumps(obj))
    except Exception as e:
        print("Something went wrong when sending the object or transforming it to a JSON string.\n")
        print("Exception: %s"%e)
        sys.exit()

################
#main function
#You should only change things here or in your own functions, 
#changing the send/recv functions may lead to errors
#we don't validate what we get from the server, but feel free to do so. If an error occurs on serverside we will send you a hint of what happened, 
#which will have a different format then expected -> may cause an exception
################
def main():

    #create socket and connect to server1
    HOST = '131.159.15.68' #netsec.net.in.tum.de
    PORT = 20005            
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #Uncomment the following lines if you want to use IPv6 (not necessary)
    #s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    #HOST = '2001:4ca0:2001:18:216:3eff:fef0:3933'
    
    plaintext = input("Insert the text you want to encrypt: ")
    if len(plaintext)<10:
        print("Plaintext length must be >=10 characters")
        return
    s.connect((HOST, PORT))
    send_json(s,{"Plaintext":plaintext})
    
    data = recv_json(s)#receive encrypted answer 
    print("Received data: ", data)
    
    #Data is encrypted using 2DES in CBC Mode, once again using pycrypto as library 
    #Block size is 8 Byte, so we need padding if necessary (we don't use padding if the plaintext length already is a multiple of 8 bytes).
    #As padding algorithm we use simple zero padding (http://en.wikipedia.org/wiki/Padding_%28cryptography%29#Zero_padding)
    #example padding: "HelloWorld"->"HelloWorld000000" length 10 byte, so 6 byte missing to 2*8=16 byte --> add "0"*6 to string
    #keys are randomly chosen between 1 and 999999, interpreted as strings with leading zeros (8 byte key length))
    #Before sending the ciphertext to the client, it is encoded as base64. 
    #our iv is chosen randomly (8 byte) an is sent with the ciphertext (also base64 encoded).
    #we send them as dictionary using json as name:value pairs, where names are "iv" and "ciphertext" and values are obviously the values (base64 encoded).
    
    #Example of 2DES-CBC for testing purposes:
    #key1="00000042"
    #key2="00001337"
    #plaintext="NetsecRocks" (without padding, don't forget it!)
    #ciphertext after first step of 2DES (with key1)=b'\xb1\x8d\x8a=\xc2\xc4\x90WU.vT\xcc/d\xb9'
    #ciphertext after second step of 2DES (with key2) =b'h\x1d\x0egJw\xff\t\xb6\xfa\x9b\x15W\xc1\xe7\x85'
    
    #Your Task: crack the 2DES encryption and send the two keys back to the server (format: dictionary with "key1" and "key2" as names, see below), 
    #make sure each key has 8 characters length (see example)  
    #There is a 60 second timeout on serverside, have this in mind.  

    keys={"Key1":"PlaceKey1Here","Key2":"PlaceKey2Here"}
    send_json(s,keys)
    
    data = recv_json(s) #receive answer 
    print("Received result: ",data)
    if(data["Result"]=="correct keys"):
        tag=input("Enter your tag: ")
        send_json(s,{"Tag":tag})
   
    s.close()

main()

