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) 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) KeePassDX(2.9.13)
* Binary image viewer #473 #749 * Binary image viewer #473 #749

View File

@@ -11,8 +11,6 @@ import org.junit.Test
import java.io.DataInputStream import java.io.DataInputStream
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.lang.Exception
import java.security.MessageDigest
class BinaryAttachmentTest { 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 { companion object {
private const val TEST_FILE_CACHE_A = "testA" private const val TEST_FILE_CACHE_A = "testA"
private const val TEST_FILE_CACHE_B = "testB" 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 com.kunzisoft.keepass.stream.readAllBytes
import org.apache.commons.io.output.CountingOutputStream import org.apache.commons.io.output.CountingOutputStream
import java.io.* import java.io.*
import java.security.MessageDigest
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
@@ -176,6 +177,25 @@ class BinaryAttachment : Parcelable {
throw IOException("Unable to delete temp file " + dataFile!!.absolutePath) 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 { override fun equals(other: Any?): Boolean {
if (this === other) if (this === other)
return true return true

View File

@@ -99,7 +99,13 @@ class BinaryPool {
private fun orderedBinaries(): List<KeyBinary> { private fun orderedBinaries(): List<KeyBinary> {
val keyBinaryList = ArrayList<KeyBinary>() val keyBinaryList = ArrayList<KeyBinary>()
for ((key, binary) in pool) { 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 return keyBinaryList
} }
@@ -108,7 +114,7 @@ class BinaryPool {
* To register a binary with a ref corresponding to an ordered index * To register a binary with a ref corresponding to an ordered index
*/ */
fun getBinaryIndexFromKey(key: Int): Int? { fun getBinaryIndexFromKey(key: Int): Int? {
val index = orderedBinaries().indexOfFirst { it.key == key } val index = orderedBinaries().indexOfFirst { it.keys.contains(key) }
return if (index < 0) return if (index < 0)
null null
else else
@@ -118,8 +124,10 @@ class BinaryPool {
/** /**
* Different from doForEach, provide an ordered index to each binary * Different from doForEach, provide an ordered index to each binary
*/ */
fun doForEachOrderedBinary(action: (index: Int, keyBinary: KeyBinary) -> Unit) { fun doForEachOrderedBinary(action: (index: Int, binary: BinaryAttachment) -> Unit) {
orderedBinaries().forEachIndexed(action) 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.writeInt(streamKeySize)
dataOutputStream.write(header.innerRandomStreamKey) dataOutputStream.write(header.innerRandomStreamKey)
database.binaryPool.doForEachOrderedBinary { _, keyBinary -> val binaryCipherKey = database.loadedCipherKey
val protectedBinary = keyBinary.binary ?: throw IOException("Unable to retrieve cipher key to write binaries")
val binaryCipherKey = database.loadedCipherKey database.binaryPool.doForEachOrderedBinary { _, binary ->
?: throw IOException("Unable to retrieve cipher key to write binaries")
// Force decompression to add binary in header // Force decompression to add binary in header
protectedBinary.decompress(binaryCipherKey) binary.decompress(binaryCipherKey)
// Write type binary // Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size // Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(protectedBinary.length + 1)) dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.length + 1))
// Write protected flag // Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (protectedBinary.isProtected) { if (binary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
} }
dataOutputStream.writeByte(flag) dataOutputStream.writeByte(flag)
protectedBinary.getInputDataStream(binaryCipherKey).use { inputStream -> binary.getInputDataStream(binaryCipherKey).use { inputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
dataOutputStream.write(buffer) dataOutputStream.write(buffer)
} }
@@ -499,10 +498,9 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemBinaries) xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
// Use indexes because necessarily (binary header ref is the order) // 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.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString()) xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
val binary = keyBinary.binary
if (binary.length > 0) { if (binary.length > 0) {
if (binary.isCompressed) { if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue) 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