Decrypt file using AES method in Android

Viewed 1331

I have encrypted files using AES encryption in php with following code.

$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
openssl_encrypt($contenuto, $ALGORITHM, $password, 0, $IV);

Now I am trying to decrypt it in Android but always I face InvalidKeyException: Key length not 128/192/256 bits error. Here is android code:

String initializationVector = "12dasdq3g5b2434b";
String password = "123";
FileInputStream fis = new FileInputStream(cryptFilepath);
FileOutputStream fos = new FileOutputStream(outputFilePath);
byte[] key = (password).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key,16);
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks, new IvParameterSpec(initializationVector.getBytes()));
CipherInputStream cis = new CipherInputStream(fis, cipher);
int b;
byte[] d = new byte[16];
while((b = cis.read(d)) != -1) {
fos.write(d, 0, b);
}
fos.flush();
fos.close();
cis.close();

Can anyone suggest me how can I do it. Any help would be appreciated.

4 Answers

The original code posted in the question uses streams to read, decrypt and write the respective files. This makes it possible to process files that are larger than the available memory.

However, the originally posted code lacks the Base64 decoding, which is necessary because the ciphertext of the PHP code is Base64 encoded.

Base64 decoding can be comfortably achieved using the Base64InputStream class of Apache Commons Codec, which operates between FileInputStream and CipherInputStream and is therefore easy to integrate:

import org.apache.commons.codec.binary.Base64InputStream;

...

