I have an RSA encryption system for securing the communication between a Server (Python) and a Client (Java). Currently, I manage to encrypt and decrypt correctly information from the client to the server using the server's keys. What I am not able to do, is to decrypt the message on the Client (Java) that comes from the Server (Python).
The encryption snippet in Python is the following:
publicKeyStr = None
with open(PATH_KEY, 'r') as file:
publicKeyStr = file.read()
publicKeyClient = RSA.importKey(publicKeyStr)
encryptor = PKCS1_v1_5.new(key)
message = encryptor.encrypt(plainMessage)
# Message's length is always 128 bytes on the server side
#Send message to the client (RabbitMQ)
The variable message with the ciphered content is received on the Client side in String format and here comes the problem: the decryption method in Java expects a byte[] input and a maximum block size of 128 bytes, but when transforming the String message to a byte[] array, these String has over 128 bytes. This was my approach on the client side (Java):
public String call(String queue, byte[] message) throws IOException,
InterruptedException {
final String corrId = UUID.randomUUID().toString();
String replyQueueName = channel.queueDeclare().getQueue();
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.correlationId(corrId).replyTo(replyQueueName).build();
channel.basicPublish("", queue, props, message);
final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
String ctag = channel.basicConsume(replyQueueName, true,
new DefaultConsumer(channel) {
#Override
public void handleDelivery(String consumerTag,
Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
if (properties.getCorrelationId().equals(corrId)) {
response.offer(new String(body, "UTF-8"));
}
}
});
String result = response.take();
channel.basicCancel(ctag);
return result;
}
public static String sendElement(String topic, byte[] message) {
SendRabbitMQ sendRabbitMQ = null;
String response = "";
try {
sendRabbitMQ = new SendRabbitMQ();
response = sendRabbitMQ.call(topic, message);
} catch (IOException | TimeoutException | InterruptedException e) {
e.printStackTrace();
} finally {
if (sendRabbitMQ != null) {
try {
sendRabbitMQ.close();
} catch (IOException _ignore) {
}
}
}
return response;
}
String response = SendRabbitMQ.sendElement("topic", encryptedInfo);
byte [] responseByte = response.getBytes();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
System.out.println(new String(cipher.doFinal(message)));
The error raises when responseByte is assigned, as it's size is over 128 characters.
Therefore, my question is, how can I transform the message String sent from the server side on the client size in order to correctly transform this String to a byte[] array?
EDIT: I believe it does not have to do with the RabbitMQ code on the client side, but just in case, I have included the methods used.
Related
I have a server which let's sockets connect to it to send data over inputstreams, this data is being encrypted with AES/GCM/NoPadding in a class called Cryptographer. The server has threads that hold functionalities for the connected clients, and each thread is being represented in a ConnectionThread class, this class holds a reference to the cryptograhper class which is being initialized in the server class.
Problem:
When I send my first command it works just fine, no problems at all. But somehow when I send my second command if gives the following stacktrace:
javax.crypto.AEADBadTagException: Tag mismatch!
at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:595)
at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2208)
at com.company.security.Cryptographer.decrypt(Cryptographer.java:53)
at com.company.client.Reader.run(Reader.java:45)
at java.base/java.lang.Thread.run(Thread.java:835)
Exception in thread "Thread-3" java.lang.NullPointerException
at java.base/java.lang.String.<init>(String.java:623)
at com.company.client.Reader.run(Reader.java:47)
at java.base/java.lang.Thread.run(Thread.java:835)
These are the classes mentioned in the stacktrace
Cryptographer
package com.company.security;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Cryptographer {
private Key secretKey;
private GCMParameterSpec gcmParameterSpec;
public Cryptographer() {
byte[] secret = new byte[16]; // 128 bit is 16 bytes, and AES accepts 16 bytes, and a few others.
byte[] secretBytes = "secret".getBytes();
byte[] IV = new byte[12];
gcmParameterSpec = new GCMParameterSpec(16 * 8, IV);
System.arraycopy(secretBytes, 0, secret, 0, secretBytes.length);
secretKey = new SecretKeySpec(secret, "AES");
}
/**
* Encrypt data.
* #param data to encrypt
* #return encrypted data
*/
public byte[] encrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
byte[] encrypted = cipher.doFinal(data);
return encrypted;
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
/**
* Decrypt data.
* #param data to decrypt
* #return decrypted data
*/
public byte[] decrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
return cipher.doFinal(data);
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
}
Reader
package com.company.client;
import com.company.FileLoader;
import com.company.client.helpers.ClientFileHelper;
import com.company.client.workers.MessageSender;
import com.company.security.Cryptographer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
public class Reader implements Runnable {
private InputStream inputStream;
private ClientFileHelper fileHelper;
private Cryptographer cryptographer;
private FileLoader fileLoader;
private BufferedReader bufferedReader;
private MessageSender messageSender;
private boolean isActive = true;
private boolean isReceivingFile = false;
public Reader(BufferedReader bufferedReader, MessageSender messageSender, InputStream inputStream) {
this.bufferedReader = bufferedReader;
this.messageSender = messageSender;
this.inputStream = inputStream;
cryptographer = new Cryptographer();
}
#Override
public void run() {
while (isActive) {
try {
int count;
byte[] buffer;
if(!isReceivingFile) {
buffer = new byte[inputStream.available()];
} else {
buffer = new byte[inputStream.available()];
}
while ((count = inputStream.read(buffer)) > 0)
{
byte[] decrypted = cryptographer.decrypt(buffer);
if(!isReceivingFile) {
handleInput(new String(decrypted));
} else {
if(fileHelper.getFileBytes().length == 0) {
fileHelper.setFileBytes(decrypted);
} else {
fileHelper.saveFile();
isReceivingFile = false;
}
}
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
/**
* Handle the user input form the console.
* #param input user input from console
*/
private void handleInput(String input) {
try {
if (input.equals("PING")) { // If we get a PING message we send back a PONG message.
messageSender.send("PONG");
} else if (input.contains("FILE")) {
setupFileAccept(input);
isReceivingFile = true;
} else {
System.out.println(input);
}
} catch (Exception ex) {
isActive = false;
}
}
/**
* Setup the file helper for the client that's going to receive a file.
* #param line command
*/
private void setupFileAccept(String line) {
String[] args = line.split(" ");
if(args[0].equals("FILE")) {
fileHelper = new ClientFileHelper(args[1], Integer.valueOf(args[2]));
}
}
}
The ConnectionThread also has a similar read functionality which looks like this:
while (isActive) {
try {
int count;
byte[] buffer;
if(!isReceivingFile) {
buffer = new byte[inputStream.available()];
} else {
buffer = fileHelper.getFileBytes();
}
while ((count = inputStream.read(buffer)) > 0)
{
byte[] decrypted = server.cryptographer.decrypt(buffer);
if(!isReceivingFile) {
getInput(new String(decrypted));
} else {
fileHelper.setFileBytes(decrypted);
// bytes received, now we can send the file!
if(fileHelper.sendToReceiver()) {
writeToClient(fileHelper.getReceiverName()
+ " received " + fileHelper.getFilename());
fileHelper = null;
}
}
}
} catch (IOException e) {
e.printStackTrace();
break;
}
}
In this case just assume that the Server class has the cryptographer property properly initialized, which is always the case.
My guess is that somewhere a value is doing something wrong but I am not sure. I am rather clueless to what I should do to fix this problem. Can somebody help me point out the mistakes and come up with possible solutions to fix this problem? My java version is 12.0.1
I would encourage to cosider to use SSL/TLS or DTLS instead of trying to reimplement parts of it.
Whether it causes your error I'm not sure but if my interpretation of the Java documentation is correct than you should change the GCMParameterSpec for each message:
after each encryption operation using GCM mode, callers should
re-initialize the cipher objects with GCM parameters which has a
different IV value
and:
GCM mode has a uniqueness requirement on IVs used in encryption with a given key. When IVs are repeated for GCM encryption, such usages are subject to forgery attacks.
Also you are not using the updateAAD (Additional Authentication Data), although that is optional according to https://www.rfc-editor.org/rfc/rfc5084 from the error message it sounds like it is causing errors here, but it may just be a misleading error message.
Update:
I wrote lots of unit tests for the Cryptographer class and only if I start to make random changes to the ciphertext before I decrypt it again I often get the same error. Because we can trust TCP/IP to reproduce exactly the same bytes on the other side of the connection we're left with these kind of problems may be:
Concurrency
Converting ciphertext bytes into Strings, Chars, Readers/Writers
Not reading the entire message from the socket (did you check how many bytes you sent and compared it to how many you received?
And no, I have not yet written and tested my own implementation yet, but there are examples out there, like this example, nicely explained by the author here from the code was found by this search
THanks to JohannesB for pointing me into the right direction!
I now have solved my problems. It first started by the reading method which I had to change to this:
byte[] buffer;
while (inputStream.available() > 0)
{
int read = inputStream.read(buffer);
if(read == 0)
break;
}
// An if statement checking if the buffer has been filled and based on this
// It will execute methods
And my Cryptographer class now looks like this:
public class Cryptographer {
private SecretKey secretKey;
private byte[] aad;
private SecureRandom secureRandom;
private byte[] IV;
public Cryptographer(SecretKey secretKey) {
this.secretKey = secretKey;
secureRandom = new SecureRandom();
IV = new byte[12];
secureRandom.nextBytes(IV);
aad = "association".getBytes();
}
/**
* Encrypt data.
* #param data to encrypt
* #return encrypted data
*/
public byte[] encrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
secureRandom.nextBytes(IV);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
cipher.updateAAD(aad);
return toByteBuffer(cipher.doFinal(data));
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
/**
* Decrypt data.
* #param data to decrypt
* #return decrypted data
*/
public byte[] decrypt(byte[] data) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
// get the data from the byte buffer
data = fromByteBuffer(data);
// create the gcm parameter with the received IV.
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, IV);
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
cipher.updateAAD(aad);
return cipher.doFinal(data);
} catch (InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | NoSuchPaddingException
| NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
/**
* Put the encrypted data through a byte buffer.
* This buffer will contain information about the IV array.
* #param data encrypted data
* #return the ByteBuffer result as byte array
*/
private byte[] toByteBuffer(byte[] data) {
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + IV.length + data.length);
byteBuffer.putInt(IV.length);
byteBuffer.put(IV);
byteBuffer.put(data);
return byteBuffer.array();
}
/**
* Gets data from a ByteBuffer and sets up data needed for decryption.
* #param data ByteBuffer data as byte array
* #return ByteBuffer encrypted data
*/
private byte[] fromByteBuffer(byte[] data) {
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) {
throw new IllegalArgumentException("invalid iv length");
}
IV = new byte[ivLength];
byteBuffer.get(IV);
byte[] remaining = new byte[byteBuffer.remaining()];
byteBuffer.get(remaining);
return remaining;
}
}
As for the reasons why I did this you can see JohannesB's suggestions and check out these articles:
https://proandroiddev.com/security-best-practices-symmetric-encryption-with-aes-in-java-7616beaaade9
How to read all of Inputstream in Server Socket JAVA
I'm trying to make a client/server program in Java that allows the server to send messages encrypted using AES to the client. Right now, I'm having problems while creating the key exchange protocol. The way that this key exchange current works is:
Client generates RSA public/private key pair
Client sends out his RSA public key to the server
Server generates and encrypts AES key with client's RSA public key
Server sends encrypted AES key to client
Both parties now have the correct AES key and all messages can be encrypted using AES
However, every time I get to step three, I am unable to encrypt the generated AES key with the client's RSA public key because I get this error:
java.security.InvalidKeyException: No installed provider supports this key: javax.crypto.spec.SecretKeySpec
at java.base/javax.crypto.Cipher.chooseProvider(Cipher.java:919)
at java.base/javax.crypto.Cipher.init(Cipher.java:1275)
at java.base/javax.crypto.Cipher.init(Cipher.java:1212)
at test.Server.<init>(Server.java:50)
at test.Start.main(Start.java:11)
As a result, I am unable to complete the AES key exchange that I'm trying to do.
Server.java is used to do things on the server side, and Client.java is used to do everything on the client side. My Server.java file looks like this:
public class Server {
private ServerSocket serverSocket; // Server socket
private Socket socket; // Socket
private BufferedReader in; // Reading from stream
private PrintWriter out; // Writing to stream
private Key key; // AES key used for encryption
// Constructor
public Server() {
// Initialize the server socket
try {
// Setup connections
serverSocket = new ServerSocket(12345);
socket = serverSocket.accept();
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(newInputStreamReader(socket.getInputStream()));
// Receive the client's public RSA key
byte[] encodedClientKey = Base64.getDecoder().decode(in.readLine());
Key clientRSAKey = new SecretKeySpec(encodedClientKey, 0, encodedClientKey.length, "RSA");
// Generate AES key
KeyGenerator aesKeyGen = KeyGenerator.getInstance("AES");
aesKeyGen.init(256);
key = aesKeyGen.generateKey();
// Encrypt the AES key using the client's RSA public key
Cipher c = Cipher.getInstance("RSA");
c.init(Cipher.ENCRYPT_MODE, clientRSAKey);
byte[] encryptedAESKey = c.doFinal(key.getEncoded());
// Send the encrypted AES key to the client
sendUnencrypted(Base64.getEncoder().encodeToString(encryptedAESKey));
} catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
}
// Receive an unencrypted message
public String receiveUnencrypted() {
try {
// Wait until the stream is ready to be read
while (true)
if (in.ready())
break;
return in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// Send an unencrypted message
public void sendUnencrypted(String message) {
out.println(message);
out.flush();
}
// Send an encrypted message
public void send(String message) {
try {
// Encrypt the message
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.ENCRYPT_MODE, key);
String encoded = Base64.getEncoder().encodeToString(message.getBytes("utf-8"));
byte[] encrypted = c.doFinal(encoded.getBytes());
String encryptedString = Base64.getEncoder().encodeToString(encrypted);
// Send the encrypted message
out.println(encryptedString);
out.flush();
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
| BadPaddingException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
My Client.java file looks like this:
public class Client {
private Socket socket; // Socket
private BufferedReader in; // Reading from stream
private PrintWriter out; // Writing to stream
private Key key; // AES key
// Constructor
public Client() {
try {
// Create streams to server
socket = new Socket("127.0.0.1", 12345);
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// Generate an RSA key pair
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair kp = keyGen.generateKeyPair();
// Send out our public key to the server
byte[] publicKey = kp.getPublic().getEncoded();
sendUnencrypted(Base64.getEncoder().encodeToString(publicKey));
// Recieve and decrypt the AES key sent from the server
String encryptedKey = receiveUnencrypted();
Cipher c = Cipher.getInstance("RSA");
c.init(Cipher.DECRYPT_MODE, kp.getPrivate());
byte[] AESKey = c.doFinal(encryptedKey.getBytes());
key = new SecretKeySpec(AESKey, 0, AESKey.length, "AES");
} catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| IllegalBlockSizeException | BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// Receive an unencrypted message
public String receiveUnencrypted() {
try {
// Wait until the stream is ready to be read
while (true)
if (in.ready())
break;
return in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// Send an unencrypted message
public void sendUnencrypted(String message) {
out.println(message);
out.flush();
}
// Receive an encrypted message
public String receive() {
try {
// Wait until the stream is ready to be read
while (true)
if (in.ready())
break;
// Obtain the encrypted message
String encrypted = in.readLine();
// Decrypt and return the message
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.DECRYPT_MODE, key);
byte[] decoded = Base64.getDecoder().decode(encrypted);
String utf8 = new String(c.doFinal(decoded));
String plaintext = new String(Base64.getDecoder().decode(utf8));
// Return the message
return plaintext;
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
| IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
return null;
}
}
Start.java is used to initial both the server and client.
package test;
import java.util.Scanner;
public class Start {
public static void main(String args[]) {
Scanner scan = new Scanner(System.in);
System.out.println("1.) Create data server.\n2.) Connect to data server.\nPlease select an option: ");
int option = scan.nextInt();
if (option == 1) { // Setup a server if they choose option one
Server s = new Server();
s.send("Hello");
} else if (option == 2) { // Setup a client if they choose option two
Client c = new Client();
System.out.println(c.receive());
}
// Close scanner
scan.close();
}
}
First, you can't use SecretKeySpec to restore an RSA public key. In your Server's constructor, change
Key clientRSAKey = new SecretKeySpec(encodedClientKey, 0, encodedClientKey.length, "RSA");
to
Key clientRSAKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(encodedClientKey));
Second, you need to decode the base64 encoded encrypted key. In your Client constructor, change
String encryptedKey = receiveUnencrypted();
to
byte[] encryptedKey = Base64.getDecoder().decode(receiveUnencrypted());
Finally, in your Client constructor, change
byte[] AESKey = c.doFinal(encryptedKey.getBytes());
to
byte[] AESKey = c.doFinal(encryptedKey);
After checking multiple posts about the same questions I couldn't fix this error. My code for encrypting and decrypting is the following:
public class MessagesEncrypter {
static String key = "1234567891234567";
public static String encrypt(String input){
byte[] crypted = null;
try{
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skey);
crypted = cipher.doFinal(input.getBytes());
}catch(Exception e){
System.out.println(e.toString());
}
return new String(Base64.encodeBase64(crypted));
}
public static String decrypt(String input){
byte[] output = null;
try{
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skey);
output = cipher.doFinal(Base64.decodeBase64(input));
}catch(Exception e){
System.out.println(e.toString());
}
return new String(output);
}
}
And I get as stated in the title the exception
javax.crypto.IllegalBlockSizeException: Input length must be multiple
of 16 when decrypting with padded cipher.
I'm using this function to encrypt the parameters for multiple REST requests in my jax-rs API, like usernames and IDs for the rooms I'm creating.
----------------------------------EDIT 1 -------------------------------------
As requested bellow are some examples of usage of this methods in my api services and the method POST itself:
#POST
#Path("/register")
#Produces("application/json")
public static Response newPlayer(#FormParam("name") String name,
#FormParam("password") String password,
#FormParam("chips") String chips) throws Exception {
String n = MessagesEncrypter.decrypt(name);
int c = Integer.parseInt(MessagesEncrypter.decrypt(chips));
boolean result = new AccessManager().insertPlayer(n, password, c);
if(result==false){
return Response.status(Response.Status.NOT_ACCEPTABLE).entity("Register Failed for: " + name).build();
}
else {
return Response.ok("Register Successful", MediaType.APPLICATION_JSON).build();
}
}
public static int POST(String path, String[] paramName, String[] paramVal) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
// Define the server endpoint to send the HTTP request to
URL serverUrl =
new URL(urlStandard + path);
HttpURLConnection urlConnection = (HttpURLConnection)serverUrl.openConnection();
// Indicate that we want to write to the HTTP request body
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("POST");
// Create the form content
OutputStream out = urlConnection.getOutputStream();
Writer writer = new OutputStreamWriter(out, "ISO-8859-1");
for (int i = 0; i < paramName.length; i++) {
writer.write(paramName[i]);
writer.write("=");
String value = paramVal[i];
/*
if(paramName[i].equals("password"))
value = paramVal[i];
else
value = MessagesEncrypter.encrypt(paramVal[i]);
*/
writer.write(value);
writer.write("&");
}
writer.close();
out.close();
int response = urlConnection.getResponseCode();
//print result
//System.out.println("POST request returned:" + response);
urlConnection.disconnect();
return response;
}
I am writing an encrypted Server/Client system and I need to be able to encrypt objects (Packets) over the network. My problem involves creating the ObjectOutput and Input streams from the CipherInput and Output Streams.
Firstly, (on the client end), when I create my ObjectInputStream, it just hangs there:
public void connectToServer(String serverIP, int port)
{
try
{
socket = new Socket(serverIP, port);
key = KeyGeneratorWrapper.getEncryptionKey(); // KeyGen
encryptedSocket = new SecretSocket(socket, key);
out = new ObjectOutputStream(encryptedSocket.getOutputStream());
in = new ObjectInputStream(encryptedSocket.getInputStream()); // HANGS HERE
}
catch (IOException e)
{
e.printStackTrace();
}
}
Secondly, (If i comment out making the inputstream on the client side), when I actually send an object from the client to the server, the server keeps waiting for an object to be sent through:
Socket socket = null; // Client's Socket
socket = serverSocket.accept(); // Wait for a new connection
key = KeyGeneratorWrapper.getEncryptionKey(); // Get key for encryption/decryption
encryptedSocket = new SecretSocket(socket, key); // Create secure communication path to receive/send data on
out = new ObjectOutputStream(encryptedSocket.getOutputStream()); // Get the output stream
in = new ObjectInputStream(encryptedSocket.getInputStream()); // Get the input stream for client socket
Packet clientDetails = null;
clientDetails = (Packet)(in.readObject());// SERVER WAITS HERE
System.out.println("Received");
Please note that the "encryptedSocket" returns the CipherOutput and Input Streams
Here is the code for "SecretSocket":
public class SecretSocket
{
private Key key; // Encryption key
private Cipher inCipher; // Decryption Cipher
private Cipher outCipher; // Encryption Cipher
private CipherInputStream in; // Cipher incoming data
private CipherOutputStream out; // Cipher outgoing data
private Socket socket; // UNSAFE COMMUNICATION SOCKET
public static String ALGORITHM = "DES"; // Encryption algorithm
private String currentAlgorithm; // Current encryption algorithm being used
public SecretSocket(Socket s, Key key)
{
this.key = key;
socket = s;
currentAlgorithm = ALGORITHM;
initialize();
}
/**
* Create encryption and decryption ciphers
* #param input
*/
public void initialize()
{
try
{
outCipher = Cipher.getInstance(currentAlgorithm);
outCipher.init(Cipher.ENCRYPT_MODE, key);
inCipher = Cipher.getInstance(currentAlgorithm);
inCipher.init(Cipher.DECRYPT_MODE, key);
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch (NoSuchPaddingException e)
{
e.printStackTrace();
}
catch (InvalidKeyException e)
{
e.printStackTrace();
}
}
/**
* Return incomming datastream
* #param input
*/
public InputStream getInputStream()
{
InputStream is = null;
try
{
is = socket.getInputStream();
}
catch (IOException e)
{
e.printStackTrace();
}
in = new CipherInputStream(is, inCipher);
return in;
}
/**
* Return outgoing datastream
* #param input
*/
public OutputStream getOutputStream()
{
OutputStream os = null;
try
{
os = socket.getOutputStream();
}
catch (IOException e)
{
e.printStackTrace();
}
out = new CipherOutputStream(os, outCipher);
return out;
}
}
Like this:
CipherInputStream cis = new CipherInputStream(socket.getInputStream, inCipher);
ObjectInputStream ois = new ObjectInputStream(cis);
You need to flush the ObjectOutputStream after creating it. Otherwise the peer will block at new ObjectInputStream(...). This isn't always so, but it is so when there is an underlying buffered stream or cipher stream.
Let me explain my problem.
I want to override Socket and ServerSocket classes in order to encrypt my messages in this way:
1) Client sends a random generated symmetric key (AES algorithm) to the Server
2) After that, client and server can communicate by encrypting their messages with this key
3) To exchange the symmetric key the client encrypts it using the public key of the server (RSA algorithm)
I override Socket and ServerSocket, so automatically, when the client opens a Socket, this will send the symmetric key encrypted by the server's public key. The server reads the first 128 byte in the stream, decodes them, and builds the symmetric key.
This part seems work. I check the communication using Wireshark: packets are encrypted and received symmetric key is correctly delivered.
In order to guarantee a transparent use of my Sockets I override the getInputStream and getOutputStream methods, returning a CipheredInputStream and a ChiperedOutputStream.
It doesn't work for now.. When I try to get OutputStream to send data, the program goes through the instruction but it doesn't matter (I check via Wireshark and no packets are sent).
This is the code of the ServerSocket:
public class SecureServerSocket extends ServerSocket {
public SecureServerSocket(int port) throws IOException {
super(port);
}
public Socket accept() throws IOException {
SecureSocket s = new SecureSocket();
implAccept(s);
SecretKey seckey;
InputStream is = s.getInputStream();
byte[] tmp = new byte[128]; //128: length of the key
int i = 0;
while (i < 128) {
tmp[i] = (byte) (is.read() & 0x000000FF);
++i;
}
byte[] mess = EncryptionManager.rsaDecryptPrivate(tmp);
seckey = new SecretKeySpec(mess, "AES");
try {
s.setkey(seckey);
} catch (InvalidKeyException | NoSuchAlgorithmException
| NoSuchPaddingException e) {
e.printStackTrace();
}
return s;
}
}
This is the code of the Socket:
public class SecureSocket extends Socket {
private SecretKey seckey;
private InputStream in = null;
private OutputStream out = null;
private CipherInputStream cin = null;
private CipherOutputStream cout = null;
public SecureSocket() throws IOException {
}
public SecureSocket(String address, int port) throws UnknownHostException,
IOException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException {
super(address, port);
if (out == null) {
this.out = super.getOutputStream();
}
if (in == null) {
this.in = super.getInputStream();
}
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
SecureRandom random = new SecureRandom();
keyGen.init(random);
seckey = keyGen.generateKey();
byte[] mess = EncryptionManager.rsaEncryptPublic(seckey.getEncoded());
// writing the initial message with the AES encryption key
out.write(mess);
// Initialization of the Cipher streams
Cipher cipherEn = Cipher.getInstance("AES");
cipherEn.init(Cipher.ENCRYPT_MODE, seckey);
Cipher cipherDc = Cipher.getInstance("AES");
cipherDc.init(Cipher.DECRYPT_MODE, seckey);
cout = new CipherOutputStream(out, cipherEn);
cin = new CipherInputStream(in, cipherDc);
}
public InputStream getInputStream() throws IOException {
if (cin == null)
return super.getInputStream();
return cin;
}
public OutputStream getOutputStream() throws IOException {
if (cout == null)
return super.getOutputStream();
return cout;
}
public synchronized void close() throws IOException {
OutputStream o = getOutputStream();
o.flush();
super.close();
}
public void setkey(SecretKey seckey) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException, IOException {
this.seckey = seckey;
Cipher cipherEn = Cipher.getInstance("AES");
cipherEn.init(Cipher.ENCRYPT_MODE, seckey);
cout = new CipherOutputStream(super.getOutputStream(), cipherEn);
Cipher cipherDc = Cipher.getInstance("AES");
cipherDc.init(Cipher.DECRYPT_MODE, seckey);
cin = new CipherInputStream(super.getInputStream(), cipherDc);
}
}
I can't figure out where is the problem. Thank you!
The problem is that a Cipher requires a specific amount of data (for example 128 bits) to be able to encrypt it correctly. If you send a file, that's no problem because the last few bits of the file will be sent when you close the stream.
However, you need a padding for network communication. You can specify one for your Cipher instances:
Cipher cipherEnOrDe = Cipher.getInstance("AES/CBC/PKCS5Padding"); //for example, check documentation for more
Using a padding, the Cipher will be able to send your data once you call the flush() method (which you should do whenever you want something to be sent anyway).
Note: Your application is only safe if your client is distributed with the public key. Otherwise, you cannot be sure that you are connecting to the right server in the first place. Anyone can create a public key.