Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android cipher doesn't decrypt first 16 bytes / characters of encrypted data

I am working on a file encryption/decryption app. I am using a simple .txt file for testing. When I select the file from within the app and choose to encrypt, the entire file data is encrypted. However, when I decrypt only part of the file data gets decrypted. For some reason the first 16 bytes/characters doesn't get decrypted.

test_file.txt contents: "This sentence is used to check file encryption/decryption results."

encryption result: "¾mÁSTÐÿT:Y­„"O¤]ÞPÕµß~ëqrÈb×ßq²¨†ldµJ,O|56\e^-’@þûÝû"

decryption result: "£ÿÒÜÑàh]VÄþ„- used to check file encryption/decryption results."

There aren't any errors in the logcat.

What am I doing wrong?

Method to encrypt file:

public void encryptFile(String password, String filePath) {
    byte[] encryptedFileData = null;
    byte[] fileData = null;

    try {
        fileData = readFile(filePath);//method provided below

        // 64 bit salt for testing only
        byte[] salt = "goodsalt".getBytes("UTF-8");
        SecretKey key = generateKey(password.toCharArray(), salt);//method provided below

        byte[] keyData = key.getEncoded();
        SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);

        encryptedFileData = cipher.doFinal(fileData);

        saveData(encryptedFileData, filePath);//method provided below
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

Method to read file content:

public byte[] readFile(String filePath) {
    byte[] fileData;
    File file = new File(filePath);
    int size = (int) file.length();
    fileData = new byte[size];

    try {
        BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
        inputStream.read(fileData);
        inputStream.close();
    } 
    catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    catch (IOException e) {
        e.printStackTrace();
    }

    return fileData;
}

Method to generate secret key:

private SecretKey generateKey(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
    // Number of PBKDF2 hardening rounds to use. Larger values increase computation time. You
    // should select a value that causes computation to take >100ms.
    final int iterations = 1000;

    // Generate a 256-bit key
    final int outputKeyLength = 256;

    SecretKeyFactory secretKeyFactory;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // Use compatibility key factory -- only uses lower 8-bits of passphrase chars
        secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit");
    }
    else {
        // Traditional key factory. Will use lower 8-bits of passphrase chars on
        // older Android versions (API level 18 and lower) and all available bits
        // on KitKat and newer (API level 19 and higher).
        secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    }

    KeySpec keySpec = new PBEKeySpec(password, salt, iterations, outputKeyLength);

    return secretKeyFactory.generateSecret(keySpec);
}

Method to save encrypted/decrypted data to the file:

private void saveData(byte[] newFileData, String filePath) {
    File file = new File(filePath);

    try {
        BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));

        outputStream.write(newFileData);
        outputStream.flush();
        outputStream.close();
    }
    catch (IOException e) {
        e.printStackTrace();
    }
}

Method to decrypt file:

public void decryptFile(String password, String filePath) {
    byte[] decryptedFileData = null;
    byte[] fileData = null;

    try {
        fileData = readFile(filePath);

        byte[] salt = "goodsalt".getBytes("UTF-8");//generateSalt();
        SecretKey key = generateKey(password.toCharArray(), salt);

        byte[] keyData = key.getEncoded();
        SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, sKeySpec);

        decryptedFileData = cipher.doFinal(fileData);

        saveData(decryptedFileData, filePath);
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

This line of code encrypts the file:

//simple password for testing only
encryptor.encryptFile("password", "storage/emulated/0/Download/test_file.txt");

This line decrypts the file:

encryptor.decryptFile("password", "storage/emulated/0/Download/test_file.txt");

Edit: Thanks to DarkSquirrel42 and Oncaphillis. You guys are awesome!

Adding this line of code to both encrypt and decrypt functions solved my problem.

//note: the initialization vector (IV) must be 16 bytes in this case
//so, if a user password is being used to create it, measures must
//be taken to ensure proper IV length; random iv is best and should be
//stored, possibly alongside the encrypted data
IvParameterSpec ivSpec = new IvParameterSpec(password.getBytes("UTF-8"));

and then,

cipher.init(Cipher.XXXXXXX_MODE, sKeySpec, ivSpec);
like image 454
Joshua C. Avatar asked Nov 15 '14 20:11

Joshua C.


People also ask

What is AES encryption in Android?

The AES algorithm is a symmetric block cipher that can encrypt (encipher) and decrypt (decipher) information. It uses only one secret key to encrypt plain data, and uses 128-, 192-, and 256-bit keys to process 128-bit data locks. This algorithm receives data and encrypts it using a password.

Does Android use AES encryption?

The encryption uses AES in CBC mode with random IV. Note that the data stored in the class EncryptedData ( salt , iv , and encryptedData ) can be concatenated to a single byte array. You can then save the data or transmit it to the recipient.


1 Answers

your problem has something to do with the cipher's mode of operation ... cbc, or cipher block chaining mode

in general CBC is simple ... take whatever the output of your previous encryiption block was, and xor that onto the current input before encrypting it

for the first block we obviously have a problem... there is no previous block ... therefore we introduce something called IV ... an initialisation vector ... a block ength of random bytes ...

now ... as you can imagine, you will need the same IV when you want to decrypt ...

since you don't save that, the AES implementation will give you a random IV every time ...

therefore you don't have all information to decrypt block 1 ... which is the first 16 bytes in case of AES ...

when handling CBC mode data, it's allways a good choice to simply prepend the used IV in your cypertext output ... the IV shall just be random ... it is no secret ...

like image 166
DarkSquirrel42 Avatar answered Nov 11 '22 09:11

DarkSquirrel42