phpandroidencryptionaesblock-cipher

Decrypt file using AES method in Android


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.


Solution

  • 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):

    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).