2

I am encrypting a file created on the android app in a Bluetooth service. In another class later on I want to decrypt this file and upload it to a server.

For encryption I am using the AndroidX androidx.security:security-crypto:1.0.0-alpha02 library which is a wrapper around Tink. I have read all the developer docs and tutorials I could find for EncryptedFile, EncryptedFile.Builder, and so on.

I encrypt the file as follows:

String keySetAlias = "BilboBaggins";
String keySetPref = "Hobbits";

EncryptedFile m_StudyChannelEncryptedFile = new EncryptedFile.Builder(
    filePath,
    getApplicationContext(),
    masterKeyAlias,
   EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).setKeysetAlias(keySetAlias).setKeysetPrefName(keySetPref).build();
m_output = m_StudyChannelEncryptedFile.openFileOutput();

From here I can write to a file like with a normal FileOutputStream, and from looking at the data that is written in the phone's storage I can confirm that it is encrypted.

Prior to uploading, I attempt to do the same thing in another class and then decrypt it.

String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
String keySetAlias = "BilboBaggins";
String keySetPref = "Hobbits";

EncryptedFile encryptedFile = new EncryptedFile.Builder(
  filePath,
  getApplicationContext(),
  masterKeyAlias,
  EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB).setKeysetAlias(keySetAlias).setKeysetPrefName(keySetPref).build();
// Read channel data file  
FileInputStream fChannel = encryptedFile.openFileInput();
m_Dat1Size = fChannel.available();

From here the issue is that the available size of the file I'm getting is zero - like it doesn't exist. I can confirm that the original data written is not overwritten though as the file on the phone storage still has encrypted data.

I believe that by providing it with a location keySetAlias, keySetPref the EncryptedFile builder should be able to initialise an EncryptedFile instance which will have the correct keys.

I would appreciate any help or insight!

Thank you, Michael

mcfisty
  • 187
  • 10
  • 1
    Have you tried reading from it? `available` just shows the data that is directly available. Just curious if reading will return -1 or actual data. – Maarten Bodewes Feb 20 '20 at 03:03
  • That was a terrific suggestion - reading from the file worked OK. Before that I was using .available() as part of some logic for handling the file size, but I guess that may behave differently if there's a layer of encryption...thanks for the suggestion! – mcfisty Feb 20 '20 at 04:19

2 Answers2

0

For InputStream instances the return value of available cannot be relied upon to return the total number of bytes that can be read:

Returns an estimate of the number of remaining bytes that can be read (or skipped over) from this input stream without blocking by the next invocation of a method for this input stream.

For files on the same system it makes some sense that this is always the amount of bytes that are "left" up to the end of the file. However, for other streams available is mainly a hint about how many bytes you should request. For decryption, available() could simply return the number of bytes that are already available after decryption. It could well be that ciphertext is only decrypted on request, and that it therefore returns 0.


For specific ciphers it may also be tricky to determine if the end of the stream is reached due to the plaintext requiring unpadding (ECB/CBC) or verification & removal of the authentication tag (GCM). That might complicate the calculation of available. The API implementation may just decide to simply return 0 always (which means that a call to read may always block).

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
0

I have created the mentioned class to perform the file encryption and decryption using AES, below is the the code. I have converted the file in to byte array and perform encryption on it and again decrypt it using file and get return the bytes from the function.

class AESEncryptDecrypt private constructor() {
private val secretKey: SecretKey
private val iV: AlgorithmParameterSpec

init {
    this.secretKey = createAESKey(randomString()) as SecretKey
    this.iV = IvParameterSpec(AES_IV.toByteArray())
}

/**
 * Encrypt File
 * @param file : File to Encrypt
 */
fun encryptFile(file: File): ByteArray {
    var cipher: Cipher? = null
    try {
        cipher = Cipher.getInstance(AES_TRANSFORMATION)
        cipher!!.init(Cipher.ENCRYPT_MODE, secretKey, iV)
    } catch (e: NoSuchAlgorithmException) {
        e.printStackTrace()
    } catch (e: NoSuchPaddingException) {
        e.printStackTrace()
    } catch (e: InvalidAlgorithmParameterException) {
        e.printStackTrace()
    } catch (e: InvalidKeyException) {
        e.printStackTrace()
    }
    return cipher!!.doFinal(file.readBytes())
}

/**
 * Encrypt Bytes
 * @param byteArray : Plain Bytes to Encrypted Bytes
 */
fun encryptBytes(byteArray: ByteArray): ByteArray {
    var cipher: Cipher? = null
    try {
        cipher = Cipher.getInstance(AES_TRANSFORMATION)
        cipher!!.init(Cipher.ENCRYPT_MODE, secretKey, iV)
    } catch (e: NoSuchAlgorithmException) {
        e.printStackTrace()
    } catch (e: NoSuchPaddingException) {
        e.printStackTrace()
    } catch (e: InvalidAlgorithmParameterException) {
        e.printStackTrace()
    } catch (e: InvalidKeyException) {
        e.printStackTrace()
    }
    return cipher!!.doFinal(byteArray)
}

/**
 * Decrypt File
 * @param file : File to Decrypt
 */
fun decryptFile(file: File): ByteArray {
    var cipher: Cipher? = null
    try {
        cipher = Cipher.getInstance(AES_TRANSFORMATION)
        cipher!!.init(Cipher.DECRYPT_MODE, secretKey, iV)
    } catch (e: NoSuchAlgorithmException) {
        e.printStackTrace()
    } catch (e: NoSuchPaddingException) {
        e.printStackTrace()
    } catch (e: InvalidAlgorithmParameterException) {
        e.printStackTrace()
    } catch (e: InvalidKeyException) {
        e.printStackTrace()
    }
    return cipher!!.doFinal(file.readBytes())
}


companion object {

    private var INSTANCE: AESEncryptDecrypt? = null

    fun getInstance() =
        INSTANCE ?: synchronized(AESEncryptDecrypt::class.java) {
            INSTANCE ?: AESEncryptDecrypt()
                .also { INSTANCE = it }
        }

    private fun createAESKey(keyValue: String): Key {
        if (keyValue.length != AES_RANDOM_STRING_LENGTH)
            try {
                throw Exception("Key must be exactly 16 characters")
            } catch (e: Exception) {
                e.printStackTrace()
            }
        return SecretKeySpec(keyValue.toByteArray(), AES_TRANSFORMATION)
    }

    private fun randomString(): String {
        return "ABCDE12345ABCDE1"
    }
}
}

Constants values are as mentioned below

const val AES_IV = "abcaqwerabcaqwer"
const val AES_TRANSFORMATION = "AES/CBC/PKCS7Padding"
const val AES_RANDOM_STRING = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$"
const val AES_RANDOM_STRING_LENGTH = 16

I have used for image file encryption and decryption. The file is being stored unencrypted first and then I used the function

For encryption: FileUtils.writeByteArrayToFile(file, AESEncryptDecrypt.getInstance().encryptBytes(byteArray))

AESEncryptDecrypt.getInstance().encryptBytes(byteArray) made my file encrypted secondly the FileUtils.writeByteArrayToFile is the function from which I override my file and make it encrypted.

For decryption:

val file: File? = getFile()
FileUtils.writeByteArrayToFile(
                        file, AESEncryptDecrypt.getInstance().decryptFile(file)
                    )

Note: FileUtils.writeByteArrayToFile is used from the dependency as mentioned below: (Secondly, this function is from the dependency as mentioned.)

implementation 'org.apache.commons:commons-lang3:3.7'
Sibtain Raza
  • 205
  • 2
  • 4