Fix binary deduplication #715

This commit is contained in:
J-Jamet
2021-02-22 18:56:21 +01:00
parent fcd7fd2889
commit fc5ffc5f62
7 changed files with 57 additions and 35 deletions

View File

@@ -1,5 +1,6 @@
KeePassDX(2.9.14)
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation)
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation) #532 #714
* Fix binary deduplication #715
KeePassDX(2.9.13)
* Binary image viewer #473 #749

View File

@@ -11,8 +11,6 @@ import org.junit.Test
import java.io.DataInputStream
import java.io.File
import java.io.InputStream
import java.lang.Exception
import java.security.MessageDigest
class BinaryAttachmentTest {
@@ -132,20 +130,6 @@ class BinaryAttachmentTest {
}
}
private fun BinaryAttachment.md5(): String {
val md = MessageDigest.getInstance("MD5")
return this.getInputDataStream(loadedKey).use { fis ->
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
generateSequence {
when (val bytesRead = fis.read(buffer)) {
-1 -> null
else -> bytesRead
}
}.forEach { bytesRead -> md.update(buffer, 0, bytesRead) }
md.digest().joinToString("") { "%02x".format(it) }
}
}
companion object {
private const val TEST_FILE_CACHE_A = "testA"
private const val TEST_FILE_CACHE_B = "testB"

View File

@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.stream.readAllBytes
import org.apache.commons.io.output.CountingOutputStream
import java.io.*
import java.security.MessageDigest
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
@@ -176,6 +177,25 @@ class BinaryAttachment : Parcelable {
throw IOException("Unable to delete temp file " + dataFile!!.absolutePath)
}
/**
* MD5 of the raw encrypted file in temp folder, only to compare binary data
*/
fun md5(): String {
val md = MessageDigest.getInstance("MD5")
if (dataFile == null)
return ""
return FileInputStream(dataFile).use { fis ->
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
generateSequence {
when (val bytesRead = fis.read(buffer)) {
-1 -> null
else -> bytesRead
}
}.forEach { bytesRead -> md.update(buffer, 0, bytesRead) }
md.digest().joinToString("") { "%02x".format(it) }
}
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true

View File

@@ -99,7 +99,13 @@ class BinaryPool {
private fun orderedBinaries(): List<KeyBinary> {
val keyBinaryList = ArrayList<KeyBinary>()
for ((key, binary) in pool) {
keyBinaryList.add(KeyBinary(key, binary))
// Don't deduplicate
val existentBinary = keyBinaryList.find { it.binary.md5() == binary.md5() }
if (existentBinary == null) {
keyBinaryList.add(KeyBinary(binary, key))
} else {
existentBinary.addKey(key)
}
}
return keyBinaryList
}
@@ -108,7 +114,7 @@ class BinaryPool {
* To register a binary with a ref corresponding to an ordered index
*/
fun getBinaryIndexFromKey(key: Int): Int? {
val index = orderedBinaries().indexOfFirst { it.key == key }
val index = orderedBinaries().indexOfFirst { it.keys.contains(key) }
return if (index < 0)
null
else
@@ -118,8 +124,10 @@ class BinaryPool {
/**
* Different from doForEach, provide an ordered index to each binary
*/
fun doForEachOrderedBinary(action: (index: Int, keyBinary: KeyBinary) -> Unit) {
orderedBinaries().forEachIndexed(action)
fun doForEachOrderedBinary(action: (index: Int, binary: BinaryAttachment) -> Unit) {
orderedBinaries().forEachIndexed { index, keyBinary ->
action.invoke(index, keyBinary.binary)
}
}
/**
@@ -149,7 +157,16 @@ class BinaryPool {
}
/**
* Utility data class to order binaries
* Utility class to order binaries
*/
data class KeyBinary(val key: Int, val binary: BinaryAttachment)
private class KeyBinary(val binary: BinaryAttachment, key: Int) {
val keys = HashSet<Int>()
init {
addKey(key)
}
fun addKey(key: Int) {
keys.add(key)
}
}
}

View File

@@ -137,24 +137,23 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
dataOutputStream.writeInt(streamKeySize)
dataOutputStream.write(header.innerRandomStreamKey)
database.binaryPool.doForEachOrderedBinary { _, keyBinary ->
val protectedBinary = keyBinary.binary
val binaryCipherKey = database.loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to write binaries")
database.binaryPool.doForEachOrderedBinary { _, binary ->
// Force decompression to add binary in header
protectedBinary.decompress(binaryCipherKey)
binary.decompress(binaryCipherKey)
// Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length + 1))
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.length + 1))
// Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (protectedBinary.isProtected) {
if (binary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
}
dataOutputStream.writeByte(flag)
protectedBinary.getInputDataStream(binaryCipherKey).use { inputStream ->
binary.getInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes { buffer ->
dataOutputStream.write(buffer)
}
@@ -499,10 +498,9 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
// Use indexes because necessarily (binary header ref is the order)
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, keyBinary ->
mDatabaseKDBX.binaryPool.doForEachOrderedBinary { index, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
val binary = keyBinary.binary
if (binary.length > 0) {
if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)

View File

@@ -1 +1,2 @@
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation)
* Dark Themes (WARNING: You must reselect your theme if upgrading from an old installation) #532 #714
* Fix binary deduplication #715

View File

@@ -1 +1,2 @@
* Thèmes sombres (ATTENTION: Vous devez reselectionner votre theme si mise à niveau d'une ancienne installation)
* Thèmes sombres (ATTENTION: Vous devez reselectionner votre theme si mise à niveau d'une ancienne installation) #532 #714
* Correction de la duplication des binaires #715