简体   繁体   English

为什么我的 ByteArrays 在写入 Room 数据库后有所不同?

[英]Why are my ByteArrays different after writing to a Room database?

Description描述

I'm attempting to encrypt a token along with its IV to a pair of ByteArrays, serialize it, then write it to a Room database.我试图将一个令牌连同它的 IV 一起加密到一对 ByteArrays,序列化它,然后将它写入 Room 数据库。 The steps are obviously reversed when attempting to decrypt and read it.尝试解密和阅读时,这些步骤显然是相反的。

When repeating the encryption/serialization/deserialization/decryption steps, but without writing it to a database, the given ByteArray decrypts just fine.当重复加密/序列化/反序列化/解密步骤,但不将其写入数据库时,给定的 ByteArray 解密就可以了。 Writing it gives me the following error on decryption:写它给我以下解密错误:

java.io.StreamCorruptedException: invalid stream header

I'm struggling to understand why this happens, and I'd appreciate the help.我正在努力理解为什么会发生这种情况,非常感谢您的帮助。

Code代码

ByteArray Functions字节数组函数

@Suppress("UNCHECKED_CAST")
fun <T : Serializable> fromByteArray(byteArray: ByteArray): T {
    val inputStream = ByteArrayInputStream(byteArray)
    val objectInput = ObjectInputStream(inputStream)
    val result = objectInput.readObject() as T
    
    objectInput.close()
    inputStream.close()
    return result
}

fun Serializable.toByteArray(): ByteArray {
    val outputStream = ByteArrayOutputStream()
    val objectOutput = ObjectOutputStream(outputStream)
    
    objectOutput.writeObject(this)
    objectOutput.flush()
    
    val result = outputStream.toByteArray()
    
    outputStream.close()
    objectOutput.close()
    
    return result
}

Encryption Functions加密功能

    override fun <T : Serializable> encryptData(data: T): Pair<ByteArray, ByteArray> {
        var temp = data.toByteArray()
        
        if (temp.size % 16 != 0) {
            temp = temp.copyOf(
                (temp.size + 16) - (temp.size % 16)
            )
        }
        
        cipher.init(Cipher.ENCRYPT_MODE, getKey())

        val ivBytes = cipher.iv
        val encryptedArray = cipher.doFinal(temp)
        
        return Pair(ivBytes, encryptedArray)
    }
    
    @Suppress("UNCHECKED_CAST")
    override fun <T> decryptData(ivBytes: ByteArray, data: ByteArray): T {
        val ivSpec = IvParameterSpec(ivBytes)
        
        cipher.init(Cipher.DECRYPT_MODE, getKey(), ivSpec)
        val tempArray: ByteArray = cipher.doFinal(data)
        return fromByteArray(tempArray) as T
    }

Room Data Class房间资料 Class

data class UserData(
    val profilePictureId: Long?,
    val savedTimestamp: Long = System.currentTimeMillis(),
    @PrimaryKey
    val username: String = "",
    val userToken: Pair<ByteArray, ByteArray>?
)

Database Class数据库 Class

@Database(entities = [UserData::class], version = 1)
@TypeConverters(UserDataConverters::class)
abstract class UserDataDatabase : RoomDatabase() {
    abstract val userDataDao: UserDataDao
    
    companion object {
        const val DB_NAME = "user_data_db"
    }
}

Database DAO数据库DAO

@Dao
interface UserDataDao {
    
    @Query("SELECT * FROM UserData")
    fun loadUserData(): Flow<UserData>
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun updateUserData(userData: UserData)
}

Database Type Converters数据库类型转换器

class UserDataConverters {
    @TypeConverter
    fun fromTokenPair(pair: Pair<ByteArray, ByteArray>): String {
        return Json.encodeToString(pair)
    }
    
    @TypeConverter
    fun toTokenPair(serializedPair: String): Pair<ByteArray, ByteArray> {
        return Json.decodeFromString(serializedPair)
    }
}

So this isn't actually related to Room.所以这实际上与 Room 无关。 My mistake.我的错。

I didn't realise that serializing objects to ByteArrays with ObjectInputStream also writes a header for later serialization with ObjectOutputStream.我没有意识到使用 ObjectInputStream 将对象序列化为 ByteArrays 也会写入一个 header 以便稍后使用 ObjectOutputStream 进行序列化。

When encrypting the serialized data, I was using CDC block mode, which requires padding to a block size divisble by 16. That extra padding caused the aforementioned header to become invalid for accompanying data.加密序列化数据时,我使用的是 CDC 块模式,该模式需要填充到块大小可被 16 整除的块。额外的填充导致上述 header 对伴随数据无效。

Removing the padding raises issues with detecting when padding stops and content starts (copyOf adds zeroes).删除填充会引发检测填充何时停止和内容何时开始的问题(copyOf 添加零)。 With that in mind, and after later finding out that CBC is less secure than GCM (which requires no padding), I changed the block mode to GCM.考虑到这一点,在后来发现 CBC 不如 GCM(不需要填充)安全之后,我将块模式更改为 GCM。

See below for resultant code (irrelevent blocks removed):请参阅下面的结果代码(删除了不相关的块):

    private val keyGenParameterSpec = KeyGenParameterSpec.Builder(
        "TroupetentKeyAlias",
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
    )
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .build()

Also, when decrypting, the previous spec ivParameterSpec() couldn't be used, as GCM requires a tag length.此外,在解密时,无法使用之前的规范ivParameterSpec() ,因为 GCM 需要标签长度。 That was also changed:这也改变了:

    @Suppress("UNCHECKED_CAST")
    override fun <T> decryptData(ivBytes: ByteArray, data: ByteArray): T {
        val ivSpec = GCMParameterSpec(128, ivBytes)
        
        cipher.init(Cipher.DECRYPT_MODE, getKey(), ivSpec)
        val decryptedArray: ByteArray = cipher.doFinal(data)
        return fromByteArray(decryptedArray) as T
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM