I have a Python server script and a Java client application and the goal is to encrypt the data over the socket. The data will always be a string. (I am new to Java)
What is supposed to happen:
The Python script creates a socket and listens for connections, accepts new connections, sends the IV in plain-text for AES CBC PKCS5Padding, sets up the ciphers, then receives encrypted data from the Java application (decrypts, then prints out in the terminal), then sends encrypted data to the Java application, then closes the connection.
The Java application connects to the python socket, receives the IV, sets up the same ciphers, encrypts a simple padded string, sends it to the python server, then waits for a reply, decrypts the reply and prints it out to the screen. The connection then closes.
What is actually happening
Server sets up socket, java client connects and recieves IV, both setup the same encryption/decryption ciphers, then the java client sends an encrypted padded string. the Python server succesfully receives the ciphertext, decrypts the string and unpads, and the string shows correctly. The server then sends an encrypted padded string to the java client, the client receives the string, then on decryption it fails due a BadPaddingException.
I have verified that the PKCS5 Padding on the python side is correct according to the RFC. I have tried other padding methods (zero-padding, etc), and nothing is working. I have also tried several different encodings on the strings, I have also tried M2Crypto (using pycrypto now), etc. I have tried using CipherInputStream over the socket for the client, and the same result.
Nothing works. I am still thinking there might be an encoding problem between Python and Java, but I am stuck in a rut as to only why the Java client is failing at decrypting.
Python Server:
#!/usr/bin/env python
import os, time, threading, json
from socket import *
import sys, base64
from Crypto.Cipher import AES
from socket import *
### Settings
serverHost = '' # localhost
serverPort = 5555 # non-reserved
masterkey = 'mysecretpassword'
BLOCK_SIZE = 16 # Block-size for cipher (16, 24 or 32 for AES)
#PADDING = '{' # block padding for AES
# PKCS5 Padding
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s : s[0:-ord(s[-1])]
# generate new IV's - why you ask? ... just look at WEP
def createCipher(key):
iv = os.urandom(16)
return (AES.new(key, AES.MODE_CBC, iv), iv)
class IRCTalkServer(threading.Thread):
def __init__(self, masterkey, host='', port=5555):
try:
self.sockobj = socket(AF_INET, SOCK_STREAM) # create TCP socket obj
self.sockobj.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # make port reusable
self.sockobj.bind((host, port)) # bind socket to port
self.sockobj.listen(5) # listen, allow 5 pending connects
if host == '': host='localhost'
print "Started server on %s:%d" % (host, port)
except:
print "Error starting server"
sys.exit(0)
threading.Thread.__init__(self)
self.die = False # loop killer
self.masterkey = masterkey
self.host = host
self.port = port
def run(self):
try:
while True and not self.die: # infinite loop unless called to quit
connection, address = self.sockobj.accept()
print 'Server connected by ', address
# generate cipher and get first IV - prevent same WEP hacks
#paddedKey = keypad(masterkey)
#print "master key:", masterkey, " - padded key:", paddedKey, " - diff key:", keypad('test')
self.cipher, self.iv = createCipher(masterkey)
print "IV:", self.iv.encode('hex'), "Sending IV: ", repr(self.iv)
connection.send("%s%s" % (self.iv.encode('hex'),'\n')) # send iv first
while True and not self.die: # read from client
print "waiting for client"
data = connection.recv(10485760)
print "recieved from client:", repr(data.rstrip())
if not data:
print "NO DATA"
break
dataCheck, JSON = self.decryptData(data.rstrip())
print 'Recieved from Client:', repr(JSON)
#print 'Recieved from Client:', repr(data)
#if dataCheck:
#senddata = self.encryptData('SUCCESS')
#print "Size of compresseddata:", len(senddata)
#connection.send(senddata)
#morestuff = 'abc123def456'*int(10000/9)
#senddata = self.encryptData(['test', morestuff, 'test1', {'key': 'value'}, 2223])
#print "Size of compresseddata:", len(senddata)
#connection.send(senddata)
print "Sending reply to client..."
senddata = self.encryptData('test')
print "reply data:", repr("%s%s" % (senddata, "\n"))
connection.send("%s%s" % (senddata, "\n"))
#connection.send("Hello back mr android!")
#successReply = connection.recv(256) # only for "END REQUEST"
break
print "Closing connection... \n\n"
connection.close()
except:
print "exception on try loop"
pass # an error occurred, just drop it, the client will try again later
def encryptData(self, plaintext):
# convert to json string, pad the string, then encrypt, then compress
#JSON = json.dumps(plaintext, separators=(',',':'))
JSON = plaintext
#print "Size of JSON:", len(JSON)
ciphertext = pad(unicode(JSON))
print "padded text:", repr(ciphertext)
ciphertext = self.cipher.encrypt(ciphertext)
print "ciphertext:", repr(ciphertext), "|", len(ciphertext), "|", ciphertext
ciphertext = ciphertext.encode('hex').upper()
print "hexified text:", repr(ciphertext)
#ciphertext = self.cipher.encrypt(pad(JSON)).encode('hex').upper()
print "Size of ciphertext:", len(ciphertext)
return ciphertext
def decryptData(self, ciphertext):
try:
# decompress data to ciphertext, decrypt, convert to json
print "length of ciphertext:", len(ciphertext)
ptext = ciphertext.decode('hex')
print "unhexifed:", repr(ptext)
ptext = self.cipher.decrypt(ptext)
print "decrypted:", repr(ptext)
ptext = unpad(ptext)
print "unpadded:", repr(ptext)
#ptext = unpad(self.cipher.decrypt(ciphertext.decode('hex')))
print "ptext: ", repr(ptext)
JSON = ptext
#plaintext = unpad(self.cipher.decrypt(ciphertext))
##JSON = json.loads(plaintext)
#JSON = plaintext
except:
print "Error on decryption"
JSON = None
return (True, JSON)
def addToQueue(self, data):
pass
def getFromQueue(self):
pass
testserver =IRCTalkServer(masterkey)
testserver.start()
testserver.join()
Java Client: (parts of this code still needs to be properly credited – [i just haven’t done it yet])
import java.io.*;
import java.util.*;
import java.net.*;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
//import javax.crypto.CipherInputStream;
//import javax.crypto.CipherOutputStream;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.spec.SecretKeySpec;
class clientHandler {
/*
* This class sets up AES CBC encryption for the socket,
* new instance for every connection since the IV changes
*/
// Declare variables
private Cipher ecipher;
private Cipher dcipher;
Socket testSocket = null;
//DataOutputStream out = null;
//DataInputStream in = null;
PrintWriter out = null;
BufferedReader in = null;
String masterkey = "mysecretpassword";
static final String HEXES = "0123456789ABCDEF";
public static String byteToHex( byte [] raw ) {
if ( raw == null ) {
return null;
}
final StringBuilder hex = new StringBuilder( 2 * raw.length );
for ( final byte b : raw ) {
hex.append(HEXES.charAt((b & 0xF0) >> 4))
.append(HEXES.charAt((b & 0x0F)));
}
return hex.toString();
}
public static byte[] hexToByte(String hexString) {
int len = hexString.length();
byte[] ba = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
ba[i/2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i+1), 16));
}
return ba;
}
public String encrypt(String plaintext) {
// encrypt string
try {
byte[] cipherbyte = ecipher.doFinal(plaintext.getBytes("UTF-8"));
//String ciphertext = new String(ecipher.doFinal(plaintext.getBytes("UTF8")));
return byteToHex(cipherbyte);
} catch (Exception e){
e.printStackTrace();
return null;
}
}
public String decrypt(String ciphertext) {
// decrypt hex string
try {
System.out.println("decrypt byte length: " + hexToByte(ciphertext).length);
String tp = new String(hexToByte(ciphertext), "UTF-8");
System.out.println("toString(): " + tp);
String plaintext = new String(dcipher.doFinal(hexToByte(ciphertext.trim())), "UTF-8");
//String plaintext = new String(dcipher.doFinal(ciphertext.getBytes("UTF8")), "UTF-8");
return plaintext;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public void setupCrypto(byte[] iv, String key) {
// setup AES CBC encryption
// convert IV and key to byte array for crypto
try {
System.out.println("Setting up Crypto...");
//byte[] ivb = iv.getBytes("UTF8");
byte[] keyb = key.getBytes("UTF8");
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
SecretKeySpec skey = new SecretKeySpec(keyb, "AES");
ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
ecipher.init(Cipher.ENCRYPT_MODE, skey, paramSpec);
dcipher.init(Cipher.DECRYPT_MODE, skey, paramSpec);
} catch (Exception e) {
System.err.println("Error:" + e);
}
}
public void startServer() {
// starts server
try {
System.out.println("Connecting to Server");
testSocket = new Socket("localhost", 5555);
//out = new DataOutputStream(testSocket.getOutputStream());
//in = new DataInputStream(testSocket.getInputStream());
out = new PrintWriter(testSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(testSocket.getInputStream()));
} catch (UnknownHostException e) {
System.err.println("Uknown Host");
} catch (IOException e) {
System.err.println("Couldn't get I/O for the connection");
}
}
public void androidClientHandler() {
// actual communication
if (testSocket != null && out != null && in != null) {
try {
System.out.println("Waiting for IV..");
String iv;
iv = in.readLine();
byte [] ivb = hexToByte(iv);
System.out.println("Got IV..." + iv);
setupCrypto(ivb, masterkey);
String ciphertext;
String plaintext;
System.out.println("Sending \"test\" to server");
//out.writeBytes("test\n");
ciphertext = encrypt("test");
System.out.println("Sent: " + ciphertext);
out.println(ciphertext);
System.out.println("Waiting for Server to reply");
String responseLine;
responseLine = in.readLine().replaceAll("\\\\n", "");
System.out.println("Recieved from Server: " + responseLine + " - length: " + responseLine.length());
plaintext = decrypt(responseLine);
System.out.println("Recieved from Server: " + plaintext);
System.out.println("Closing Connection");
out.close();
in.close();
testSocket.close();
} catch (UnknownHostException e) {
System.err.println("Trying to connect to unknown host:" + e);
} catch (IOException e) {
System.err.println("IOException: " + e);
}
}
}
public void javaIsGay() {
startServer();
androidClientHandler();
}
public static void main(String[] args) {
System.setProperty("file.encoding", "UTF-8");
clientHandler c = new clientHandler();
c.javaIsGay();
}
}
Output of Python Server:
Started server on localhost:5555
Server connected by ('127.0.0.1', 59683)
IV: c54aae0a5c43f547f0355ee7a0ee38c1 Sending IV: '\xc5J\xae\n\\C\xf5G\xf05^\xe7\xa0\xee8\xc1'
waiting for client
recieved from client: 'BBE7E09093625204CD3F7B755066419D'
length of ciphertext: 32
unhexifed: '\xbb\xe7\xe0\x90\x93bR\x04\xcd?{uPfA\x9d'
decrypted: 'test\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
unpadded: 'test'
ptext: 'test'
Recieved from Client: 'test'
Sending reply to client...
padded text: u'test\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'
ciphertext: '\x9fT\xfc\xf0\xa8\xc2\xd3N*\x8f\x8e~\xc7\x8a\xbfR' | 16 | �T�����N*~NJ�R
hexified text: '9F54FCF0A8C2D34E2A8F8E7EC78ABF52'
Size of ciphertext: 32
reply data: '9F54FCF0A8C2D34E2A8F8E7EC78ABF52\n'
Closing connection...
Output of Java Client:
Connecting to Server
Waiting for IV..
Got IV...c54aae0a5c43f547f0355ee7a0ee38c1
Setting up Crypto...
Sending "test" to server
Sent: BBE7E09093625204CD3F7B755066419D
Waiting for Server to reply
Recieved from Server: 9F54FCF0A8C2D34E2A8F8E7EC78ABF52 - length: 32
decrypt byte length: 16
toString(): �T����N*��~NJ�R
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:317)
at javax.crypto.Cipher.doFinal(Cipher.java:1813)
at clientHandler.decrypt(SocketClient.java:70)
at clientHandler.androidClientHandler(SocketClient.java:134)
at clientHandler.javaIsGay(SocketClient.java:151)
at clientHandler.main(SocketClient.java:157)
Recieved from Server: null
Closing Connection
You mentioned trying nopadding, did you try switching the cipher mode? CTR should be just as secure as CBC for your application and doesn’t require padding.