Determining that decryption has occurred - java

I am developing a GUI based encryptor/decryptor based on AES-128 bit symmetric encryption.
My problem is that how to determine that decryption has not occurred and show a dialog box "Decryption Failed". The code I have written would always generate a file without a .enc extension regardless of the fact that it is still encrypted !
Hoping to get a answer as always from Stack Overflow's top notch programmers :)
Do note that the decryption process doesn't fail or throws exception ! It's just the fact that it generates a file that's still not decrypted. That we have to stop and that's what I meant !
Code here: (Sorry for bad indentation !)
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.MessageDigest;
public class FileEncryptor{
private String algo;
private String path;
private String password;
public FileEncryptor(String algo,String path, String password) {
this.algo = algo; //setting algo
this.path = path;//setting file path
this.password = password;
}
public void encrypt() throws Exception{
SecureRandom padding = new SecureRandom();
byte[] salt = new byte[16];
padding.nextBytes(salt);
//generating key
byte k[] = password.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
k = sha.digest(k);
k = Arrays.copyOf(k, 16);
SecretKeySpec key = new SecretKeySpec(k,algo);
//creating and initialising cipher and cipher streams
Cipher encrypt = Cipher.getInstance(algo);
encrypt.init(Cipher.ENCRYPT_MODE, key);
//opening streams
FileOutputStream fos =new FileOutputStream(path+".enc");
try(FileInputStream fis =new FileInputStream(path)){
try(CipherOutputStream cout=new CipherOutputStream(fos, encrypt)){
copy(fis,cout);
}
}
}
public void decrypt() throws Exception{
SecureRandom padding = new SecureRandom();
byte[] salt = new byte[16];
padding.nextBytes(salt);
//generating same key
byte k[] = password.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
k = sha.digest(k);
k = Arrays.copyOf(k, 16);
SecretKeySpec key = new SecretKeySpec(k,algo);
//creating and initialising cipher and cipher streams
Cipher decrypt = Cipher.getInstance(algo);
decrypt.init(Cipher.DECRYPT_MODE, key);
//opening streams
FileInputStream fis = new FileInputStream(path);
try(CipherInputStream cin=new CipherInputStream(fis, decrypt)){
try(FileOutputStream fos =new FileOutputStream(path.substring(0,path.lastIndexOf(".")))){
copy(cin,fos);
}
}
}
private void copy(InputStream is,OutputStream os) throws Exception{
byte buf[] = new byte[4096]; //4K buffer set
int read = 0;
while((read = is.read(buf)) != -1) //reading
os.write(buf,0,read); //writing
}
public static void main (String[] args)throws Exception {
System.out.println("Enter Password: ");
new FileEncryptor("AES","sample.txt",new java.util.Scanner(System.in).nextLine()).encrypt();
new FileEncryptor("AES","sample.txt.enc",new java.util.Scanner(System.in).nextLine()).decrypt();
}
}