public static void decrypt(String ciphertextFilepath, String decryptedFilePath) throws Exception {
    
    String password = "123";
    String initializationVector = "12dasdq3g5b2434b";

    byte[] key = new byte[16];
    byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
    System.arraycopy(passwordBytes, 0, key, 0, passwordBytes.length);
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
    
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
    
    try (FileInputStream fis = new FileInputStream(ciphertextFilepath);
         Base64InputStream b64is = new Base64InputStream(fis);
         CipherInputStream cis = new CipherInputStream(b64is, cipher);
         FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {
            
        int read;
        byte[] buffer = new byte[16]; // 16 bytes for testing, in practice use a suitable size (depending on your RAM size), e.g. 64 Mi
        while((read = cis.read(buffer)) != -1) {
            fos.write(buffer, 0, read);
        }
    }
}

The other fixed bugs / optimized points are (see also the other answers / comments):

  • Use of the CBC mode analogous to the PHP code
  • Use of the key from the PHP code
  • Explicit specification of the used encoding

Edit: Consideration of an IV, see @Michael Fehr's comment.

Usually a new random IV is generated for each encryption. The IV is not secret and is commonly placed before the ciphertext and the result is Base64 encoded. The recipient can separate both parts, because the size of the IV is known (corresponds to the blocksize). This construct can also be used in combination with the Base64InputStream class, where the IV must be determined between the Base64InputStream instantiation and the Cipher instantiation/initialization:

...
try (FileInputStream fis = new FileInputStream(ciphertextFilepath);
     Base64InputStream b64is = new Base64InputStream(fis)){
        
    byte[] iv = b64is.readNBytes(16); // 16 bytes for AES
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
    
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
            
    try (CipherInputStream cis = new CipherInputStream(b64is, cipher);
         FileOutputStream fos = new FileOutputStream(decryptedFilePath)) {
        ...

If during encryption IV and ciphertext are Base64 encoded separately and then concatenated, delimited by a separator (see @Michael Fehr's comment), the determination of the IV must be done between the FileInputStream and Base64InputStream instantiation (the separator must also be flushed).

The following full working examples show how to deal with the password issue and do Base64-decoding, the examples just work with string instead of files. Please keep in mind what @Topaco stated as openssl encodes the output in a Base64-encoding that needs to get converted to a byte-format before the file can get used with CipherInputStream for decryption!

A third point (not realy a bug) is that on Java/Android side you don't set a charset for the conversion from string to byte arrays - just add the StandardCharsets.UTF_8 and your're fine with that point.

Beware that there is no proper exception handling !

This is my sample PHP-code:

<?php
// https://stackoverflow.com/questions/63113746/decrypt-file-using-aes-method-in-android
$ALGORITHM = 'AES-128-CBC';
$IV = '12dasdq3g5b2434b';
$password = '123';
$plaintext = "my content to encrypt";
echo 'plaintext: ' . $plaintext . PHP_EOL;
$ciphertext = openssl_encrypt($plaintext, $ALGORITHM, $password, 0, $IV);
echo 'ciphertext: ' . $ciphertext . PHP_EOL;
$decryptedtext = openssl_decrypt($ciphertext, $ALGORITHM, $password, 0, $IV);
echo 'decryptedtext: ' . $decryptedtext . PHP_EOL;
?>

Output on PHP-side:

plaintext: my content to encrypt
ciphertext: DElx3eON2WX0MCj2GS8MnD+kn5NOu1i5IOTcrpKegG4=
decryptedtext: my content to encrypt

Sample Java-code:

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class DecryptInJava {
    public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        System.out.println("https://stackoverflow.com/questions/63113746/decrypt-file-using-aes-method-in-android");
        String password = "123";
        String initializationVector = "12dasdq3g5b2434b";
        String ciphertext = "DElx3eON2WX0MCj2GS8MnD+kn5NOu1i5IOTcrpKegG4="; // password 123 in openssl
        // openssl encodes the output in base64 encoding, so first we have to decode it
        byte[] ciphertextByte = Base64.getDecoder().decode(ciphertext);
        // creating a key filled with 16 'x0'
        byte[] key = new byte[16]; // 16 bytes for aes cbc 128
        // copying the password to the key, leaving the x0 at the end
        System.arraycopy(password.getBytes(StandardCharsets.UTF_8), 0, key, 0, password.getBytes(StandardCharsets.UTF_8).length);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));
        // don't use just AES because that defaults to AES/ECB...
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        byte[] decryptedtext = cipher.doFinal(ciphertextByte);
        System.out.println("decryptedtext: " + new String(decryptedtext));
    }
}

Output on Java-side:

decryptedtext: my content to encrypt

With the help of Michael's answer, I wrote down the code which successfully decrypts the file.

Add following dependencies in your app level build.gradle file:

implementation 'commons-io:commons-io:2.7'
implementation 'commons-codec:commons-codec:1.13'

Java code:

public static void decrypt(String path, String outPath) throws Exception {
    String password = "123";
    String initializationVector = "12dasdq3g5b2434b";
    byte[] key = new byte[16]; // 16 bytes for aes cbc 128
    System.arraycopy(password.getBytes(StandardCharsets.UTF_8), 0, key, 0, password.getBytes(StandardCharsets.UTF_8).length);
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector.getBytes(StandardCharsets.UTF_8));

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

    byte[] input_file;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        input_file = Files.readAllBytes(Paths.get(path));
    } else {
        input_file = org.apache.commons.io.FileUtils.readFileToByteArray(new File(path));
    }

    byte[] decodedBytes;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        decodedBytes = Base64.getDecoder().decode(input_file);
    } else {
        decodedBytes = org.apache.commons.codec.binary.Base64.decodeBase64(input_file);
    }

    byte[] decryptedtext = cipher.doFinal(decodedBytes);
    FileOutputStream fos = new FileOutputStream(outPath);
    fos.write(decryptedtext);
    fos.flush();
    fos.close();
}

I hope it will help.

Good answers but want to add my 2 cents regarding Usually a new random IV is generated for each encryption. Change Usually to NEED TO or MUST. I don't know of a reason NOT to use a new IV each time. If you don't and happen to use the same key, the same data will encrypt to the same output each time. This means that an attacker can tell if the decrypted values are the same just by comparing the encrypted values. Encryption protects the original data from prying eyes, you still have to make sure they can't determine anything about the original data as well. If you encrypt enough data, using the same Key and IV for each record, that can give an hacker a place to start an attacking your systems.

Related