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/
Related
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();
}
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.
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.
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");
}
[...]
}
I have created a method that lets me encrypt and decrypt an object with a password. However, I am using Java's native encryption libraries but these are not very secure. (For small changes in the object I am trying to serialize, I get 208 identical bytes out of a total of 243 encrypted bytes.) I think Shiro has alternatives, but I can't seem to find them (at least in 1.1.0 for password-based-encryption). Here is my code for encrypting. I inject the password value to the class and I'm leaving out any exception handling to keep things simpler:
public String encryptToString(Serializable object) {
SecretKeyFactory keyFactory =
SecretKeyFactory.getInstance(ALGORITHM);
KeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKey secretKey = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(SALT, ITERATIONS);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
// Serialize map
final ByteArrayOutputStream byteArrayOutputStream =
new ByteArrayOutputStream();
CipherOutputStream cout =
new CipherOutputStream(byteArrayOutputStream, cipher);
ObjectOutputStream out = new ObjectOutputStream(cout);
out.writeObject(object);
out.close();
cout.close();
byteArrayOutputStream.close();
return new String(
Base64.encode(byteArrayOutputStream.toByteArray()));
}
And my code for decrypting:
public Object decryptToObject(String encodedString) {
SecretKeyFactory keyFactory =
SecretKeyFactory.getInstance(ALGORITHM);
KeySpec keySpec = new PBEKeySpec(password.toCharArray());
SecretKey secretKey = keyFactory.generateSecret(keySpec);
PBEParameterSpec paramSpec = new PBEParameterSpec(SALT, ITERATIONS);
Cipher decipher = Cipher.getInstance(ALGORITHM);
decipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec);
final ByteArrayInputStream byteArrayInputStream =
new ByteArrayInputStream(Base64.decode(encodedString
.getBytes()));
CipherInputStream cin =
new CipherInputStream(byteArrayInputStream, decipher);
ObjectInputStream in = new ObjectInputStream(cin);
Object result = in.readObject();
in.close();
cin.close();
byteArrayInputStream.close();
return result;
}