Without looking at the API calls, the decrypt methods should throw an exception if an error occurs. In your exception handler, you can set a flag that will allow you to display an error message. You can also delay the decrypted file creation till after successful decryption (or at least till after the first block has been successfully decrypted). If decryption then fails further along the line, you can delete the (essentially temporary) decrypted output file and display the error message.
[edit]
I slightly misunderstood the original post, so some suggestions to check for failed decryption (note that these are higher level than AES, so it might be specific to your application only):
Add a checksum to the plaintext data before encryption
Append other metadata (file size, user, date, etc) to the plaintext, and check for these when decrypting
Usually, a padding exception would occur on decryption - check for these (and any other giveaways)
Use PKI (public key infrastructure) functionality such as signatures (this is outside the scope of this answer, and possibly outside the scope of the problem you're trying to solve)

I suggest appending a constant, rather than a checksum, to your data before encryption, and verifying it after encryption.
And the encryption algorithm should use chaining, that means avoid ECB (see here why: http://bobnalice.wordpress.com/2009/01/28/friends-don%E2%80%99t-let-friends-use-ecb-mode-encryption).
Using a constant with chaining, is nearly as good as a checksum and much simpler.

Related

Java AES Decryption with keyFile using BouncyCastle SSL

I am trying to write a Java code decrypt a file encrypted with AES256 using BouncyCastle compatible with OpenSSL decryption.
s_key is the file provided which contains the key that will be used to encrypt and decrypt
Steps to be done: 1 - Read the key file 2 - Use the key provided to decrypt file inputfilename
Below I have use so far but I am getting error:
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;
import javax.crypto.NoSuchPaddingException;
public class test5_encrypt {
public static void main(String[] args) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException {
File file = new File("/home/roxane/key");
String passwordStr = FileUtils.readFileToString(file, "UTF-8");
String outputPath = "/home/roxane/test1";
String inputPath = "/home/roxane/test";
SecureRandom random = new SecureRandom();
byte salt[] = new byte[8];
random.nextBytes(salt);
// Derive 32 bytes key (AES_256) and 16 bytes IV
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
pbeGenerator.init(password, salt);
ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128);// keySize, ivSize in bits
System.out.println(parameters.getIV());
// Decrypt with AES-256
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
// Encrypt chunkwise (for large data)
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
cipher.init(false, parameters);
try (FileInputStream fis = new FileInputStream(inputPath);
CipherOutputStream cos = new CipherOutputStream(fos, cipher)) {
int bytesRead = -1;
byte[] buffer = new byte[64 * 1024 * 1024];
while ((bytesRead = fis.read(buffer)) != -1) {
cos.write(buffer, 0, bytesRead);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Error:
Exception in thread "main" java.lang.RuntimeException: org.bouncycastle.crypto.io.InvalidCipherTextIOException: Error finalising cipher data
at decrypt.test5_encrypt.main(test5_encrypt.java:61)
Caused by: org.bouncycastle.crypto.io.InvalidCipherTextIOException: Error finalising cipher data
at org.bouncycastle.crypto.io.CipherOutputStream.close(Unknown Source)
at decrypt.test5_encrypt.main(test5_encrypt.java:59)
Caused by: org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
When using a password, OpenSSL stores the ciphertext in a specific format, namely the ASCII encoding of Salted__, followed by the 8 bytes salt, then the actual ciphertext.
During decryption, the salt must not be randomly generated (as it is done in the posted code), otherwise the wrong key and IV will be derived. Instead, the salt must be determined from the metadata of the ciphertext. Also the use of the stream classes must be fixed:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ParametersWithIV;
...
String inputPath = "..."; // path to enc file
String outputPath = "..."; // path to dec file
String passwordStr = "...";
// Decrypt with AES-256, CBC using streams
try (FileInputStream fis = new FileInputStream(inputPath)){
// Determine salt from OpenSSL format
fis.readNBytes(8); // Skip prefix Salted__
byte[] salt = fis.readNBytes(8); // Read salt
// Derive 32 bytes key (AES_256) and 16 bytes IV via EVP_BytesToKey()
byte[] password = passwordStr.getBytes(StandardCharsets.UTF_8);
OpenSSLPBEParametersGenerator pbeGenerator = new OpenSSLPBEParametersGenerator(new MD5Digest()); // SHA256 as of v1.1.0 (if in OpenSSL the default digest is applied)
pbeGenerator.init(password, salt);
ParametersWithIV parameters = (ParametersWithIV) pbeGenerator.generateDerivedParameters(256, 128); // keySize, ivSize in bits
// Decrypt chunkwise (for large data)
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
cipher.init(false, parameters);
try (CipherInputStream cis = new CipherInputStream(fis, cipher);
FileOutputStream fos = new FileOutputStream(outputPath)) {
int bytesRead = -1;
byte[] buffer = new byte[64 * 1024 * 1024]; // chunksize, e.g. 64 MiB
while ((bytesRead = cis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
}
}
This is functionally equivalent to the OpenSSL statement:
openssl enc -d -aes256 -k <passpharse> -in <enc file> -out <dec file>
Note that OpenSSL applied MD5 as digest by default in earlier versions and SHA256 as of v.1.1.0. Code and OpenSSL statement must use the same digest for compatibility.
In the code the digest is explicitly specified, in the OpenSSL statement it can be explicitly set via the -md option so that matching is possible on both sides.
Keep in mind that EVP_BytesToKey(), which is used by default by OpenSSL for key derivation, is deemed insecure nowadays.
Addition regarding Java 8: For Java 8, e.g. the following implementation can be applied for the determination of the salt:
int i = 0;
byte[] firstBlock = new byte[16];
while (i < firstBlock.length) {
i += fis.read(firstBlock, i, firstBlock.length - i);
}
byte[] salt = Arrays.copyOfRange(firstBlock, 8, 16);
The loop is necessary because read(byte[],int,int), unlike readNBytes(int), does not guarantee that the buffer is completely filled (considering here the non-EOF and non-error case).
If you omit the loop (which means using the equivalent read(byte[])), the code will still run for those JVMs which also fill the buffer completely. Since this applies to the most common JVMs for small buffer sizes the code will mostly work, see the comment by dave_thompson_085. However, this is not guaranteed for any JVM and is therefore less robust (though probably not by much).

javax.crypto.BadPaddingException during RSA Decryption

In my Java code, I'm trying to encrypt a String using RSA, with a public key. The String is a Base64 encoded String that represents an Image (Image was converted to String). It will be decrypted using a private key.
During the Encryption, I first got an exception "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes". So, I processed the String (plaintext) in blocks of 189 which then resolved it.
During the Decryption, I got another exception "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes". So, I processed the byte[] (ciphertext), by converting it to a String first, in blocks of 256 which then resolved it as well.
Again, during my decryption process, I end up getting a "javax.crypto.BadPaddingException: Decryption error" Exception, which I have been unable to resolve.
Upon the recommendation of experts on this site, I used "OAEPWithSHA-256AndMGF1Padding". I even tried using No Padding, after other padding methods, to see if the Exception would go away, but it did not work. What have I done wrong?
I was able to identify that the Exception was thrown at the line - decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
- which is in the decryption portion of the main method.
Please bear with me if my coding practices are poor. I'd really prefer to just find out the error behind the exception for now.
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
public class Tester
{
public KeyPair buildKeyPair() throws NoSuchAlgorithmException
{
final int keySize = 2048;
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keySize);
return keyPairGenerator.genKeyPair();
}
public byte[] encrypt(PublicKey publicKey, String message) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(message.getBytes());
}
public String decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception
{
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(encrypted));
}
public byte[] rsaEncrypt(String watermarkMsg, PublicKey publicKey) throws Exception
{
byte[] cipherText = encrypt(publicKey, watermarkMsg);
return cipherText;
}
public String rsaDecrypt(byte[] cipherText, PrivateKey privateKey) throws Exception
{
String plainText = decrypt(privateKey, cipherText);
return plainText;
}
public static void main(String args[]) throws NoSuchAlgorithmException
{
Tester t = new Tester();
String inputImageFilePath = "<file_path_here";
String stringOfImage = null;
byte[] encryptedImage = null;
byte[] encryptedImagePartial = null;
KeyPair keyPair = t.buildKeyPair();
PublicKey pubKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate()
//-----------IMAGE TO STRING CONVERSION----------------
//The imagetostring() function retrieves the image at the file path and converts it into a Base64 encoded String
try
{
stringOfImage = t.imagetostring(inputImageFilePath);
}
catch(Exception e)
{
System.out.println(e.toString());
}
//-----------ENCRYPTION OF STRING----------------
//The encryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 190 bytes"
try
{
String plaintext = stringOfImage;
String plaintextTrimmed = "";
System.out.println(stringOfImage);
encryptedImage = new byte[15512]; //The size is given as 15512 because the length of the particular string was found to be 15512
while(plaintext!="")
{
if(plaintext.length()>189)
{
plaintextTrimmed = plaintext.substring(0, 189);
plaintext = plaintext.substring(189);
}
else
{
plaintextTrimmed = plaintext;
plaintext = "";
}
encryptedImagePartial = t.rsaEncrypt(plaintextTrimmed, pubKey);
encryptedImage = t.concatenate(encryptedImage, encryptedImagePartial);
System.out.println(encryptedImage.length);
}
}
catch(Exception e)
{
System.out.println(e.toString());
}
t.byteDigest(encryptedImage);
//-----------DECRYPTION OF STRING--------------
//The decryption is done in blocks of 189, because earlier I got an exception - "javax.crypto.IllegalBlockSizeException: Data must not be longer than 256 bytes"
try
{
// The ciphertext is located in the variable encryptedImage which is a byte[]
String stringRepOfCipherText = new String(encryptedImage); String cipherTextTrimmed = "";
String decryptedImagePartial;
String decryptedImage = "";
while(stringRepOfCipherText!="")
{
if(stringRepOfCipherText.length()>189)
{
cipherTextTrimmed = stringRepOfCipherText.substring(0, 189);
stringRepOfCipherText = stringRepOfCipherText.substring(189);
}
else
{
cipherTextTrimmed = stringRepOfCipherText;
stringRepOfCipherText = "";
}
decryptedImagePartial = t.rsaDecrypt(cipherTextTrimmed.getBytes(), privateKey);
decryptedImage = decryptedImage + decryptedImagePartial;
}
}
catch(BadPaddingException e)
{
System.out.println(e.toString());
}
catch(Exception e)
{
System.out.println(e.toString());
}
}
}
Also, I noticed a few other examples where KeyFactory was used to generate the keys. Could anyone also tell me the difference between using KeyFactory and what I have used?
You can not cut the ciphertext into arbitrary chunks!
Since you specifically asked for plain RSA without symmetric algorithms involved (which I strongly recommend against!), this is what you need to do:
Find out the maximum payload size for your RSA configuration.
Split your plaintext into chunks of this size
Encrypt each chunk individually and do not simply concatenate them and discard chunk boundaries!
During decryption:
Pass each ciphertext chunk to the decrypt function using the original size it has after encryption. Do not append any data and do not create "substrings".
Concatenate the resulting plaintexts.
Ideally you should use a hybrid encryption scheme:
generate an encryption key (encKey)
encrypt your image using a symmetric algorithm with encKey
encrypt encKey using pubKey with RSA
Symmetric ciphers can be used in different modes of operation, that avoid such length limitations.
First of all, it makes absolutely no sense to first encode the image to base 64. The input of modern ciphers consist of bytes, and images are already bytes. You may want to base 64 encode the ciphertext if you want to store that a string.
The input block size is indeed 190 bytes. You can see a table for RSA / OAEP here (don't forget to upvote!). I'm not sure why you would want to use 189 in that case; my code is however generalized. The output block size is simply the key size for RSA as it is explicitly converted to the key size in bytes (even if it could be smaller).
During decryption you convert the ciphertext to a string. However, string decoding in Java is lossy; if the decoder finds a byte that doesn't represent a character then it is dropped silently. So this won't (always work), resulting for instance in a BadPaddingException. That's OK though, we can keep to binary ciphertext.
So without further ado, some code for you to look at. Note the expansion of the ciphertext with the 66 bytes per block and the poor performance of - mainly - the decryption. Using AES with RSA in a hybrid cryptosystem is highly recommended (and not for the first time for this question).
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
import javax.crypto.Cipher;
public class Tester {
private static final int KEY_SIZE = 2048;
private static final int OAEP_MGF1_SHA256_OVERHEAD = 66;
public static KeyPair buildKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static void main(String args[]) throws Exception {
KeyPair keyPair = Tester.buildKeyPair();
RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// assumes the bitLength is a multiple of 8 (check first!)
int keySizeBytes = pubKey.getModulus().bitLength() / Byte.SIZE;
byte[] image = new byte[1000];
Arrays.fill(image, (byte) 'm');
// --- encryption
final Cipher enc;
try {
enc = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("OAEP with MGF-1 using SHA-256 not available in this runtime", e);
}
enc.init(Cipher.ENCRYPT_MODE, pubKey);
int fragmentsize = keySizeBytes - OAEP_MGF1_SHA256_OVERHEAD;
ByteArrayOutputStream ctStream = new ByteArrayOutputStream();
int off = 0;
while (off < image.length) {
int toCrypt = Math.min(fragmentsize, image.length - off);
byte[] partialCT = enc.doFinal(image, off, toCrypt);
ctStream.write(partialCT);
off += toCrypt;
}
byte[] ct = ctStream.toByteArray();
// --- decryption
Cipher dec = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
dec.init(Cipher.DECRYPT_MODE, privateKey);
ByteArrayOutputStream ptStream = new ByteArrayOutputStream();
off = 0;
while (off < ct.length) {
int toCrypt = Math.min(keySizeBytes, ct.length - off);
byte[] partialPT = dec.doFinal(ct, off, toCrypt);
ptStream.write(partialPT);
off += toCrypt;
}
byte[] pt = ptStream.toByteArray();
// mmmm...
System.out.println(new String(pt, StandardCharsets.US_ASCII));
}
}

Java AES Padding Encryption Error While Transferring Video over TCP

I am currently doing a project in Java to transfer a video file over TCP from a server to a client. The idea is the server will continue running and listen to incoming connection. Once there is an incoming connection from a client, the server will automatically send a video file to the client. (As of now the IP and file name is hard coded). The idea is so that the file can be copied and played at the same time
It is used locally and will automatically turn on VLC from the receiving computer to play the file being transferred. I have done the transfer part with no issues. The only problem comes up when I try to encrypt/decrypt the file. The code I have are below
Runnable Thread FileTransfer Server
public class FileTransferServer {
public static void main(String[] args) throws Exception {
//Initialize Sockets
int i = 0;
ServerSocket ssock = new ServerSocket(6012);
while (true){
ClientConnection CC;
CC = new ClientConnection(ssock.accept());
Thread t = new Thread(CC);
t.start();
}
}
}
Server Java File
import java.io.BufferedInputStream;
import javax.crypto.Cipher;
import java.io.InputStream;
import java.io.OutputStream;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class ClientConnection implements Runnable
{
private Socket socketPort;
public ClientConnection (Socket socketPort)
{
this.socketPort = socketPort;
}
public void run()
{
try {
DataInputStream input = new DataInputStream(socketPort.getInputStream());
String videoName = input.readUTF();
// automatically get local ip
InetAddress IA = InetAddress.getByName("10.0.0.1");
String key = "Maryhadonecat111";
byte[] keyByte = key.getBytes("UTF-8");
System.out.println(keyByte);
System.out.println(keyByte.toString());
//Specify the file
File file = new File("D:\\Temp\\"+videoName);
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
//Get socket's output stream
OutputStream os = socketPort.getOutputStream();
//Read File Contents into contents array
byte[] contents;
long fileLength = file.length();
long current = 0;
long start = System.nanoTime();
while(current!=fileLength){
int size = 1000000;
if(fileLength - current >= size)
current += size;
else{
size = (int)(fileLength - current);
current = fileLength;
}
contents = new byte[size];
bis.read(contents, 0, size);
//os.write(contents);
os.write(CryptoTest1.doEncrypt(contents,keyByte));
System.out.print("Sending file to "+ socketPort.getInetAddress().toString() +" " +(current*100)/fileLength+"% complete!\n");
}
os.flush();
//File transfer done. Close the socket connection!
socketPort.close();
// ssock.close();
System.out.println("File sent succesfully!");
} catch (Exception e)
{
System.out.println(e);
}
}
}
Client Java File
import java.io.BufferedOutputStream;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import javax.crypto.Cipher;
import java.io.InputStream;
import java.io.OutputStream;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
public class FileTransferClient {
public static void main(String[] args) throws Exception{
requestFile("10.0.0.1", "papa.avi");
}
public static void requestFile(String IP, String videoName) throws Exception{
//Initialize socket
Socket socket = new Socket(InetAddress.getByName(IP), 6012);
DataOutputStream output = new DataOutputStream( socket.getOutputStream());
output.writeUTF(videoName);
String key = "Maryhadonecat111";
byte[] keyByte = key.getBytes("UTF-8");
byte[] contents = new byte[1000000];
//Initialize the FileOutputStream to the output file's full path.
FileOutputStream fos = new FileOutputStream("D:\\Temp2\\"+videoName);
BufferedOutputStream bos = new BufferedOutputStream(fos);
InputStream is = socket.getInputStream();
System.out.println("Receiving File");
ProcessBuilder pb = new ProcessBuilder("C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe", "D:\\Temp2\\"+videoName);
Process start = pb.start();
//No of bytes read in one read() call
int bytesRead = 0;
while((bytesRead=is.read(contents))!=-1){
System.out.println("Bytes Received: " + bytesRead);
contents = (CryptoTest1.doDecrypt(contents,keyByte));
bos.write(contents, 0, bytesRead);
}
bos.flush();
socket.close();
System.out.println("File saved successfully!");
}
}
CryptoTest1 Java File
public class CryptoTest1
{
public static byte[] doEncrypt(byte[] msg, byte[] key) throws Exception {
//prepare key
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
//prepare cipher
String cipherALG = "AES/CBC/PKCS5Padding"; // use your preferred algorithm
Cipher cipher = Cipher.getInstance(cipherALG);
String string = cipher.getAlgorithm();
//as iv (Initial Vector) is only required for CBC mode
if (string.contains("CBC")) {
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
IvParameterSpec ivParameterSpec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
} else {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
}
byte[] encMessage = cipher.doFinal(msg);
return encMessage;
}
public static byte[] doDecrypt(byte[] encMsgtoDec, byte[] key) throws Exception {
//prepare key
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
//prepare cipher
String cipherALG = "AES/CBC/PKCS5Padding"; // use your preferred algorithm
Cipher cipher = Cipher.getInstance(cipherALG);
String string = cipher.getAlgorithm();
//as iv (Initial Vector) is only required for CBC mode
if (string.contains("CBC")) {
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
IvParameterSpec ivParameterSpec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
}
byte[] decMsg = cipher.doFinal(encMsgtoDec);
return decMsg;
}
}
Issue
I seem to have no issue encrypting the file and it is being sent over. The issue is decrypting the file. I can't seem to get it working. I've tried a lot of times and most of the errors comes down to "Padding Exception"
I am currently using AES/CBC/PKCS5Padding but I have tried the following
AES/CBC/PKCS5Padding
AES/CBC/NoPadding
AES/ECB/PKCS5Padding
AES/ECB/NoPadding
If I use padding, i will get an exception
javax.crypto.BadPaddingException: Given final block not properly padded
If I do not use padding, I will get an exception of
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes.
Some other exception I have encountered while I was tinkering
Missing Parameter
java.security.InvalidKeyException: Illegal key size
java.security.InvalidKeyException: Invalid AES key length: 64 bytes
I would like to ask if anyone of you would be willing to point me in a proper direction as to what I am doing wrong. I am still very new to Java, so please assume that I have very little knowledge.
I have searched Stackoverflow for a long time and most encryption questions here are on text files, not on an actual video. If the encryption method I am using is not suitable for video, please let me know if there is a better one.
The IV must be the same for both encryption and decryption. Generally a random IV is created on encryption and this must be provided to the decryption method. One way to handle this is to prefix the encrypted data with with the IV so on decryption it will be available.
Notes:
AES is a block cipher so the input must be an exact multiple of the block size, this is generally accomplished with padding.
A bad padding error generally means that either the key, IV or encrypted data is incorrect, not that the padding is incorrect. The padding is incorrect because the decryption failed.

Spring Security Crypto final block not properly padded

I'm trying to create a proof of concept of file encryption with spring security crypto. Per some of the other posts I've found I have downloaded the Unlimited JCE Policy and added the jars to my java_home/jre/lib/security folder.
The error occurs when calling bytesEncryptor.decrypt. The encrypt may be working as there is encrypted content in the file after execution but I'm not sure of a way to confirm that's not where the problem lies. Some other posts about this error that aren't using Spring are saying the key is not right but this can't be because I'm using the same bytesEncryptor object? (Given final block not properly padded)
Stack:
Exception in thread "main" java.lang.IllegalStateException: Unable to invoke Cipher due to bad padding
at org.springframework.security.crypto.encrypt.CipherUtils.doFinal(CipherUtils.java:142)
at org.springframework.security.crypto.encrypt.AesBytesEncryptor.decrypt(AesBytesEncryptor.java:128)
at com.test.encryption.MyTest.crypt(MyTest.java:45)
at com.test.encryption.MyTest.decryptFile(MyTest.java:31)
at com.test.encryption.MyTest.main(MyTest.java:21)
Caused by: 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:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at org.springframework.security.crypto.encrypt.CipherUtils.doFinal(CipherUtils.java:135)
... 4 more
Code:
public static void main(String args[]) throws Exception {
String salt = KeyGenerators.string().generateKey();
BytesEncryptor bytesEncryptor = Encryptors.standard("password", salt);
encryptFile(bytesEncryptor, "C:/test/testIn.txt", "C:/test/testOut.txt");
decryptFile(bytesEncryptor, "C:/test/testOut.txt", "C:/test/testOutDecode.txt");
}
private static void encryptFile (BytesEncryptor bytesEncryptor, String in, String out) throws Exception {
crypt(bytesEncryptor, in, out, true);
}
private static void decryptFile (BytesEncryptor bytesEncryptor, String in, String out) throws Exception {
crypt(bytesEncryptor, in, out, false);
}
private static void crypt (BytesEncryptor bytesEncryptor, String in, String out, boolean encrypt) throws Exception {
byte[] buffer = new byte[1024];
int numRead;
byte[] bytes = null;
InputStream input = new FileInputStream(in);
OutputStream output = new FileOutputStream(out);
while ((numRead = input.read(buffer)) > 0) {
if(encrypt) {
bytes = bytesEncryptor.encrypt(buffer);
} else {
bytes = bytesEncryptor.decrypt(buffer);
}
if (bytes != null) {
output.write(bytes, 0, numRead);
}
}
input.close();
output.close();
}
For what it's worth, I got this message, when I was using the wrong password/salt to decrypt (same length though)
I suspect the problem comes from they way you read your file in chunks of 1024 bytes. AES is a block cipher so operates on blocks of data of a certain size. When it enciphers it will pad output to make sure it fits into chunks of the appropriate size. When you give it data to decipher it expects the data to be similarly padded if necessary.
Try reading the whole file into a byte array, then encrypting and decrypting that. Alternatively you could try and use a chunk size matching the block size you are using for AES (it will be 128, 192 or 256 bits).
Turns out you need to use a CipherInputStream and CipherOutputStream for FileInputStreams and FileOutputStreams.
Below is more or less taken from this http://www.programcreek.com/java-api-examples/index.php?source_dir=cube-master/cube-common/src/main/java/ch/admin/vbs/cube/common/crypto/AESEncrypter.java
Updated code:
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
public class MyTest {
private static String algorithm = "AES/CBC/PKCS5Padding";
public static void main(String args[]) throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
final int AES_KEYLENGTH = 256;
byte[] iv = new byte[AES_KEYLENGTH / 16];
SecureRandom prng = new SecureRandom();
prng.nextBytes(iv);
Cipher aesCipherForEncryption = Cipher.getInstance(algorithm);
Cipher aesCipherForDecryption = Cipher.getInstance(algorithm);
aesCipherForEncryption.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
aesCipherForDecryption.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
encryptFile(aesCipherForEncryption, "C:/test/testIn.txt", "C:/test/testOut.txt");
decryptFile(aesCipherForDecryption, "C:/test/testOut.txt", "C:/test/testOutDecode.txt");
}
private static void encryptFile (Cipher cipher, String in, String out) throws Exception {
crypt(cipher, in, out, true);
}
private static void decryptFile (Cipher cipher, String in, String out) throws Exception {
crypt(cipher, in, out, false);
}
private static void crypt (Cipher cipher, String in, String out, boolean encrypt) throws Exception {
byte[] buffer = new byte[256];
int numRead;
InputStream input = new FileInputStream(in);
OutputStream output = new FileOutputStream(out);
if(encrypt) {
output = new CipherOutputStream(output, cipher);
} else {
input = new CipherInputStream(input, cipher);
}
while ((numRead = input.read(buffer)) >= 0) {
output.write(buffer, 0, numRead);
}
input.close();
output.close();
}
}
Note - I did end up going away from using the Spring Security Crypto implementation.

Can i avoid the cipher reinitialization per encrypt/decrypt call when using random salts per encryption?

Edit
Actually reinitializing the cipher is not that slow. Creating the key itself is slow because of the iteration count.
Also, the iteration count is ignored and never used in the encryption itself, only on the key generation. The JCE api is kind of misleading depending on the chosen algorithm
Original post
As cryptography in Java is quite... cryptographic, im struggling to do some optimizations. In the functional aspect, this class works quite well and i hope it serves as an example of AES encryption usage
I have a performance issue when encrypting and decrypting data using AES implementation of BouncyCastle (im not comparing, thats the only one implementation I tested). Actually this problem is generic to any cipher I decide to use.
The main issue is: can i avoid the two ciphers whole re-initialization per encrypt/decrypt call? They are too expensive
For the sake of simplicity, keep in mind that the following code had its exception handling removed and a lot of simplification was made to keep the focus on the problem. The synchronized blocks are there to guarantee thread safety
By the way, feedbacks on any part of the code are welcome
Thx
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AES {
private static final int ITERATIONS = 120000;
private static final int SALT_SIZE_IN_BYTES = 8;
private static final String algorithm = "PBEWithSHA256And128BitAES-CBC-BC";
private static final byte[] KEY_SALT = "a fixed key salt".getBytes(Charset.forName("UTF-8"));
private Cipher encryptCipher;
private Cipher decryptCipher;
private SecretKey key;
private RandomGenerator randomGenerator = new RandomGenerator();
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
Security.addProvider(new BouncyCastleProvider());
}
public AES(String passphrase) throws Exception {
encryptCipher = Cipher.getInstance(algorithm);
decryptCipher = Cipher.getInstance(algorithm);
PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), KEY_SALT, ITERATIONS);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
key = keyFactory.generateSecret(keySpec);
}
public byte[] encrypt(byte[] data) throws Exception {
byte[] salt = randomGenerator.generateRandom(SALT_SIZE_IN_BYTES);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONS);
data = DataUtil.append(data, salt);
byte[] encrypted;
synchronized (encryptCipher) {
// as a security constrain, it is necessary to use different salts per encryption
// core issue: want to avoid this reinitialization to change the salt that will be used. Its quite time consuming
encryptCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key, parameterSpec);
encrypted = encryptCipher.doFinal(data);
}
return DataUtil.append(encrypted, salt);
}
public byte[] decrypt(byte[] data) throws Exception {
byte[] salt = extractSaltPart(data);
data = extractDataPart(data);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, ITERATIONS);
byte[] decrypted;
synchronized (decryptCipher) {
// as a security constrain, it is necessary to use different salts per encryption
// core issue: want to avoid this reinitialization to change the salt that will be used. Its quite time consuming
decryptCipher.init(javax.crypto.Cipher.DECRYPT_MODE, key, parameterSpec);
decrypted = decryptCipher.doFinal(data);
}
byte[] decryptedSalt = extractSaltPart(decrypted);
if (Arrays.equals(salt, decryptedSalt))
return extractDataPart(decrypted);
else
throw new IllegalArgumentException("Encrypted data is corrupted: Bad Salt");
}
protected byte[] extractDataPart(byte[] bytes) {
return DataUtil.extract(bytes, 0, bytes.length - SALT_SIZE_IN_BYTES);
}
protected byte[] extractSaltPart(byte[] bytes) {
return DataUtil.extract(bytes, bytes.length - SALT_SIZE_IN_BYTES, SALT_SIZE_IN_BYTES);
}
// main method to basic check the code execution
public static void main(String[] args) throws Exception {
String plainText = "some plain text, have fun!";
String passphrase = "this is a secret";
byte[] data = plainText.getBytes(Charset.forName("UTF-8"));
AES cipher = new AES(passphrase);
byte[] encrypted = cipher.encrypt(data);
byte[] decrypted = cipher.decrypt(encrypted);
System.out.println("expected: true, actual: " + Arrays.equals(data, decrypted));
}
}
// Utility class
class RandomGenerator {
private SecureRandom random = new SecureRandom();
public RandomGenerator() {
random = new SecureRandom();
random.nextBoolean();
}
public synchronized byte[] generateRandom(int length) {
byte[] data = new byte[length];
random.nextBytes(data);
return data;
}
}
// Utility class
class DataUtil {
public static byte[] append(byte[] data, byte[] append) {
byte[] merged = new byte[data.length + append.length];
System.arraycopy(data, 0, merged, 0, data.length);
System.arraycopy(append, 0, merged, data.length, append.length);
return merged;
}
public static byte[] extract(byte[] data, int start, int length) {
if (start + length > data.length)
throw new IllegalArgumentException("Cannot extract " + length + " bytes starting from index " + start + " from data with length " + data.length);
byte[] extracted = new byte[length];
System.arraycopy(data, start, extracted, 0, length);
return extracted;
}
}
You're out of luck. If you're picking a new salt each time, that means you're using a new key for encryption/decryption each time, which means you need to call init each time.
If you want something faster, just salt your message:
byte[] salt = randomGenerator.generateRandom(SALT_SIZE_IN_BYTES);
encryptCipher.update(salt);
encrypted = encryptCipher.doFinal(data);
That way, you use the same key every time so you don't need to reinitialize it. (Don't use PBE, just use 128 bit AES/CBC). It's hard to know if that is adequate for your needs without knowing how you plan to apply this encryption in the real world.
p.s. ITERATIONS == 120000? No wonder it is so slow.
Actually reinitializing the cipher is not that slow. Creating the key itself is slow because of the iteration count.
Also, the iteration count is ignored and never used in the encryption itself, only on the key generation. The JCE API is kind of misleading depending on the chosen algorithm
About the salt: Adding a salt to the plain message is completely unnecessary. What I really should use to achieve randomness in each encryption is using a random Initialization Vector, that can be appended or prepended to the ciphertext after the encryption just like the salt.
If you're just transmitting data and need it to be encrypted in flight, use TLS/SSL. Its way faster and won't break.
Make Sure you use some authentication on your cipher-text. Either use a MAC or better use AES in GCM or CCM modes. Otherwise you encryption is insecure.
As to your question : yes its fixable.
Just generate the key once and reuse it/ . AES is safe to use to send multiple messages with the same key. So just derive the key/Cipher once and keep using it.
Just make sure you use a fresh IV for each message.

Categories