1

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.

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
NobodyTellsMe
  • 113
  • 4
  • 14
  • If you keep reading and writing the complete file contents in before doing any encryption or decryption (not recommended), you might as well use a library that does this in a secure manner. Like this one: https://github.com/tozny/java-aes-crypto – Artjom B. Apr 01 '17 at 16:28
  • @ArtjomB. Nah, the idea is that the file contents is encrypted, then saved. When the user wants to view the contents, it is read from the file, then decrypted. – NobodyTellsMe Apr 01 '17 at 16:50
  • Still hoping for an answer, not solved yet!!! – NobodyTellsMe Apr 02 '17 at 10:14
  • Have you tried that library? – Artjom B. Apr 02 '17 at 11:34
  • I haven't no, because going by your description, it's not what I'm looking for. Atm my main problem is coming from doFinal when decrypting. – NobodyTellsMe Apr 02 '17 at 18:52
  • I've read your code again and see what at least one of the issues is. That's why we require that example inputs and outputs are shown. – Artjom B. Apr 02 '17 at 19:05
  • Okay, thanks for all your help everyone but I have managed to solve the issue. I pretty much re-wrote the code above but still found trouble with decrypting. I found that when passing a user submitted password via an intent along with a filename, the original intent etra was being overwritten so I then used a Bundle to allow me to pass multiple extras with my intent. The code now works fully. Once again, thanks for all your help everybody. :) – NobodyTellsMe Apr 02 '17 at 21:46
  • If you rewrote it, you can post it as your own answer. – Artjom B. Apr 02 '17 at 22:16

2 Answers2

0

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.

Community
  • 1
  • 1
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
-2

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/

Darish
  • 11,032
  • 5
  • 50
  • 70
  • 5
    This will not work on modern versions of Android. SecureRandom a SHA1PRNG algorithm from the Crypto provider is deprecated by Google since API 24. – Martin Revert Apr 01 '17 at 14:50
  • Not only is this deprecated. It also results in different keys depending on your API version. That's because the implementation of "SHA1PRNG" was changed. – Artjom B. Apr 01 '17 at 16:22