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
Related
I am facing the following issue while working with Java cryptography.
error decrjavax.crypto.BadPaddingException: Given final block not
properly padded. Such issues can arise if a bad key is used during
decryption.
I have checked all possible answers, but couldn't find the exact reason behind this.
One observation that when i use AES/CBC/NoPadding in place of AES/CBC/PKCS5Padding, i can execute it successfully.
here is my code snippet.
package demo;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
public class TestEncryption {
private static final int BUFFER_SIZE = 32;
private static final int KEY_ITERATIONS = 65535;
private static final int DEFAULT_KEY_BITS = 128;
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String TRANSFORMATION = "AES";
private static final String PBKDF_2_WITH_HMAC_SHA_256 = "PBKDF2WithHmacSHA256";
private static final int IV_SIZE = 16;
private final Cipher ecipher;
private final Cipher dcipher;
private SecretKey secretKey;
/**
* Initialize the ciphers using the given key.
* #param key
* #param keyBits
*/
public TestEncryption(String key, int keyBits) {
byte[] salt = new byte[8];
if (key.length() < 8) {
throw new IllegalArgumentException("key must contain 8 characters or more");
}
for (int i = 0; i < 8; i = i + 1) {
salt[i] = ((byte) key.charAt(i));
}
char[] password = key.toCharArray();
int keyLength = DEFAULT_KEY_BITS;
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF_2_WITH_HMAC_SHA_256);
if (keyBits == 256) {
keyLength = 256;
}
KeySpec spec = new PBEKeySpec(password, salt, KEY_ITERATIONS, keyLength);
secretKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), TRANSFORMATION);
ecipher = Cipher.getInstance(ALGORITHM);
dcipher = Cipher.getInstance(ALGORITHM);
} catch (InvalidKeySpecException | NoSuchPaddingException | NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to initialize encryption.", e);
}
}
public void encryptFile(File src, File dest){
try {
InputStream inputStream = new FileInputStream(src);
OutputStream outputStream = new FileOutputStream(dest);
CipherOutputStream cipherOutputStream= new CipherOutputStream(outputStream, ecipher);
// Generating IV.
byte[] iv = new byte[IV_SIZE];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// First write the IV at the beginning of the encrypted file.
outputStream.write(iv, 0, IV_SIZE);
System.out.println("key " + secretKey);
// Initialize cipher with IV
ecipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
// Encrypt input file and write in to output
while ((bytesRead = inputStream.read(buffer)) > 0) {
cipherOutputStream.write(buffer, 0, bytesRead);
}
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IOException e) {
System.out.println("error encryption" + e.getMessage());
e.printStackTrace();
}
}
public void decryptFile(File srcFile, File destFile) {
try (
InputStream is = new FileInputStream(srcFile);
OutputStream out = new FileOutputStream(destFile);
CipherInputStream cis = new CipherInputStream(is, dcipher)
) {
// Extract IV
byte[] iv = new byte[IV_SIZE];
is.read(iv, 0, IV_SIZE);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// Initialize cypher with IV
dcipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = cis.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
} catch ( InvalidKeyException | InvalidAlgorithmParameterException | IOException e) {
System.out.println("error decr" + e.getMessage());
e.printStackTrace();
}
}
}
package demo;
import java.io.*;
public class Client {
public static void main(String [] args){
File tempFile =null, src = null, dest = null;
try {
tempFile = new File("temp.txt");
src = new File("C:\\Users\\x\\Desktop\\test.txt");
dest = new File("C:\\Users\\x\\Desktop\\out.txt");
TestEncryption encryption = new TestEncryption("helloworld", 256);
encryption.encryptFile(src, tempFile);
encryption.decryptFile(tempFile, dest);
}
finally {
tempFile.delete();
//src.delete();
//dest.delete();
}
}
}
Your error is the way to use your streams when encrypting:
For a CipherOutputStream it is essential to be closed at the end because only when it is closed the final padding can be written.
In your code however the cipherOutputStream instance is never closed. hence the padding is never written to the encrypted file.
Of course when decrypting the file there is no padding where a padding should be and you are getting the BadPaddingException.
Therefore you should change the encyrption to this:
public void encryptFile(File src, File dest) {
try (InputStream inputStream = new FileInputStream(src);
OutputStream outputStream = new FileOutputStream(dest)) {
try (CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, ecipher)) {
// Generating IV.
byte[] iv = new byte[IV_SIZE];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// First write the IV at the beginning of the encrypted file.
outputStream.write(iv, 0, IV_SIZE);
System.out.println("key 0x" + new BigInteger(1, secretKey.getEncoded()).toString(16));
// Initialize cipher with IV
ecipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
// Encrypt input file and write in to output
while ((bytesRead = inputStream.read(buffer)) >= 0) {
cipherOutputStream.write(buffer, 0, bytesRead);
}
}
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IOException e) {
System.out.println("error encryption" + e.getMessage());
e.printStackTrace();
}
}
public void decryptFile(File srcFile, File destFile) {
try (InputStream is = new FileInputStream(srcFile); OutputStream out = new FileOutputStream(destFile)) {
try (CipherInputStream cis = new CipherInputStream(is, dcipher)) {
// Extract IV
byte[] iv = is.readNBytes(IV_SIZE);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// Initialize cypher with IV
dcipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = cis.read(buffer)) >= 0) {
out.write(buffer, 0, bytesRead);
}
}
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IOException e) {
System.out.println("error decr" + e.getMessage());
e.printStackTrace();
}
}
There is another reason that probably the content which you need to description, but the arguments is null when passed to the mathod, it will be throw you a badpaddedException as well.
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);
I'm trying to convert JAVA Code into Objective - C language. I have a requirement where I should Use the same code that Android developers are using. Code follows as
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class EncDec {
public static void main(String args[])
{
String reqMessage="{\"accountType\":\"ALL\",\"uId\":\"c8ff46be-a083-4009-8a33-fc2d22cc40e3|123456784\",\"deviceId\":\"qvxy1234\"}";
Map requestMap=new HashMap();
requestMap.put("body", reqMessage);
String bodyString=(String) requestMap.get("body");
String authKey="M/98hZivBqJQftMHsPvMgg&&";
String encString= encode(authKey,bodyString);
System.out.println("encString ::: "+ encString);
String decString= decode(authKey,encString);
System.out.println("decString ::: "+ decString);
}
public static String encode(String keyString, String stringToEncode) throws NullPointerException {
if (keyString.length() == 0 || keyString == null) {
throw new NullPointerException("Please give Password");
}
if (stringToEncode.length() == 0 || stringToEncode == null) {
throw new NullPointerException("Please give text");
}
try {
SecretKeySpec skeySpec = getKey(keyString);
byte[] clearText = stringToEncode.getBytes("UTF8");
// IMPORTANT TO GET SAME RESULTS ON iOS and ANDROID
final byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// Cipher is not thread safe
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);
byte[] encryptedByte=cipher.doFinal(clearText);
String encrypedValue = new String(Base64.encodeBase64(encryptedByte));
System.out.println("Encrypted: " + stringToEncode + " -> " + encrypedValue);
return encrypedValue;
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return "";
}
/**
* Decodes a String using AES-128 and Base64
*
* #param context
* #param password
* #param text
* #return desoded String
*/
public static String decode(String password, String text) throws NullPointerException {
if (password.length() == 0 || password == null) {
throw new NullPointerException("Please give Password");
}
if (text.length() == 0 || text == null) {
throw new NullPointerException("Please give text");
}
try {
SecretKey key = getKey(password);
// IMPORTANT TO GET SAME RESULTS ON iOS and ANDROID
final byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
byte[] encrypedPwdBytes = Base64.decodeBase64(text.getBytes());
// cipher is not thread safe
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
byte[] decrypedValueBytes = (cipher.doFinal(encrypedPwdBytes));
String decrypedValue = new String(decrypedValueBytes);
System.out.println("Decrypted: " + text + " -> " + decrypedValue);
return decrypedValue;
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return "";
}
/**
* Generates a SecretKeySpec for given password
*
* #param password
* #return SecretKeySpec
* #throws UnsupportedEncodingException
*/
private static SecretKeySpec getKey(String password) throws UnsupportedEncodingException {
// You can change it to 256 if you wish
int keyLength = 128;
byte[] keyBytes = new byte[keyLength / 8];
// explicitly fill with zeros
Arrays.fill(keyBytes, (byte) 0x0);
// if password is shorter then key length, it will be zero-padded
// to key length
byte[] passwordBytes = password.getBytes("UTF-8");
int length = passwordBytes.length < keyBytes.length ? passwordBytes.length : keyBytes.length;
System.arraycopy(passwordBytes, 0, keyBytes, 0, length);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
return key;
}
}
So I want this to be converted in to Objective C. I don't know how to do this. Help me out in this !!
I have searched some code in JAVA And I tried of doing it. But the problem is it will give some other decrypted data but not the exact one that gives using this code. So If I convert the same thing I may get the exact code I guess.
People might be knowing JAVA as well as Objective c here. Those people can help me i guess.
I'm not sure, but you can try it :).
For the encode phase :
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// Cipher is not thread safe
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParameterSpec);
byte[] encryptedByte=cipher.doFinal(clearText);
String encrypedValue = new String(Base64.encodeBase64(encryptedByte));
Using CCCrypt in Obj-C :
CCCryptorStatus CCCrypt(
CCOperation op, //is kCCEncrypt in your case
CCAlgorithm alg, //is kCCAlgorithmAES128
CCOptions options, //is kCCModeCBC
const void *key, //may be skeySpec
size_t keyLength,
const void *iv, // is your iv key : ivParameterSpec
const void *dataIn,
size_t dataInLength,
void *dataOut, /* data RETURNED here */
size_t dataOutAvailable,
size_t *dataOutMoved)
__OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0);
Please reference : CCCrypt decrypting in AES CBC works even without IV
for more information, Hope this help you.
Hey I got the answer go through these links below you will get the exact code for Android and IOS...
https://gist.github.com/m1entus/f70d4d1465b90d9ee024
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.
My AES encryption program is returning different encrypted values on different machines. Can anyone please help on finding out what needs to be done to ensure the same encrypted value is returned on any machine the program is run..
private static SecretKeySpec secret;
private static String seed;
private static String text;
private static String salt = "Salt123";
private static int pswdIterations = 65536;
private static int keySize = 256;
/**
*
* #param mySeed
*/
public static void setSeed(String mySeed) {
try {
byte[] saltBytes = salt.getBytes("UTF-8");
PBEKeySpec spec = new PBEKeySpec(mySeed.toCharArray(), saltBytes,
pswdIterations, keySize);
SecretKeyFactory factory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
SecretKey secretKey = factory.generateSecret(spec);
secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
}
public static String getEncryptedStringFor(String text) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] encryptedData = cipher.doFinal(text.getBytes("UTF-8"));
return new String(Base64.encodeBase64(encryptedData));
} catch (Exception e) {
System.out.println("Error while encrypting: " + e.toString());
}
return null;
}
public static String getDecryptedStringFor(String text) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secret);
return (new String(cipher.doFinal(Base64
.decodeBase64(text.getBytes("UTF-8")))));
} catch (Exception e) {
System.out.println("Error while decrypting: " + e.toString());
}
return null;
}
Some sample values
seed : seed123
text : #text!
Encrypted value : RoVE3KsjzN0nNxCNsNpRPg==
seed : seed!!
text : #text123!
Encrypted value :X6pfUKCVXXrAEyqKko/kFQ==
The only thing I can see in the code is the following line:
return (new String(cipher.doFinal(Base64.decodeBase64(text.getBytes("UTF-8")))));
Now it looks like this is actually returning a String after decoding with UTF-8. But it doesn't: it uses the platform default:
return (new String(
cipher.doFinal(
Base64.decodeBase64(
text.getBytes("UTF-8")
)
)
));
Of course, the first ( is superfluous anyway, so try this:
byte[] ciphertext = Base64.decodeBase64(text.getBytes("UTF-8"));
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, "UTF-8");
Note that you can also use import static java.nio.charsets.StandardCharsets.UTF_8 nowadays, which lets you do away with the exception as well. I wish they would do the same for StandardCiphers.AES_CBC_PKCS7PADDING :)