Appending IV onto encrypted file for later decryption - java

I am trying to append a 16 byte securely generated IV onto each file after it is encrypted but before it is written. This is so that I can decrypt it later by pulling the IV off the file. This is my code so far:
public static void encrypt(byte[] file, String password, String fileName, String dir)
throws Exception {
SecureRandom r = new SecureRandom();
//128 bit IV generated for each file
byte[] iv = new byte[IV_LENGTH];
r.nextBytes(iv);
SecretKeySpec keySpec = new SecretKeySpec(password.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);
FileOutputStream fos = new FileOutputStream(dir + fileName);
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
// Have to append IV --------
cos.write(file);
fos.flush();
cos.flush();
cos.close();
fos.close();
Any suggestions on how to do this?

FileOutputStream fos = new FileOutputStream(dir + fileName);
fos.write(iv);
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
should do the trick. The IV can be written to the file before the Cipher is even set up.

Related

Java Android - Encrypt/Decrypt file contents

So at the moment I'm trying to write some encrypted text to a file and then be able to read that back in, decrypt it and display it to the user. I'm currently using AES-256 with PBKDF2 password derivation as I'd like to be able to use a user's password to encrypt/decrypt the files. The files are simple text files. The code I am currently using to encrypt some text and save it to a file is below. As far as I can tell, from having a look using adb, this works correctly.
FileOutputStream out = new FileOutputStream(mypath);
String defaultMessage = "Empty File";
int iterationCount = 1000;
int keyLength = 256;
int saltLength = keyLength / 8;
SecureRandom randomGenerator = new SecureRandom();
byte[] salt = new byte[saltLength];
randomGenerator.nextBytes(salt);
KeySpec keySpec = new PBEKeySpec(submittedPassword.toCharArray(), salt, iterationCount, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[cipher.getBlockSize()];
randomGenerator.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivParams);
byte[] ciphertext = cipher.doFinal(defaultMessage.getBytes("UTF-8"));
String finalMessage = ciphertext.toString() + "]" + iv.toString() + "]" + salt.toString();
out.write(finalMessage.getBytes());
out.close();
P.S The above is within a Try/Except.
The code below is what I'm currently trying to use to read in the file and then decrypt it, however, when I try to display the decrypted contents via the test view at the end, it does not show up.
FileInputStream fileInputStream = new FileInputStream(mypath);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer stringBuffer = new StringBuffer();
while ((fileContents = bufferedReader.readLine()) != null) {
stringBuffer.append(fileContents + "\n");
}
String fileContentsString = stringBuffer.toString();
String[] fileContentsList = fileContentsString.split("]");
byte[] cipherText = fileContentsList[0].getBytes();
Toast.makeText(getApplicationContext(), fileContentsList[0], Toast.LENGTH_LONG).show();
byte[] iv = fileContentsList[1].getBytes();
byte[] salt = fileContentsList[2].getBytes();
KeySpec keySpec = new PBEKeySpec(submittedPassword.toCharArray(), salt, 1000, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
byte[] plaintext = cipher.doFinal(cipherText);
String plainrStr = new String(plaintext , "UTF-8");
textEdit.setText(plainrStr);
Hopefully someone can provide me with some assistance here. Again, the second code segment is within a Try/Except statement.
You have multiple problems with your code.
Encryption
This code
String finalMessage = ciphertext.toString() + "]" + iv.toString() + "]" + salt.toString();
does not produce a ciphertext. See here: Java: Syntax and meaning behind "[B#1ef9157"? Binary/Address?
The IV and salt have fixed sizes, so they can be placed in front of the ciphertext. After you've written the whole ciphertext, you need to use something like Base64 or Hex in order to get a String. Modern ciphers like AES produce ciphertexts that can contain bytes of any value which don't always constitute valid character encodings such as UTF-8. Strings are no containers for arbitrary byte[] contents.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(iv);
baos.write(salt);
baos.write(ciphertext);
String finalMessage = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
But you don't need that at all, because you can directly write your ciphertext into the file:
out.write(iv);
out.write(salt);
out.write(ciphertext);
Decryption
Don't use InputStreamReader, a BufferedReader and a StringBuffer for binary data. Otherwise, you'll corrupt your binary ciphertext.
You only need this:
byte[] iv = new byte[16];
byte[] salt = new byte[32];
byte[] ctChunk = new byte[8192]; // not for whole ciphertext, just a buffer
if (16 != fileInputStream.read(iv) || 32 != fileInputStream.read(salt)) {
throw new Exception("IV or salt too short");
}
KeySpec keySpec = new PBEKeySpec(submittedPassword.toCharArray(), salt, 1000, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParams = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivParams);
int read;
ByteArrayOutputStream ctBaos = new ByteArrayOutputStream();
while((read = fileInputStream.read(ctChunk)) > 0) {
ctBaos.write(cipher.update(cipherText, 0, read));
}
ctBaos.write(cipher.doFinal());
String plainrStr = new String(ctBaos.toByteArray(), "UTF-8");
textEdit.setText(plainrStr);
This handles randomization properly but doesn't provide integrity. If you want to detect (malicious) manipulations of your ciphertexts (and generally you'll want that to prevent some attacks), you'd need to use an authenticated mode like GCM or EAX, or employ an encrypt-then-MAC scheme with a strong MAC like HMAC-SHA256.
Use a library like tozny/java-aes-crypto in order to use good defaults.
How to write some encrypted text to a file and then be able to read that back in?
public static byte[] generateKey(String password) throws Exception
{
byte[] keyStart = password.getBytes("UTF-8");
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
sr.setSeed(keyStart);
kgen.init(128, sr);
SecretKey skey = kgen.generateKey();
return skey.getEncoded();
}
public static byte[] encodeFile(byte[] key, byte[] fileData) throws Exception
{
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(fileData);
return encrypted;
}
public static byte[] decodeFile(byte[] key, byte[] fileData) throws Exception
{
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(fileData);
return decrypted;
}
To save a encrypted file to sd do:
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "your_folder_on_sd", "file_name");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
byte[] yourKey = generateKey("password");
byte[] filesBytes = encodeFile(yourKey, yourByteArrayContainigDataToEncrypt);
bos.write(fileBytes);
bos.flush();
bos.close();
To decode a file use:
byte[] yourKey = generateKey("password");
byte[] decodedData = decodeFile(yourKey, bytesOfYourFile);
For reading in a file to a byte Array there a different way out there. A Example: http://examples.javacodegeeks.com/core-java/io/fileinputstream/read-file-in-byte-array-with-fileinputstream/

Trying to remove the last 16 bytes that I appended onto the byte[] (which is the IV) then decrypt

Here is my encryption class:
public static void encrypt(byte[] file, String password, String fileName, String dir) throws Exception {
SecureRandom r = new SecureRandom();
//128 bit IV generated for each file
byte[] iv = new byte[IV_LENGTH];
r.nextBytes(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeySpec keySpec = new SecretKeySpec(password.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivspec);
FileOutputStream fos = new FileOutputStream(dir + fileName);
fos.write(iv);
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
// Have to append IV --------
cos.write(file);
fos.flush();
cos.flush();
cos.close();
fos.close();
}
This is my Decryption method:
public static void decrypt(byte[] file, String password, String fileName, String dir) throws Exception
{
// gets the IV
int ivIndex = file.length - 16;
byte[] truncatedFile = Arrays.copyOfRange(file, 0, file.length - 16);
SecretKeySpec keySpec = new SecretKeySpec(password.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(truncatedFile, ivIndex, 16));
//IvParameterSpec ivspec = new IvParameterSpec(iv);
//
//cipher.init(Cipher.DECRYPT_MODE, keySpec, ivspec);
FileOutputStream fos = new FileOutputStream(dir + fileName);
CipherOutputStream cos = new CipherOutputStream(fos, cipher);
cos.write(file);
fos.flush();
cos.flush();
cos.close();
fos.close();
}
}
As you can see I generated a 16 byte long IV that I have appended to the end of the encrypted file. This is so that I pull off the IV for decryption as well as every filing have a unique IV. I am currently getting the error:
java.lang.IllegalArgumentException: IV buffer too short for given offset/length combination
Aside from the problem generating the error, is my logic correct? will this work?
I generated a 16 byte long IV that I have appended to the end of the encrypted file.
No you didn't. You pre-pended it. Which is a better idea anyway. So you have to read it first, and then construct your Cipher and CipherInputStream and decrypt the remainder of the file input stream. You don't need to read the entire file into memory to accomplish that:
public static void decrypt(File file, String password) throws Exception
{
byte[] iv = new byte[16];
DataInputStream dis = new DataInputStream(new FileInputStream(file));
dis.readFully(iv);
SecretKeySpec keySpec = new SecretKeySpec(password.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
CipherInputStream cis = new CipherInputStream(dis, cipher);
// Now just read plaintext from `cis` and do whatever you want with it.
cis.close();
}

StreamCorruptedException during serialised object decryption (and other byte fields)

my software raised StreamCorruptedException during a deciphering:
My cipher is AES/CBC/PKCS5Padding and my key is obtained with PBKey Derivation method, so I need to create a salt to generate AES128 key.
My goal is to obtain a file formed in this way:
(I will delete exception management code to improve readability)
My cipher code:
char[] password = passwordString.toCharArray();
SecureRandom random = new SecureRandom();
byte salt[] = new byte[SALT_BYTES];
random.nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec keySpec = new PBEKeySpec(password, salt, ITERATION, AES_KEY_BITS);
SecretKey tmp = factory.generateSecret(keySpec);
SecretKey secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
FileOutputStream fout = null;
ObjectOutputStream objOut = null;
fout = new FileOutputStream(PRIVATE_RING_FILENAME);
fout.write(salt);
byte[] ivN = cipher.getIV();
fout.write(ivN);
CipherOutputStream cos = new CipherOutputStream(fout, cipher);
objOut = new ObjectOutputStream(cos);
PrivateKeyRing prvKeyRing = new PrivateKeyRing();
SealedObject sealedObject = new SealedObject(prvKeyRing, cipher);
objOut.writeObject(sealedObject);
fout.close();
objOut.close();
cos.close();
and it works without problem.
My deciphering code:
char[] password = passwordString.toCharArray();
File file = new File(PRIVATE_RING_FILENAME);
FileInputStream fin = new FileInputStream(file);
Cipher cipher = Cipher.getInstance("AES/CFB/PKCS5Padding");
byte[] salt = new byte[SALT_BYTES];
fin.read(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec keySpec = new PBEKeySpec(password, salt, ITERATION, AES_KEY_BITS);
SecretKey = factory.generateSecret(keySpec);
SecretKey secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
byte[] ivN = new byte[AES_BYTES];
fin.read(ivN, 0, AES_BYTES);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(ivN));
CipherInputStream cis = new CipherInputStream(fin, cipher);
ObjectInputStream objIn;
PrivateKeyRing prvKeyRing = null;
SealedObject sealedObject = null;
objIn = new ObjectInputStream(cis);
sealedObject = (SealedObject) objIn.readObject();
prvKeyRing = (PrivateKeyRing) sealedObject.getObject(cipher);
objIn.close();
fin.close();
cis.close();
But StreamCorruptedException: invalid stream header: 73720019 occurs when system execute:
objIn = new ObjectInputStream(cis);
If I try to write the object without ciphering all works.
What do you think about?
I read about some problem when you try to write multiple serialised object but I think this is not the case.
This comes about because you are encrypting and decrypting twice with the same cipher. The object is first sealed with the cipher and then written to the cipher output stream, with the cipher in the state resulting from sealing the object. This does not yield a file that can be decrypted with the cipher in its initial state. You would have to unseal the object first and then read it from the stream, which is impossible. Get rid of either the cipher streams or the sealed object.

how to write IvParameterSpec to a file?

I wrote my SecretKey to a file using the following code. Similarly, I have to write my ivParameterSpec to another file. How can I do this?
SecretKey key = KeyGenerator.getInstance("AES").generateKey();
ObjectOutputStream secretkeyOS = new ObjectOutputStream(new FileOutputStream("publicKeyFile"));
secretkeyOS.writeObject(key);
secretkeyOS.close();
AlgorithmParameterSpec paramSpec1 = new IvParameterSpec(iv);
session.setAttribute("secParam", paramSpec1);
ObjectOutputStream paramOS = new ObjectOutputStream(new FileOutputStream("paramFile"));
paramOS.writeObject(paramSpec1);
paramOS.close();
Don't try to store the IvParameterSpec object. It is not serializable, because it is not intended to be stored.
The IV is the important part. Store this and create a new IvSpec from the IV. I have changed example code from here for AES encryption to store the IV and use the loaded IV to decrypt the ciphertext so you can see a possible workflow.
Please be aware that this is a minimal example. In a real usecase you would store and load the key as well and exception handling should also be reconsidered :-D
public class Test {
public static void main(String[] args) throws Exception {
String message = "This string contains a secret message.";
// generate a key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(128);
byte[] key = keygen.generateKey().getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
byte[] iv = { 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
// initialize the cipher for encrypt mode
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivspec);
// encrypt the message
byte[] encrypted = cipher.doFinal(message.getBytes());
System.out.println("Ciphertext: " + hexEncode(encrypted) + "\n");
// Write IV
FileOutputStream fs = new FileOutputStream(new File("paramFile"));
BufferedOutputStream bos = new BufferedOutputStream(fs);
bos.write(iv);
bos.close();
// Read IV
byte[] fileData = new byte[16];
DataInputStream dis = null;
dis = new DataInputStream(new FileInputStream(new File("paramFile")));
dis.readFully(fileData);
if (dis != null) {
dis.close();
}
// reinitialize the cipher for decryption
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(fileData));
// decrypt the message
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Plaintext: " + new String(decrypted) + "\n");
}
[...]
}

Strange padding after AES Cipher decryption(I THINK)

this is my code for Java AES encryption and decryption : the parameters are obtained using fileinputstream and secretkeyfactory.
public byte[] encrypt(byte[] plainText, SecretKey secretKey, String outputFilePath) throws Exception
{
//select putput file for encrypted text
FileOutputStream fos = new FileOutputStream(outputFilePath);
//Cipher in encrypt mode
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
fos.write(cipher.doFinal(plainText));
fos.close();
//Encrypted text is returned in a byte array.
return cipher.doFinal(plainText);
}
// Decryption function
public String decrypt(String sharedKeyFilepath, String cipherTextFilepath, String plainTextFilepath) throws Exception
{
FileInputStream fis = null;
File sharedKeyFile = new File(sharedKeyFilepath);
byte[] sharedKeyByte = new byte[(int)sharedKeyFile.length()];
File cipherTextFile = new File(cipherTextFilepath);
byte[] cipherText = new byte[(int)cipherTextFile.length()];
File plainTextFile = new File(plainTextFilepath);
byte[] plainText = new byte[(int)plainTextFile.length()];
fis = new FileInputStream(sharedKeyFile);
fis.read(sharedKeyByte);
fis.close();
fis = new FileInputStream(cipherTextFile);
fis.read(cipherText);
fis.close();
fis = new FileInputStream(plainTextFile);
fis.read(plainText);
fis.close();
SecretKey sharedKey = new SecretKeySpec(sharedKeyByte, 0, sharedKeyByte.length, "AES");
// Cipher in decrypt mode
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, sharedKey, new IvParameterSpec(iv));
// Select output file for decrypted cipher Text
FileOutputStream fos = new FileOutputStream("Receiver/DecryptedPlainText.txt");
String decrypted = new String(cipher.doFinal(cipherText));
byte [] decryptedBytes = cipher.doFinal(cipherText);
fos.write(decryptedBytes);
fos.close();
// Return the decrypted text as a string.
return decrypted;
}
The file i send is like :
but the decrypted version is like :
any reasons for this ? Thanks :]

Categories