Проблема с расшифровкой файла, зашифрованного с помощью EncryptedFile на Android

Я шифрую файл, созданный в приложении Android, в службе Bluetooth. Позже в другом классе я хочу расшифровать этот файл и загрузить его на сервер.

Для шифрования я использую библиотеку AndroidX androidx.security:security-crypto:1.0.0-alpha02, которая является оболочкой для Tink. Я прочитал все документы и руководства для разработчиков, которые смог найти для EncryptedFile, EncryptedFile.Builder и так далее.

Я шифрую файл следующим образом:

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();

Отсюда я могу писать в файл, как с обычным FileOutputStream, и, глядя на данные, которые записываются в память телефона, я могу подтвердить, что они зашифрованы.

Перед загрузкой я пытаюсь сделать то же самое в другом классе, а затем расшифровать его.

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();

Отсюда проблема в том, что доступный размер файла, который я получаю, равен нулю, как будто его не существует. Я могу подтвердить, что исходные записанные данные не перезаписываются, поскольку файл в памяти телефона все еще содержит зашифрованные данные.

Я считаю, что, предоставив ему расположение keySetAlias, keySetPref, построитель EncryptedFile должен иметь возможность инициализировать экземпляр EncryptedFile, который будет иметь правильные ключи.

Буду признателен за любую помощь или понимание!

Спасибо, Майкл.


person mcfisty    schedule 20.02.2020    source источник
comment
Вы пробовали с него читать? available просто показывает данные, которые доступны напрямую. Просто любопытно, будет ли чтение возвращать -1 или фактические данные.   -  person Maarten Bodewes    schedule 20.02.2020
comment
Это было потрясающее предложение - чтение из файла работало нормально. До этого я использовал . available () как часть некоторой логики для обработки размера файла, но я думаю, что это может вести себя по-другому, если есть уровень шифрования ... спасибо за предложение!   -  person mcfisty    schedule 20.02.2020


Ответы (2)


Для экземпляров InputStream нельзя полагаться на то, что возвращаемое значение available возвращает общее количество байтов, которые можно прочитать:

Возвращает оценку количества оставшихся байтов, которые можно прочитать (или пропустить) из этого входного потока без блокировки при следующем вызове метода для этого входного потока.

Для файлов в той же системе имеет смысл, что это всегда количество байтов, которое «осталось» до конца файла. Однако для других потоков available в основном является подсказкой о том, сколько байтов вы должны запросить. Для расшифровки available() может просто вернуть количество байтов, которые уже доступны после расшифровки. Вполне может быть, что зашифрованный текст расшифровывается только по запросу и поэтому возвращает 0.


Для определенных шифров также может быть сложно определить, достигнут ли конец потока из-за открытого текста, требующего распаковки (ECB/CBC) или проверки и удаления тега аутентификации (ГКМ). Это может усложнить вычисление available. Реализация API может решить просто всегда возвращать 0 (это означает, что вызов read всегда может блокироваться).

person Maarten Bodewes    schedule 20.02.2020

Я создал упомянутый класс для выполнения шифрования и дешифрования файлов с использованием AES, ниже приведен код. Я преобразовал файл в массив байтов и выполнил для него шифрование, а затем снова расшифровал его с помощью файла и вернул байты из функции.

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"
    }
}
}

Значения констант указаны ниже.

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

Я использовал для шифрования и дешифрования файлов изображений. Сначала файл хранится в незашифрованном виде, а затем я использовал функцию

Для шифрования: FileUtils.writeByteArrayToFile(file, AESEncryptDecrypt.getInstance().encryptBytes(byteArray))

AESEncryptDecrypt.getInstance().encryptBytes(byteArray) сделал мой файл зашифрованным, во-вторых, FileUtils.writeByteArrayToFile — это функция, из которой я переопределяю свой файл и делаю его зашифрованным.

Для расшифровки:

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

Примечание. FileUtils.writeByteArrayToFile используется из зависимости, как указано ниже: (Во-вторых, эта функция из упомянутой зависимости.)

implementation 'org.apache.commons:commons-lang3:3.7'
person Sibtain Raza    schedule 20.02.2020