【问题标题】:Issue decrypting a file encrypted with EncryptedFile on Android在 Android 上解密使用 EncryptedFile 加密的文件时出现问题
【发布时间】:2020-02-20 01:31:29
【问题描述】:

我正在加密在蓝牙服务中的 android 应用程序上创建的文件。稍后在另一堂课中,我想解密此文件并将其上传到服务器。

对于加密,我使用的是 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 实例。

我将不胜感激任何帮助或见解!

谢谢, 迈克尔

【问题讨论】:

  • 你试过读它吗? available 只显示直接可用的数据。只是好奇读取是否会返回 -1 或实际数据。
  • 这是一个了不起的建议 - 从文件中读取工作正常。在此之前,我使用 .available() 作为处理文件大小的一些逻辑的一部分,但我想如果有一层加密,它的行为可能会有所不同......谢谢你的建议!

标签: android file encryption androidx tink


【解决方案1】:

对于InputStream 实例,不能依赖available 的返回值来返回可读取的总字节数:

返回一个估计可以在下一次调用此方法时从此输入流无阻塞读取(或跳过)的剩余字节数输入流。

对于同一系统上的文件,这总是“剩余”到文件末尾的字节数是有道理的。但是,对于其他流available 主要是关于您应该请求多少字节的提示。对于解密,available() 可以简单地返回解密后已经可用的字节数。很可能密文只在请求时解密,因此它返回 0。


对于特定的密码,由于明文需要 unpadding (ECB/CBC) 或验证和删除 身份验证标签,确定是否到达流的末尾也可能很棘手 (GCM)。这可能会使available 的计算复杂化。 API 实现可能只是决定总是简单地返回0(这意味着对readmay always block 的调用)。

【讨论】:

    【解决方案2】:

    我已经创建了上述类来使用 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'
    

    【讨论】:

      猜你喜欢
      • 2019-11-13
      • 2013-02-26
      • 2021-08-12
      • 2021-05-13
      • 2016-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多