Merge branch 'feature/Dynamic_Memory_And_Encrypt_Module' into develop

This commit is contained in:
J-Jamet
2021-03-26 20:06:12 +01:00
206 changed files with 15022 additions and 15791 deletions

View File

@@ -9,7 +9,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 15
targetSdkVersion 30 targetSdkVersion 30
versionCode = 66 versionCode = 66
versionName = "2.9.15" versionName = "2.9.15"
@@ -29,12 +29,6 @@ android {
} }
} }
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt"
}
}
buildTypes { buildTypes {
release { release {
minifyEnabled = false minifyEnabled = false
@@ -126,8 +120,6 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
// Autofill // Autofill
implementation "androidx.autofill:autofill:1.1.0" implementation "androidx.autofill:autofill:1.1.0"
// Crypto
implementation 'org.bouncycastle:bcprov-jdk15on:1.65.01'
// Time // Time
implementation 'joda-time:joda-time:2.10.6' implementation 'joda-time:joda-time:2.10.6'
// Color // Color
@@ -137,6 +129,8 @@ dependencies {
// Apache Commons // Apache Commons
implementation 'commons-io:commons-io:2.8.0' implementation 'commons-io:commons-io:2.8.0'
implementation 'commons-codec:commons-codec:1.15' implementation 'commons-codec:commons-codec:1.15'
// Encrypt lib
implementation project(path: ':crypto')
// Icon pack // Icon pack
implementation project(path: ':icon-pack-classic') implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material') implementation project(path: ':icon-pack-material')

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto
import org.junit.Assert.assertArrayEquals
import java.io.IOException
import java.util.Random
import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
class AESKeyTest : TestCase() {
private lateinit var mRand: Random
@Throws(Exception::class)
override fun setUp() {
super.setUp()
mRand = Random()
}
@Throws(IOException::class)
fun testAES() {
// Test both an old and an even number to test my flip variable
testAESFinalKey(5)
testAESFinalKey(6)
}
@Throws(IOException::class)
private fun testAESFinalKey(rounds: Long) {
val seed = ByteArray(32)
val key = ByteArray(32)
val nativeKey: ByteArray?
val androidKey: ByteArray?
mRand.nextBytes(seed)
mRand.nextBytes(key)
val androidAESKey = AndroidAESKeyTransformer()
androidKey = androidAESKey.transformMasterKey(seed, key, rounds)
val nativeAESKey = NativeAESKeyTransformer()
nativeKey = nativeAESKey.transformMasterKey(seed, key, rounds)
assertArrayEquals("Does not match", androidKey, nativeKey)
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto
import com.kunzisoft.keepass.crypto.CipherFactory
import junit.framework.TestCase
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.Random
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import org.junit.Assert.assertArrayEquals
class AESTest : TestCase() {
private val mRand = Random()
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidAlgorithmParameterException::class)
fun testEncrypt() {
// Test above below and at the blocksize
testFinal(15)
testFinal(16)
testFinal(17)
// Test random larger sizes
val size = mRand.nextInt(494) + 18
testFinal(size)
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, IllegalBlockSizeException::class, BadPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
private fun testFinal(dataSize: Int) {
// Generate some input
val input = ByteArray(dataSize)
mRand.nextBytes(input)
// Generate key
val keyArray = ByteArray(32)
mRand.nextBytes(keyArray)
val key = SecretKeySpec(keyArray, "AES")
// Generate IV
val ivArray = ByteArray(16)
mRand.nextBytes(ivArray)
val iv = IvParameterSpec(ivArray)
val android = CipherFactory.getInstance("AES/CBC/PKCS5Padding", true)
android.init(Cipher.ENCRYPT_MODE, key, iv)
val outAndroid = android.doFinal(input, 0, dataSize)
val nat = CipherFactory.getInstance("AES/CBC/PKCS5Padding")
nat.init(Cipher.ENCRYPT_MODE, key, iv)
val outNative = nat.doFinal(input, 0, dataSize)
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2021 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -19,44 +19,32 @@
*/ */
package com.kunzisoft.keepass.tests.crypto package com.kunzisoft.keepass.tests.crypto
import com.kunzisoft.keepass.utils.readBytesLength
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Test
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.util.*
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.Random
import javax.crypto.BadPaddingException
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream import javax.crypto.CipherOutputStream
import javax.crypto.IllegalBlockSizeException
import javax.crypto.NoSuchPaddingException
import junit.framework.TestCase class EncryptionTest {
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.stream.BetterCipherInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
class CipherTest : TestCase() {
private val rand = Random() private val rand = Random()
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class) @Test
fun testCipherFactory() { fun testCipherFactory() {
val key = ByteArray(32) val key = ByteArray(32)
rand.nextBytes(key)
val iv = ByteArray(16) val iv = ByteArray(16)
rand.nextBytes(iv)
val plaintext = ByteArray(1024) val plaintext = ByteArray(1024)
rand.nextBytes(key)
rand.nextBytes(iv)
rand.nextBytes(plaintext) rand.nextBytes(plaintext)
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID) val aes = EncryptionAlgorithm.AESRijndael.cipherEngine
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv) val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv) val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
@@ -66,20 +54,20 @@ class CipherTest : TestCase() {
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext) assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
} }
@Throws(InvalidKeyException::class, NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidAlgorithmParameterException::class, IllegalBlockSizeException::class, BadPaddingException::class, IOException::class) @Test
fun testCipherStreams() { fun testCipherStreams() {
val MESSAGE_LENGTH = 1024 val length = 1024
val key = ByteArray(32) val key = ByteArray(32)
val iv = ByteArray(16)
val plaintext = ByteArray(MESSAGE_LENGTH)
rand.nextBytes(key) rand.nextBytes(key)
val iv = ByteArray(16)
rand.nextBytes(iv) rand.nextBytes(iv)
val plaintext = ByteArray(length)
rand.nextBytes(plaintext) rand.nextBytes(plaintext)
val aes = CipherFactory.getInstance(AesEngine.CIPHER_UUID) val aes = EncryptionAlgorithm.AESRijndael.cipherEngine
val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv) val encrypt = aes.getCipher(Cipher.ENCRYPT_MODE, key, iv)
val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv) val decrypt = aes.getCipher(Cipher.DECRYPT_MODE, key, iv)
@@ -91,10 +79,9 @@ class CipherTest : TestCase() {
val secrettext = bos.toByteArray() val secrettext = bos.toByteArray()
val bis = ByteArrayInputStream(secrettext) val bis = ByteArrayInputStream(secrettext)
val cis = BetterCipherInputStream(bis, decrypt) val cis = CipherInputStream(bis, decrypt)
val lis = LittleEndianDataInputStream(cis)
val decrypttext = lis.readBytes(MESSAGE_LENGTH) val decrypttext = cis.readBytesLength(length)
assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext) assertArrayEquals("Encryption and decryption failed", plaintext, decrypttext)
} }

View File

@@ -2,10 +2,9 @@ package com.kunzisoft.keepass.tests.stream
import android.content.Context import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.database.BinaryByte import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.database.BinaryFile import com.kunzisoft.keepass.database.element.binary.BinaryFile
import com.kunzisoft.keepass.stream.readAllBytes
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertEquals
import org.junit.Test import org.junit.Test
@@ -25,11 +24,11 @@ class BinaryDataTest {
private val fileB = File(cacheDirectory, TEST_FILE_CACHE_B) private val fileB = File(cacheDirectory, TEST_FILE_CACHE_B)
private val fileC = File(cacheDirectory, TEST_FILE_CACHE_C) private val fileC = File(cacheDirectory, TEST_FILE_CACHE_C)
private val loadedKey = Database.LoadedKey.generateNewCipherKey() private val binaryCache = BinaryCache()
private fun saveBinary(asset: String, binaryData: BinaryFile) { private fun saveBinary(asset: String, binaryData: BinaryFile) {
context.assets.open(asset).use { assetInputStream -> context.assets.open(asset).use { assetInputStream ->
binaryData.getOutputDataStream(loadedKey).use { binaryOutputStream -> binaryData.getOutputDataStream(binaryCache).use { binaryOutputStream ->
assetInputStream.readAllBytes(DEFAULT_BUFFER_SIZE) { buffer -> assetInputStream.readAllBytes(DEFAULT_BUFFER_SIZE) { buffer ->
binaryOutputStream.write(buffer) binaryOutputStream.write(buffer)
} }
@@ -65,11 +64,11 @@ class BinaryDataTest {
saveBinary(TEST_TEXT_ASSET, binaryA) saveBinary(TEST_TEXT_ASSET, binaryA)
saveBinary(TEST_TEXT_ASSET, binaryB) saveBinary(TEST_TEXT_ASSET, binaryB)
saveBinary(TEST_TEXT_ASSET, binaryC) saveBinary(TEST_TEXT_ASSET, binaryC)
binaryA.compress(loadedKey) binaryA.compress(binaryCache)
binaryB.compress(loadedKey) binaryB.compress(binaryCache)
assertEquals("Compress text length failed.", binaryA.getSize(), binaryB.getSize()) assertEquals("Compress text length failed.", binaryA.getSize(), binaryB.getSize())
assertEquals("Compress text MD5 failed.", binaryA.binaryHash(), binaryB.binaryHash()) assertEquals("Compress text MD5 failed.", binaryA.binaryHash(), binaryB.binaryHash())
binaryB.decompress(loadedKey) binaryB.decompress(binaryCache)
assertEquals("Decompress text length failed.", binaryB.getSize(), binaryC.getSize()) assertEquals("Decompress text length failed.", binaryB.getSize(), binaryC.getSize())
assertEquals("Decompress text MD5 failed.", binaryB.binaryHash(), binaryC.binaryHash()) assertEquals("Decompress text MD5 failed.", binaryB.binaryHash(), binaryC.binaryHash())
} }
@@ -82,29 +81,46 @@ class BinaryDataTest {
saveBinary(TEST_IMAGE_ASSET, binaryA) saveBinary(TEST_IMAGE_ASSET, binaryA)
saveBinary(TEST_IMAGE_ASSET, binaryB) saveBinary(TEST_IMAGE_ASSET, binaryB)
saveBinary(TEST_IMAGE_ASSET, binaryC) saveBinary(TEST_IMAGE_ASSET, binaryC)
binaryA.compress(loadedKey) binaryA.compress(binaryCache)
binaryB.compress(loadedKey) binaryB.compress(binaryCache)
assertEquals("Compress image length failed.", binaryA.getSize(), binaryA.getSize()) assertEquals("Compress image length failed.", binaryA.getSize(), binaryA.getSize())
assertEquals("Compress image failed.", binaryA.binaryHash(), binaryA.binaryHash()) assertEquals("Compress image failed.", binaryA.binaryHash(), binaryA.binaryHash())
binaryB = BinaryFile(fileB, true) binaryB = BinaryFile(fileB, true)
binaryB.decompress(loadedKey) binaryB.decompress(binaryCache)
assertEquals("Decompress image length failed.", binaryB.getSize(), binaryC.getSize()) assertEquals("Decompress image length failed.", binaryB.getSize(), binaryC.getSize())
assertEquals("Decompress image failed.", binaryB.binaryHash(), binaryC.binaryHash()) assertEquals("Decompress image failed.", binaryB.binaryHash(), binaryC.binaryHash())
} }
@Test @Test
fun testCompressBytes() { fun testCompressBytes() {
// Test random byte array
val byteArray = ByteArray(50) val byteArray = ByteArray(50)
Random.nextBytes(byteArray) Random.nextBytes(byteArray)
val binaryA = BinaryByte(byteArray) testCompressBytes(byteArray)
val binaryB = BinaryByte(byteArray)
val binaryC = BinaryByte(byteArray) // Test empty byte array
binaryA.compress(loadedKey) testCompressBytes(ByteArray(0))
binaryB.compress(loadedKey) }
private fun testCompressBytes(byteArray: ByteArray) {
val binaryA = binaryCache.getBinaryData("0", true)
binaryA.getOutputDataStream(binaryCache).use { outputStream ->
outputStream.write(byteArray)
}
val binaryB = binaryCache.getBinaryData("1", true)
binaryB.getOutputDataStream(binaryCache).use { outputStream ->
outputStream.write(byteArray)
}
val binaryC = binaryCache.getBinaryData("2", true)
binaryC.getOutputDataStream(binaryCache).use { outputStream ->
outputStream.write(byteArray)
}
binaryA.compress(binaryCache)
binaryB.compress(binaryCache)
assertEquals("Compress bytes decompressed failed.", binaryA.isCompressed, true) assertEquals("Compress bytes decompressed failed.", binaryA.isCompressed, true)
assertEquals("Compress bytes length failed.", binaryA.getSize(), binaryA.getSize()) assertEquals("Compress bytes length failed.", binaryA.getSize(), binaryA.getSize())
assertEquals("Compress bytes failed.", binaryA.binaryHash(), binaryA.binaryHash()) assertEquals("Compress bytes failed.", binaryA.binaryHash(), binaryA.binaryHash())
binaryB.decompress(loadedKey) binaryB.decompress(binaryCache)
assertEquals("Decompress bytes decompressed failed.", binaryB.isCompressed, false) assertEquals("Decompress bytes decompressed failed.", binaryB.isCompressed, false)
assertEquals("Decompress bytes length failed.", binaryB.getSize(), binaryC.getSize()) assertEquals("Decompress bytes length failed.", binaryB.getSize(), binaryC.getSize())
assertEquals("Decompress bytes failed.", binaryB.binaryHash(), binaryC.binaryHash()) assertEquals("Decompress bytes failed.", binaryB.binaryHash(), binaryC.binaryHash())
@@ -115,7 +131,7 @@ class BinaryDataTest {
val binaryA = BinaryFile(fileA) val binaryA = BinaryFile(fileA)
saveBinary(TEST_TEXT_ASSET, binaryA) saveBinary(TEST_TEXT_ASSET, binaryA)
assert(streamAreEquals(context.assets.open(TEST_TEXT_ASSET), assert(streamAreEquals(context.assets.open(TEST_TEXT_ASSET),
binaryA.getInputDataStream(loadedKey))) binaryA.getInputDataStream(binaryCache)))
} }
@Test @Test
@@ -123,7 +139,7 @@ class BinaryDataTest {
val binaryA = BinaryFile(fileA) val binaryA = BinaryFile(fileA)
saveBinary(TEST_IMAGE_ASSET, binaryA) saveBinary(TEST_IMAGE_ASSET, binaryA)
assert(streamAreEquals(context.assets.open(TEST_IMAGE_ASSET), assert(streamAreEquals(context.assets.open(TEST_IMAGE_ASSET),
binaryA.getInputDataStream(loadedKey))) binaryA.getInputDataStream(binaryCache)))
} }
private fun streamAreEquals(inputStreamA: InputStream, private fun streamAreEquals(inputStreamA: InputStream,

View File

@@ -20,9 +20,7 @@
package com.kunzisoft.keepass.tests.utils package com.kunzisoft.keepass.tests.utils
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import junit.framework.TestCase import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@@ -54,7 +52,7 @@ class ValuesTest : TestCase() {
val orig = ByteArray(8) val orig = ByteArray(8)
setArray(orig, value, 8) setArray(orig, value, 8)
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig))) assertArrayEquals(orig, uLongTo8Bytes(bytes64ToULong(orig)))
} }
fun testReadWriteIntZero() { fun testReadWriteIntZero() {
@@ -133,7 +131,7 @@ class ValuesTest : TestCase() {
} }
private fun testReadWriteByte(value: Byte) { private fun testReadWriteByte(value: Byte) {
val dest: Byte = UnsignedInt(UnsignedInt.fromKotlinByte(value)).toKotlinByte() val dest: Byte = UnsignedInt(value.toInt() and 0xFF).toKotlinByte()
assert(value == dest) assert(value == dest)
} }
@@ -144,13 +142,11 @@ class ValuesTest : TestCase() {
expected.set(2008, 1, 2, 3, 4, 5) expected.set(2008, 1, 2, 3, 4, 5)
val actual = Calendar.getInstance() val actual = Calendar.getInstance()
dateTo5Bytes(expected.time, cal)?.let { buf -> actual.time = DateInstant(bytes5ToDate(dateTo5Bytes(expected.time, cal), cal)).date
actual.time = bytes5ToDate(buf, cal).date
}
val jDate = DateInstant(System.currentTimeMillis()) val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate) val intermediate = DateInstant(jDate)
val cDate = bytes5ToDate(dateTo5Bytes(intermediate.date)!!) val cDate = DateInstant(bytes5ToDate(dateTo5Bytes(intermediate.date)))
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR)) assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH)) assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
@@ -183,12 +179,10 @@ class ValuesTest : TestCase() {
ulongBytes[i] = -1 ulongBytes[i] = -1
} }
val bos = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
val leos = LittleEndianDataOutputStream(bos) byteArrayOutputStream.write(UnsignedLong.MAX_BYTES)
leos.writeLong(UnsignedLong.MAX_VALUE) byteArrayOutputStream.close()
leos.close() val uLongMax = byteArrayOutputStream.toByteArray()
val uLongMax = bos.toByteArray()
assertArrayEquals(ulongBytes, uLongMax) assertArrayEquals(ulongBytes, uLongMax)
} }

View File

@@ -125,7 +125,7 @@ class EntryActivity : LockingActivity() {
historyView = findViewById(R.id.history_container) historyView = findViewById(R.id.history_container)
entryContentsView = findViewById(R.id.entry_contents) entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this)) entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryContentsView?.setAttachmentCipherKey(mDatabase?.loadedCipherKey) entryContentsView?.setAttachmentCipherKey(mDatabase)
entryProgress = findViewById(R.id.entry_progress) entryProgress = findViewById(R.id.entry_progress)
lockView = findViewById(R.id.lock_button) lockView = findViewById(R.id.lock_button)

View File

@@ -202,7 +202,7 @@ class EntryEditActivity : LockingActivity(),
// Build fragment to manage entry modification // Build fragment to manage entry modification
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment? entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
if (entryEditFragment == null) { if (entryEditFragment == null) {
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo, mDatabase?.loadedCipherKey) entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo)
} }
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG) .replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG)
@@ -485,7 +485,7 @@ class EntryEditActivity : LockingActivity(),
private fun buildNewAttachment(attachmentToUploadUri: Uri, fileName: String) { private fun buildNewAttachment(attachmentToUploadUri: Uri, fileName: String) {
val compression = mDatabase?.compressionForNewEntry() ?: false val compression = mDatabase?.compressionForNewEntry() ?: false
mDatabase?.buildNewBinaryAttachment(UriUtil.getBinaryDir(this), compression)?.let { binaryAttachment -> mDatabase?.buildNewBinaryAttachment(compression)?.let { binaryAttachment ->
val entryAttachment = Attachment(fileName, binaryAttachment) val entryAttachment = Attachment(fileName, binaryAttachment)
// Ask to replace the current attachment // Ask to replace the current attachment
if ((mDatabase?.allowMultipleAttachments != true && entryEditFragment?.containsAttachment() == true) || if ((mDatabase?.allowMultipleAttachments != true && entryEditFragment?.containsAttachment() == true) ||

View File

@@ -390,9 +390,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) { private fun performedNextEducation(fileDatabaseSelectActivityEducation: FileDatabaseSelectActivityEducation) {
// If no recent files // If no recent files
val createDatabaseEducationPerformed = val createDatabaseEducationPerformed =
createDatabaseButtonView != null && createDatabaseButtonView!!.visibility == View.VISIBLE createDatabaseButtonView != null
&& createDatabaseButtonView!!.visibility == View.VISIBLE
&& mAdapterDatabaseHistory != null && mAdapterDatabaseHistory != null
&& mAdapterDatabaseHistory!!.itemCount > 0 && mAdapterDatabaseHistory!!.itemCount == 0
&& fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation( && fileDatabaseSelectActivityEducation.checkAndPerformedCreateDatabaseEducation(
createDatabaseButtonView!!, createDatabaseButtonView!!,
{ {

View File

@@ -229,21 +229,26 @@ class IconPickerActivity : LockingActivity() {
if (documentFile.length() > MAX_ICON_SIZE) { if (documentFile.length() > MAX_ICON_SIZE) {
iconCustomState.errorStringId = R.string.error_file_to_big iconCustomState.errorStringId = R.string.error_file_to_big
} else { } else {
mDatabase?.buildNewCustomIcon(UriUtil.getBinaryDir(this@IconPickerActivity)) { customIcon, binary -> mDatabase?.buildNewCustomIcon { customIcon, binary ->
if (customIcon != null) { if (customIcon != null) {
iconCustomState.iconCustom = customIcon iconCustomState.iconCustom = customIcon
BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(contentResolver, mDatabase?.let { database ->
iconToUploadUri, binary) BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(
when { contentResolver,
binary == null -> { database,
} iconToUploadUri,
binary.getSize() <= 0 -> { binary)
} when {
mDatabase?.isCustomIconBinaryDuplicate(binary) == true -> { binary == null -> {
iconCustomState.errorStringId = R.string.error_duplicate_file }
} binary.getSize() <= 0 -> {
else -> { }
iconCustomState.error = false database.isCustomIconBinaryDuplicate(binary) -> {
iconCustomState.errorStringId = R.string.error_duplicate_file
}
else -> {
iconCustomState.error = false
}
} }
} }
if (iconCustomState.error) { if (iconCustomState.error) {

View File

@@ -39,6 +39,8 @@ import kotlin.math.max
class ImageViewerActivity : LockingActivity() { class ImageViewerActivity : LockingActivity() {
private var mDatabase: Database? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -59,23 +61,29 @@ class ImageViewerActivity : LockingActivity() {
resources.displayMetrics.heightPixels * 2 resources.displayMetrics.heightPixels * 2
) )
mDatabase = Database.getInstance()
try { try {
progressView.visibility = View.VISIBLE progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment -> intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
supportActionBar?.title = attachment.name supportActionBar?.title = attachment.name
supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryData.getSize())
BinaryDatabaseManager.loadBitmap( val size = attachment.binaryData.getSize()
attachment.binaryData, supportActionBar?.subtitle = Formatter.formatFileSize(this, size)
Database.getInstance().loadedCipherKey,
mImagePreviewMaxWidth mDatabase?.let { database ->
) { bitmapLoaded -> BinaryDatabaseManager.loadBitmap(
if (bitmapLoaded == null) { database,
finish() attachment.binaryData,
} else { mImagePreviewMaxWidth
progressView.visibility = View.GONE ) { bitmapLoaded ->
imageView.setImageBitmap(bitmapLoaded) if (bitmapLoaded == null) {
finish()
} else {
progressView.visibility = View.GONE
imageView.setImageBitmap(bitmapLoaded)
}
} }
} }
} ?: finish() } ?: finish()

View File

@@ -84,7 +84,6 @@ class EntryEditFragment: StylishFragment() {
// Elements to modify the current entry // Elements to modify the current entry
private var mEntryInfo = EntryInfo() private var mEntryInfo = EntryInfo()
private var mBinaryCipherKey: Database.LoadedKey? = null
private var mLastFocusedEditField: FocusedEditField? = null private var mLastFocusedEditField: FocusedEditField? = null
private var mExtraViewToRequestFocus: EditText? = null private var mExtraViewToRequestFocus: EditText? = null
@@ -122,7 +121,9 @@ class EntryEditFragment: StylishFragment() {
attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container) attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container)
attachmentsListView = rootView.findViewById(R.id.entry_attachments_list) attachmentsListView = rootView.findViewById(R.id.entry_attachments_list)
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext()) attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
attachmentsAdapter.binaryCipherKey = arguments?.getSerializable(KEY_BINARY_CIPHER_KEY) as? Database.LoadedKey? // TODO retrieve current database with its unique key
attachmentsAdapter.database = Database.getInstance()
//attachmentsAdapter.database = arguments?.getInt(KEY_DATABASE)
attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize -> attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize ->
if (previousSize > 0 && newSize == 0) { if (previousSize > 0 && newSize == 0) {
attachmentsContainerView.collapse(true) attachmentsContainerView.collapse(true)
@@ -502,7 +503,6 @@ class EntryEditFragment: StylishFragment() {
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
populateEntryWithViews() populateEntryWithViews()
outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo) outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo)
outState.putSerializable(KEY_BINARY_CIPHER_KEY, mBinaryCipherKey)
outState.putParcelable(KEY_LAST_FOCUSED_FIELD, mLastFocusedEditField) outState.putParcelable(KEY_LAST_FOCUSED_FIELD, mLastFocusedEditField)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
@@ -510,15 +510,16 @@ class EntryEditFragment: StylishFragment() {
companion object { companion object {
const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO" const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO"
const val KEY_BINARY_CIPHER_KEY = "KEY_BINARY_CIPHER_KEY" const val KEY_DATABASE = "KEY_DATABASE"
const val KEY_LAST_FOCUSED_FIELD = "KEY_LAST_FOCUSED_FIELD" const val KEY_LAST_FOCUSED_FIELD = "KEY_LAST_FOCUSED_FIELD"
fun getInstance(entryInfo: EntryInfo?, fun getInstance(entryInfo: EntryInfo?): EntryEditFragment {
loadedKey: Database.LoadedKey?): EntryEditFragment { //database: Database?): EntryEditFragment {
return EntryEditFragment().apply { return EntryEditFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putParcelable(KEY_TEMP_ENTRY_INFO, entryInfo) putParcelable(KEY_TEMP_ENTRY_INFO, entryInfo)
putSerializable(KEY_BINARY_CIPHER_KEY, loadedKey) // TODO Unique database key database.key
putInt(KEY_DATABASE, 0)
} }
} }
} }

View File

@@ -45,7 +45,7 @@ import kotlin.math.max
class EntryAttachmentsItemsAdapter(context: Context) class EntryAttachmentsItemsAdapter(context: Context)
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) { : AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
var binaryCipherKey: Database.LoadedKey? = null var database: Database? = null
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null
@@ -82,21 +82,23 @@ class EntryAttachmentsItemsAdapter(context: Context)
if (entryAttachmentState.previewState == AttachmentState.NULL) { if (entryAttachmentState.previewState == AttachmentState.NULL) {
entryAttachmentState.previewState = AttachmentState.IN_PROGRESS entryAttachmentState.previewState = AttachmentState.IN_PROGRESS
// Load the bitmap image // Load the bitmap image
BinaryDatabaseManager.loadBitmap( database?.let { database ->
entryAttachmentState.attachment.binaryData, BinaryDatabaseManager.loadBitmap(
binaryCipherKey, database,
mImagePreviewMaxWidth entryAttachmentState.attachment.binaryData,
) { imageLoaded -> mImagePreviewMaxWidth
if (imageLoaded == null) { ) { imageLoaded ->
entryAttachmentState.previewState = AttachmentState.ERROR if (imageLoaded == null) {
visibility = View.GONE entryAttachmentState.previewState = AttachmentState.ERROR
onBinaryPreviewLoaded?.invoke(entryAttachmentState) visibility = View.GONE
} else { onBinaryPreviewLoaded?.invoke(entryAttachmentState)
entryAttachmentState.previewState = AttachmentState.COMPLETE } else {
setImageBitmap(imageLoaded) entryAttachmentState.previewState = AttachmentState.COMPLETE
if (visibility != View.VISIBLE) { setImageBitmap(imageLoaded)
expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) { if (visibility != View.VISIBLE) {
onBinaryPreviewLoaded?.invoke(entryAttachmentState) expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) {
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
}
} }
} }
} }
@@ -123,8 +125,9 @@ class EntryAttachmentsItemsAdapter(context: Context)
} else { } else {
holder.binaryFileTitle.setTextColor(mTitleColor) holder.binaryFileTitle.setTextColor(mTitleColor)
} }
holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachmentState.attachment.binaryData.getSize()) val size = entryAttachmentState.attachment.binaryData.getSize()
holder.binaryFileSize.text = Formatter.formatFileSize(context, size)
holder.binaryFileCompression.apply { holder.binaryFileCompression.apply {
if (entryAttachmentState.attachment.binaryData.isCompressed) { if (entryAttachmentState.attachment.binaryData.isCompressed) {
text = CompressionAlgorithm.GZip.getName(context.resources) text = CompressionAlgorithm.GZip.getName(context.resources)

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto
import android.os.Build
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.engine.TwofishEngine
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.NoSuchAlgorithmException
import java.security.Security
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
object CipherFactory {
private var blacklistInit = false
private var blacklisted: Boolean = false
init {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
Security.addProvider(BouncyCastleProvider())
}
fun deviceBlacklisted(): Boolean {
if (!blacklistInit) {
blacklistInit = true
// The Acer Iconia A500 is special and seems to always crash in the native crypto libraries
blacklisted = Build.MODEL == "A500"
}
return blacklisted
}
private fun hasNativeImplementation(transformation: String): Boolean {
return transformation == "AES/CBC/PKCS5Padding"
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class)
fun getInstance(transformation: String, androidOverride: Boolean = false): Cipher {
// Return the native AES if it is possible
return if (!deviceBlacklisted() && !androidOverride && hasNativeImplementation(transformation) && NativeLib.loaded()) {
Cipher.getInstance(transformation, AESProvider())
} else {
Cipher.getInstance(transformation)
}
}
/**
* Generate appropriate cipher based on KeePass 2.x UUID's
*/
@Throws(NoSuchAlgorithmException::class)
fun getInstance(uuid: UUID): CipherEngine {
return when (uuid) {
AesEngine.CIPHER_UUID -> AesEngine()
TwofishEngine.CIPHER_UUID -> TwofishEngine()
ChaCha20Engine.CIPHER_UUID -> ChaCha20Engine()
else -> throw NoSuchAlgorithmException("UUID unrecognized.")
}
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.stream.longTo8Bytes
import java.io.IOException
import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Mac
import kotlin.math.min
object CryptoUtil {
fun resizeKey(inBytes: ByteArray, inOffset: Int, cbIn: Int, cbOut: Int): ByteArray {
if (cbOut == 0) return ByteArray(0)
val hash: ByteArray = if (cbOut <= 32) {
hashSha256(inBytes, inOffset, cbIn)
} else {
hashSha512(inBytes, inOffset, cbIn)
}
if (cbOut == hash.size) {
return hash
}
val ret = ByteArray(cbOut)
if (cbOut < hash.size) {
System.arraycopy(hash, 0, ret, 0, cbOut)
} else {
var pos = 0
var r: Long = 0
while (pos < cbOut) {
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size)
System.arraycopy(part, 0, ret, pos, copy)
pos += copy
r++
Arrays.fill(part, 0.toByte())
}
}
Arrays.fill(hash, 0.toByte())
return ret
}
fun hashSha256(data: ByteArray, offset: Int = 0, count: Int = data.size): ByteArray {
return hashGen("SHA-256", data, offset, count)
}
fun hashSha512(data: ByteArray, offset: Int = 0, count: Int = data.size): ByteArray {
return hashGen("SHA-512", data, offset, count)
}
private fun hashGen(transform: String, data: ByteArray, offset: Int, count: Int): ByteArray {
val hash: MessageDigest
try {
hash = MessageDigest.getInstance(transform)
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
val nos = NullOutputStream()
val dos = DigestOutputStream(nos, hash)
try {
dos.write(data, offset, count)
dos.close()
} catch (e: IOException) {
throw RuntimeException(e)
}
return hash.digest()
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto
import org.bouncycastle.crypto.StreamCipher
import org.bouncycastle.crypto.engines.ChaCha7539Engine
import org.bouncycastle.crypto.engines.Salsa20Engine
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.crypto.params.ParametersWithIV
object StreamCipherFactory {
private val SALSA_IV = byteArrayOf(0xE8.toByte(), 0x30, 0x09, 0x4B, 0x97.toByte(), 0x20, 0x5D, 0x2A)
@Throws(Exception::class)
fun getInstance(alg: CrsAlgorithm?, key: ByteArray): StreamCipher {
return when {
alg === CrsAlgorithm.Salsa20 -> getSalsa20(key)
alg === CrsAlgorithm.ChaCha20 -> getChaCha20(key)
else -> throw Exception("Invalid random cipher")
}
}
private fun getSalsa20(key: ByteArray): StreamCipher {
// Build stream cipher key
val key32 = CryptoUtil.hashSha256(key)
val keyParam = KeyParameter(key32)
val ivParam = ParametersWithIV(keyParam, SALSA_IV)
val cipher = Salsa20Engine()
cipher.init(true, ivParam)
return cipher
}
private fun getChaCha20(key: ByteArray): StreamCipher {
// Build stream cipher key
val hash = CryptoUtil.hashSha512(key)
val key32 = ByteArray(32)
val iv = ByteArray(12)
System.arraycopy(hash, 0, key32, 0, 32)
System.arraycopy(hash, 32, iv, 0, 12)
val keyParam = KeyParameter(key32)
val ivParam = ParametersWithIV(keyParam, iv)
val cipher = ChaCha7539Engine()
cipher.init(true, ivParam)
return cipher
}
}

View File

@@ -1,68 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class AesEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray, androidOverride: Boolean): Cipher {
val cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding", androidOverride)
cipher.init(opmode, SecretKeySpec(key, "AES"), IvParameterSpec(IV))
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.AESRijndael
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class TwofishEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray, androidOverride: Boolean): Cipher {
val cipher: Cipher = if (opmode == Cipher.ENCRYPT_MODE) {
CipherFactory.getInstance("Twofish/CBC/ZeroBytePadding", androidOverride)
} else {
CipherFactory.getInstance("Twofish/CBC/NoPadding", androidOverride)
}
cipher.init(opmode, SecretKeySpec(key, "AES"), IvParameterSpec(IV))
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.Twofish
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey
import com.kunzisoft.keepass.crypto.CipherFactory.deviceBlacklisted
object AESKeyTransformerFactory : KeyTransformer() {
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
// Prefer the native final key implementation
val keyTransformer = if (!deviceBlacklisted()
&& NativeAESKeyTransformer.available()) {
NativeAESKeyTransformer()
} else {
// Fall back on the android crypto implementation
AndroidAESKeyTransformer()
}
return keyTransformer.transformMasterKey(seed, key, rounds)
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation;
import com.kunzisoft.keepass.crypto.NativeLib;
import com.kunzisoft.keepass.utils.UnsignedInt;
import java.io.IOException;
public class Argon2Native {
enum CType {
ARGON2_D(0),
ARGON2_I(1),
ARGON2_ID(2);
int cValue = 0;
CType(int i) {
cValue = i;
}
}
public static byte[] transformKey(Argon2Kdf.Type type, byte[] password, byte[] salt, UnsignedInt parallelism,
UnsignedInt memory, UnsignedInt iterations, byte[] secretKey,
byte[] associatedData, UnsignedInt version) throws IOException {
NativeLib.INSTANCE.init();
CType cType = CType.ARGON2_D;
if (type.equals(Argon2Kdf.Type.ARGON2_ID))
cType = CType.ARGON2_ID;
return nTransformMasterKey(
cType.cValue,
password,
salt,
parallelism.toKotlinInt(),
memory.toKotlinInt(),
iterations.toKotlinInt(),
secretKey,
associatedData,
version.toKotlinInt());
}
private static native byte[] nTransformMasterKey(int type, byte[] password, byte[] salt, int parallelism,
int memory, int iterations, byte[] secretKey,
byte[] associatedData, int version) throws IOException;
}

View File

@@ -25,6 +25,8 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
@@ -55,7 +57,10 @@ class LoadDatabaseRunnable(private val context: Context,
mReadonly, mReadonly,
context.contentResolver, context.contentResolver,
UriUtil.getBinaryDir(context), UriUtil.getBinaryDir(context),
Database.LoadedKey.generateNewCipherKey(), { memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
LoadedKey.generateNewCipherKey(),
mFixDuplicateUUID, mFixDuplicateUUID,
progressTaskUpdater) progressTaskUpdater)
} }

View File

@@ -29,14 +29,14 @@ import androidx.fragment.app.FragmentActivity
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment
import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG import com.kunzisoft.keepass.activities.dialogs.DatabaseChangedDialogFragment.Companion.DATABASE_CHANGED_DIALOG_TAG
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.model.SnapFileDatabaseInfo import com.kunzisoft.keepass.model.SnapFileDatabaseInfo
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
@@ -48,8 +48,8 @@ import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RELOAD_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_REMOVE_UNLINKED_DATA_TASK
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE

View File

@@ -21,6 +21,8 @@ package com.kunzisoft.keepass.database.action
import android.content.Context import android.content.Context
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ActionRunnable
@@ -33,10 +35,10 @@ class ReloadDatabaseRunnable(private val context: Context,
private val mLoadDatabaseResult: ((Result) -> Unit)?) private val mLoadDatabaseResult: ((Result) -> Unit)?)
: ActionRunnable() { : ActionRunnable() {
private var tempCipherKey: Database.LoadedKey? = null private var tempCipherKey: LoadedKey? = null
override fun onStartRun() { override fun onStartRun() {
tempCipherKey = mDatabase.loadedCipherKey tempCipherKey = mDatabase.binaryCache.loadedCipherKey
// Clear before we load // Clear before we load
mDatabase.clear(UriUtil.getBinaryDir(context)) mDatabase.clear(UriUtil.getBinaryDir(context))
mDatabase.wasReloaded = true mDatabase.wasReloaded = true
@@ -46,7 +48,10 @@ class ReloadDatabaseRunnable(private val context: Context,
try { try {
mDatabase.reloadData(context.contentResolver, mDatabase.reloadData(context.contentResolver,
UriUtil.getBinaryDir(context), UriUtil.getBinaryDir(context),
tempCipherKey ?: Database.LoadedKey.generateNewCipherKey(), { memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
tempCipherKey ?: LoadedKey.generateNewCipherKey(),
progressTaskUpdater) progressTaskUpdater)
} catch (e: LoadDatabaseException) { } catch (e: LoadDatabaseException) {
setError(e) setError(e)

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.CipherFactory
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
class AesEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getAES(opmode, key, IV)
}
override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.AESRijndael
}
}

View File

@@ -17,19 +17,14 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.engine package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.encrypt.CipherFactory
import com.kunzisoft.keepass.stream.bytes16ToUuid
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class ChaCha20Engine : CipherEngine() { class ChaCha20Engine : CipherEngine() {
@@ -38,34 +33,11 @@ class ChaCha20Engine : CipherEngine() {
} }
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class) @Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray, androidOverride: Boolean): Cipher { override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
val cipher = Cipher.getInstance("Chacha7539", BouncyCastleProvider()) return CipherFactory.getChacha20(opmode, key, IV)
cipher.init(opmode, SecretKeySpec(key, "ChaCha7539"), IvParameterSpec(IV))
return cipher
} }
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm { override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.ChaCha20 return EncryptionAlgorithm.ChaCha20
} }
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
}
} }

View File

@@ -17,9 +17,7 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.engine package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
@@ -39,13 +37,8 @@ abstract class CipherEngine {
} }
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class) @Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray, androidOverride: Boolean): Cipher abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class) abstract fun getEncryptionAlgorithm(): EncryptionAlgorithm
fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return getCipher(opmode, key, IV, false)
}
abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm
} }

View File

@@ -17,11 +17,13 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.encrypt.StreamCipher
enum class CrsAlgorithm constructor(val id: UnsignedInt) { enum class CrsAlgorithm(val id: UnsignedInt) {
Null(UnsignedInt(0)), Null(UnsignedInt(0)),
ArcFourVariant(UnsignedInt(1)), ArcFourVariant(UnsignedInt(1)),
@@ -30,6 +32,15 @@ enum class CrsAlgorithm constructor(val id: UnsignedInt) {
companion object { companion object {
@Throws(Exception::class)
fun getCipher(algorithm: CrsAlgorithm?, key: ByteArray): StreamCipher {
return when (algorithm) {
Salsa20 -> HashManager.getSalsa20(key)
ChaCha20 -> HashManager.getChaCha20(key)
else -> throw Exception("Invalid random cipher")
}
}
fun fromId(num: UnsignedInt): CrsAlgorithm? { fun fromId(num: UnsignedInt): CrsAlgorithm? {
for (e in values()) { for (e in values()) {
if (e.id == num) { if (e.id == num) {

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.utils.bytes16ToUuid
import java.security.NoSuchAlgorithmException
import java.util.*
enum class EncryptionAlgorithm {
AESRijndael,
Twofish,
ChaCha20;
val cipherEngine: CipherEngine
get() {
return when (this) {
AESRijndael -> AesEngine()
Twofish -> TwofishEngine()
ChaCha20 -> ChaCha20Engine()
}
}
val uuid: UUID
get() {
return when (this) {
AESRijndael -> AES_UUID
Twofish -> TWOFISH_UUID
ChaCha20 -> CHACHA20_UUID
}
}
override fun toString(): String {
return when (this) {
AESRijndael -> "Rijndael (AES)"
Twofish -> "Twofish"
ChaCha20 -> "ChaCha20"
}
}
companion object {
/**
* Generate appropriate cipher based on KeePass 2.x UUID's
*/
@Throws(NoSuchAlgorithmException::class)
fun getFrom(uuid: UUID): EncryptionAlgorithm {
return when (uuid) {
AES_UUID -> AESRijndael
TWOFISH_UUID -> Twofish
CHACHA20_UUID -> ChaCha20
else -> throw NoSuchAlgorithmException("UUID unrecognized.")
}
}
private val AES_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
}
private val TWOFISH_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
}
private val CHACHA20_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
}
}
}

View File

@@ -17,35 +17,40 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.stream package com.kunzisoft.keepass.database.crypto
import java.io.IOException import java.io.IOException
import java.security.DigestOutputStream import java.security.InvalidKeyException
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
object HmacBlockStream { object HmacBlock {
fun getHmacKey64(key: ByteArray, blockIndex: Long): ByteArray {
fun getHmacSha256(blockKey: ByteArray): Mac {
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("No HmacAlogirthm")
} catch (e: InvalidKeyException) {
throw IOException("Invalid Hmac Key")
}
return hmac
}
fun getHmacKey64(key: ByteArray, blockIndex: ByteArray): ByteArray {
val hash: MessageDigest val hash: MessageDigest
try { try {
hash = MessageDigest.getInstance("SHA-512") hash = MessageDigest.getInstance("SHA-512")
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
hash.update(blockIndex)
val nos = NullOutputStream() hash.update(key)
val dos = DigestOutputStream(nos, hash)
val leos = LittleEndianDataOutputStream(dos)
try {
leos.writeLong(blockIndex)
leos.write(key)
leos.close()
} catch (e: IOException) {
throw RuntimeException(e)
}
//assert(hashKey.length == 64);
return hash.digest() return hash.digest()
} }
} }

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.CipherFactory
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
class TwofishEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getTwofish(opmode, key, IV)
}
override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.Twofish
}
}

View File

@@ -17,13 +17,10 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.utils package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.stream.* import java.io.*
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.nio.charset.Charset import java.nio.charset.Charset
import java.util.* import java.util.*
@@ -55,12 +52,12 @@ open class VariantDictionary {
return dict[name]?.value as UnsignedInt? return dict[name]?.value as UnsignedInt?
} }
fun setUInt64(name: String, value: Long) { fun setUInt64(name: String, value: UnsignedLong) {
putType(VdType.UInt64, name, value) putType(VdType.UInt64, name, value)
} }
fun getUInt64(name: String): Long? { fun getUInt64(name: String): UnsignedLong? {
return dict[name]?.value as Long? return dict[name]?.value as UnsignedLong?
} }
fun setBool(name: String, value: Boolean) { fun setBool(name: String, value: Boolean) {
@@ -115,22 +112,21 @@ open class VariantDictionary {
@Throws(IOException::class) @Throws(IOException::class)
fun deserialize(data: ByteArray): VariantDictionary { fun deserialize(data: ByteArray): VariantDictionary {
val inputStream = LittleEndianDataInputStream(ByteArrayInputStream(data)) val inputStream = ByteArrayInputStream(data)
return deserialize(inputStream) return deserialize(inputStream)
} }
@Throws(IOException::class) @Throws(IOException::class)
fun serialize(kdfParameters: KdfParameters): ByteArray { fun serialize(variantDictionary: VariantDictionary): ByteArray {
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
val outputStream = LittleEndianDataOutputStream(byteArrayOutputStream) serialize(variantDictionary, byteArrayOutputStream)
serialize(kdfParameters, outputStream)
return byteArrayOutputStream.toByteArray() return byteArrayOutputStream.toByteArray()
} }
@Throws(IOException::class) @Throws(IOException::class)
fun deserialize(inputStream: LittleEndianDataInputStream): VariantDictionary { fun deserialize(inputStream: InputStream): VariantDictionary {
val dictionary = VariantDictionary() val dictionary = VariantDictionary()
val version = inputStream.readUShort() val version = inputStream.readBytes2ToUShort()
if (version and VdmCritical > VdVersion and VdmCritical) { if (version and VdmCritical > VdVersion and VdmCritical) {
throw IOException("Invalid format") throw IOException("Invalid format")
} }
@@ -143,14 +139,14 @@ open class VariantDictionary {
if (bType == VdType.None) { if (bType == VdType.None) {
break break
} }
val nameLen = inputStream.readUInt().toKotlinInt() val nameLen = inputStream.readBytes4ToUInt().toKotlinInt()
val nameBuf = inputStream.readBytes(nameLen) val nameBuf = inputStream.readBytesLength(nameLen)
if (nameLen != nameBuf.size) { if (nameLen != nameBuf.size) {
throw IOException("Invalid format") throw IOException("Invalid format")
} }
val name = String(nameBuf, UTF8Charset) val name = String(nameBuf, UTF8Charset)
val valueLen = inputStream.readUInt().toKotlinInt() val valueLen = inputStream.readBytes4ToUInt().toKotlinInt()
val valueBuf = inputStream.readBytes(valueLen) val valueBuf = inputStream.readBytesLength(valueLen)
if (valueLen != valueBuf.size) { if (valueLen != valueBuf.size) {
throw IOException("Invalid format") throw IOException("Invalid format")
} }
@@ -159,7 +155,7 @@ open class VariantDictionary {
dictionary.setUInt32(name, bytes4ToUInt(valueBuf)) dictionary.setUInt32(name, bytes4ToUInt(valueBuf))
} }
VdType.UInt64 -> if (valueLen == 8) { VdType.UInt64 -> if (valueLen == 8) {
dictionary.setUInt64(name, bytes64ToLong(valueBuf)) dictionary.setUInt64(name, bytes64ToULong(valueBuf))
} }
VdType.Bool -> if (valueLen == 1) { VdType.Bool -> if (valueLen == 1) {
dictionary.setBool(name, valueBuf[0] != 0.toByte()) dictionary.setBool(name, valueBuf[0] != 0.toByte())
@@ -181,48 +177,47 @@ open class VariantDictionary {
@Throws(IOException::class) @Throws(IOException::class)
fun serialize(variantDictionary: VariantDictionary, fun serialize(variantDictionary: VariantDictionary,
outputStream: LittleEndianDataOutputStream?) { outputStream: OutputStream?) {
if (outputStream == null) { if (outputStream == null) {
return return
} }
outputStream.writeUShort(VdVersion) outputStream.write2BytesUShort(VdVersion)
for ((name, vd) in variantDictionary.dict) { for ((name, vd) in variantDictionary.dict) {
val nameBuf = name.toByteArray(UTF8Charset) val nameBuf = name.toByteArray(UTF8Charset)
outputStream.write(vd.type.toInt()) outputStream.writeByte(vd.type)
outputStream.writeInt(nameBuf.size) outputStream.write4BytesUInt(UnsignedInt(nameBuf.size))
outputStream.write(nameBuf) outputStream.write(nameBuf)
var buf: ByteArray var buf: ByteArray
when (vd.type) { when (vd.type) {
VdType.UInt32 -> { VdType.UInt32 -> {
outputStream.writeInt(4) outputStream.write4BytesUInt(UnsignedInt(4))
outputStream.writeUInt((vd.value as UnsignedInt)) outputStream.write4BytesUInt(vd.value as UnsignedInt)
} }
VdType.UInt64 -> { VdType.UInt64 -> {
outputStream.writeInt(8) outputStream.write4BytesUInt(UnsignedInt(8))
outputStream.writeLong(vd.value as Long) outputStream.write8BytesLong(vd.value as UnsignedLong)
} }
VdType.Bool -> { VdType.Bool -> {
outputStream.writeInt(1) outputStream.write4BytesUInt(UnsignedInt(1))
val bool = if (vd.value as Boolean) 1.toByte() else 0.toByte() outputStream.writeBooleanByte(vd.value as Boolean)
outputStream.write(bool.toInt())
} }
VdType.Int32 -> { VdType.Int32 -> {
outputStream.writeInt(4) outputStream.write4BytesUInt(UnsignedInt(4))
outputStream.writeInt(vd.value as Int) outputStream.write4BytesUInt(UnsignedInt(vd.value as Int))
} }
VdType.Int64 -> { VdType.Int64 -> {
outputStream.writeInt(8) outputStream.write4BytesUInt(UnsignedInt(8))
outputStream.writeLong(vd.value as Long) outputStream.write8BytesLong(vd.value as Long)
} }
VdType.String -> { VdType.String -> {
val value = vd.value as String val value = vd.value as String
buf = value.toByteArray(UTF8Charset) buf = value.toByteArray(UTF8Charset)
outputStream.writeInt(buf.size) outputStream.write4BytesUInt(UnsignedInt(buf.size))
outputStream.write(buf) outputStream.write(buf)
} }
VdType.ByteArray -> { VdType.ByteArray -> {
buf = vd.value as ByteArray buf = vd.value as ByteArray
outputStream.writeInt(buf.size) outputStream.write4BytesUInt(UnsignedInt(buf.size))
outputStream.write(buf) outputStream.write(buf)
} }
else -> { else -> {

View File

@@ -17,13 +17,12 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.database.crypto.kdf
import android.content.res.Resources import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.crypto.CryptoUtil import com.kunzisoft.encrypt.aes.AESTransformer
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory import com.kunzisoft.keepass.utils.bytes16ToUuid
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.io.IOException import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
@@ -38,32 +37,28 @@ class AesKdf : KdfEngine() {
get() { get() {
return KdfParameters(uuid!!).apply { return KdfParameters(uuid!!).apply {
setParamUUID() setParamUUID()
setUInt64(PARAM_ROUNDS, defaultKeyRounds) setUInt64(PARAM_ROUNDS, UnsignedLong(defaultKeyRounds))
} }
} }
override val defaultKeyRounds: Long = 500000L override val defaultKeyRounds = 500000L
override fun getName(resources: Resources): String {
return resources.getString(R.string.kdf_AES)
}
@Throws(IOException::class) @Throws(IOException::class)
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray { override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
var seed = kdfParameters.getByteArray(PARAM_SEED) var seed = kdfParameters.getByteArray(PARAM_SEED)
if (seed != null && seed.size != 32) { if (seed != null && seed.size != 32) {
seed = CryptoUtil.hashSha256(seed) seed = HashManager.hashSha256(seed)
} }
var currentMasterKey = masterKey var currentMasterKey = masterKey
if (currentMasterKey.size != 32) { if (currentMasterKey.size != 32) {
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey) currentMasterKey = HashManager.hashSha256(currentMasterKey)
} }
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS) val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)?.toKotlinLong()
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0) return AESTransformer.transformKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
} }
override fun randomize(kdfParameters: KdfParameters) { override fun randomize(kdfParameters: KdfParameters) {
@@ -76,11 +71,15 @@ class AesKdf : KdfEngine() {
} }
override fun getKeyRounds(kdfParameters: KdfParameters): Long { override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ROUNDS) ?: defaultKeyRounds return kdfParameters.getUInt64(PARAM_ROUNDS)?.toKotlinLong() ?: defaultKeyRounds
} }
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) { override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ROUNDS, keyRounds) kdfParameters.setUInt64(PARAM_ROUNDS, UnsignedLong(keyRounds))
}
override fun toString(): String {
return "AES"
} }
companion object { companion object {

View File

@@ -17,13 +17,13 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.database.crypto.kdf
import android.content.res.Resources
import androidx.annotation.StringRes
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.encrypt.argon2.Argon2Transformer
import com.kunzisoft.encrypt.argon2.Argon2Type
import com.kunzisoft.keepass.utils.bytes16ToUuid
import java.io.IOException import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
@@ -48,40 +48,30 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
} }
override val defaultKeyRounds: Long override val defaultKeyRounds: Long
get() = DEFAULT_ITERATIONS get() = DEFAULT_ITERATIONS.toKotlinLong()
override fun getName(resources: Resources): String {
return resources.getString(type.nameId)
}
@Throws(IOException::class) @Throws(IOException::class)
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray { override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
val salt = kdfParameters.getByteArray(PARAM_SALT) val salt = kdfParameters.getByteArray(PARAM_SALT) ?: ByteArray(0)
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.let { val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.toKotlinLong() ?: DEFAULT_PARALLELISM.toKotlinLong()
UnsignedInt(it) val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.toKotlinLong()?.div(MEMORY_BLOCK_SIZE) ?: DEFAULT_MEMORY.toKotlinLong()
} val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.toKotlinLong() ?: DEFAULT_ITERATIONS.toKotlinLong()
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.div(MEMORY_BLOCK_SIZE)?.let { val version = kdfParameters.getUInt32(PARAM_VERSION)?.toKotlinInt() ?: MAX_VERSION.toKotlinInt()
UnsignedInt.fromKotlinLong(it)
}
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.let {
UnsignedInt.fromKotlinLong(it)
}
val version = kdfParameters.getUInt32(PARAM_VERSION)?.let {
UnsignedInt(it)
}
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
return Argon2Native.transformKey( // Not used
type, // val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
// val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
val argonType = if (type == Type.ARGON2_ID) Argon2Type.ARGON2_ID else Argon2Type.ARGON2_D
return Argon2Transformer.transformKey(
argonType,
masterKey, masterKey,
salt, salt,
parallelism, parallelism,
memory, memory,
iterations, iterations,
secretKey,
assocData,
version) version)
} }
@@ -95,32 +85,32 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
} }
override fun getKeyRounds(kdfParameters: KdfParameters): Long { override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ITERATIONS) ?: defaultKeyRounds return kdfParameters.getUInt64(PARAM_ITERATIONS)?.toKotlinLong() ?: defaultKeyRounds
} }
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) { override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ITERATIONS, keyRounds) kdfParameters.setUInt64(PARAM_ITERATIONS, UnsignedLong(keyRounds))
} }
override val minKeyRounds: Long override val minKeyRounds: Long
get() = MIN_ITERATIONS get() = MIN_ITERATIONS.toKotlinLong()
override val maxKeyRounds: Long override val maxKeyRounds: Long
get() = MAX_ITERATIONS get() = MAX_ITERATIONS.toKotlinLong()
override fun getMemoryUsage(kdfParameters: KdfParameters): Long { override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_MEMORY) ?: defaultMemoryUsage return kdfParameters.getUInt64(PARAM_MEMORY)?.toKotlinLong() ?: defaultMemoryUsage
} }
override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) { override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
kdfParameters.setUInt64(PARAM_MEMORY, memory) kdfParameters.setUInt64(PARAM_MEMORY, UnsignedLong(memory))
} }
override val defaultMemoryUsage: Long override val defaultMemoryUsage: Long
get() = DEFAULT_MEMORY get() = DEFAULT_MEMORY.toKotlinLong()
override val minMemoryUsage: Long override val minMemoryUsage: Long
get() = MIN_MEMORY get() = MIN_MEMORY.toKotlinLong()
override val maxMemoryUsage: Long override val maxMemoryUsage: Long
get() = MAX_MEMORY get() = MAX_MEMORY
@@ -135,16 +125,20 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromKotlinLong(parallelism)) kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromKotlinLong(parallelism))
} }
override fun toString(): String {
return "$type"
}
override val defaultParallelism: Long override val defaultParallelism: Long
get() = DEFAULT_PARALLELISM.toKotlinLong() get() = DEFAULT_PARALLELISM.toKotlinLong()
override val minParallelism: Long override val minParallelism: Long
get() = MIN_PARALLELISM get() = MIN_PARALLELISM.toKotlinLong()
override val maxParallelism: Long override val maxParallelism: Long
get() = MAX_PARALLELISM get() = MAX_PARALLELISM.toKotlinLong()
enum class Type(val CIPHER_UUID: UUID, @StringRes val nameId: Int) { enum class Type(val CIPHER_UUID: UUID, private val typeName: String) {
ARGON2_D(bytes16ToUuid( ARGON2_D(bytes16ToUuid(
byteArrayOf(0xEF.toByte(), byteArrayOf(0xEF.toByte(),
0x63.toByte(), 0x63.toByte(),
@@ -161,7 +155,7 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
0x03.toByte(), 0x03.toByte(),
0xE3.toByte(), 0xE3.toByte(),
0x0A.toByte(), 0x0A.toByte(),
0x0C.toByte())), R.string.kdf_Argon2d), 0x0C.toByte())), "Argon2d"),
ARGON2_ID(bytes16ToUuid( ARGON2_ID(bytes16ToUuid(
byteArrayOf(0x9E.toByte(), byteArrayOf(0x9E.toByte(),
0x29.toByte(), 0x29.toByte(),
@@ -178,7 +172,11 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
0xC6.toByte(), 0xC6.toByte(),
0xF0.toByte(), 0xF0.toByte(),
0xA1.toByte(), 0xA1.toByte(),
0xE6.toByte())), R.string.kdf_Argon2id); 0xE6.toByte())), "Argon2id");
override fun toString(): String {
return typeName
}
} }
companion object { companion object {
@@ -194,21 +192,17 @@ class Argon2Kdf(private val type: Type) : KdfEngine() {
private val MIN_VERSION = UnsignedInt(0x10) private val MIN_VERSION = UnsignedInt(0x10)
private val MAX_VERSION = UnsignedInt(0x13) private val MAX_VERSION = UnsignedInt(0x13)
private const val MIN_SALT = 8 private val DEFAULT_ITERATIONS = UnsignedLong(2L)
private val MAX_SALT = UnsignedInt.MAX_VALUE.toKotlinLong() private val MIN_ITERATIONS = UnsignedLong(1L)
private val MAX_ITERATIONS = UnsignedLong(4294967295L)
private const val MIN_ITERATIONS: Long = 1L private val DEFAULT_MEMORY = UnsignedLong((1024L * 1024L))
private const val MAX_ITERATIONS = 4294967295L private val MIN_MEMORY = UnsignedLong(1024L * 8L)
private const val MIN_MEMORY = (1024 * 8).toLong()
private val MAX_MEMORY = UnsignedInt.MAX_VALUE.toKotlinLong() private val MAX_MEMORY = UnsignedInt.MAX_VALUE.toKotlinLong()
private const val MEMORY_BLOCK_SIZE: Long = 1024L private const val MEMORY_BLOCK_SIZE: Long = 1024L
private const val MIN_PARALLELISM: Long = 1L
private const val MAX_PARALLELISM: Long = ((1 shl 24) - 1).toLong()
private const val DEFAULT_ITERATIONS: Long = 2L
private const val DEFAULT_MEMORY = (1024 * 1024).toLong()
private val DEFAULT_PARALLELISM = UnsignedInt(2) private val DEFAULT_PARALLELISM = UnsignedInt(2)
private val MIN_PARALLELISM = UnsignedInt.fromKotlinLong(1L)
private val MAX_PARALLELISM = UnsignedInt.fromKotlinLong(((1 shl 24) - 1))
} }
} }

View File

@@ -17,17 +17,15 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.database.crypto.kdf
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException import java.io.IOException
import java.io.Serializable import java.io.Serializable
import java.util.UUID import java.util.*
// TODO Parcelable // TODO Parcelable
abstract class KdfEngine : ObjectNameResource, Serializable { abstract class KdfEngine : Serializable {
var uuid: UUID? = null var uuid: UUID? = null

View File

@@ -17,7 +17,7 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.database.crypto.kdf
object KdfFactory { object KdfFactory {
var aesKdf = AesKdf() var aesKdf = AesKdf()

View File

@@ -17,11 +17,11 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.database.crypto.kdf
import com.kunzisoft.keepass.stream.bytes16ToUuid import com.kunzisoft.keepass.utils.bytes16ToUuid
import com.kunzisoft.keepass.stream.uuidTo16Bytes import com.kunzisoft.keepass.utils.uuidTo16Bytes
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.database.crypto.VariantDictionary
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*

View File

@@ -21,8 +21,8 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryByte import com.kunzisoft.keepass.database.element.binary.BinaryByte
import com.kunzisoft.keepass.database.element.database.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
data class Attachment(var name: String, data class Attachment(var name: String,

View File

@@ -23,19 +23,27 @@ import android.content.ContentResolver
import android.content.res.Resources import android.content.res.Resources
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.utils.readBytes4ToUInt
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.database.* import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconsManager import com.kunzisoft.keepass.database.element.icon.IconsManager
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
@@ -44,15 +52,11 @@ import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.search.SearchParameters import com.kunzisoft.keepass.database.search.SearchParameters
import com.kunzisoft.keepass.icons.IconDrawableFactory import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.model.MainCredential import com.kunzisoft.keepass.model.MainCredential
import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import java.io.* import java.io.*
import java.security.Key
import java.security.SecureRandom
import java.util.* import java.util.*
import javax.crypto.KeyGenerator
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -70,7 +74,7 @@ class Database {
var isReadOnly = false var isReadOnly = false
val iconDrawableFactory = IconDrawableFactory( val iconDrawableFactory = IconDrawableFactory(
{ loadedCipherKey }, { binaryCache },
{ iconId -> iconsManager.getBinaryForCustomIcon(iconId) } { iconId -> iconsManager.getBinaryForCustomIcon(iconId) }
) )
@@ -92,18 +96,22 @@ class Database {
* Cipher key regenerated when the database is loaded and closed * Cipher key regenerated when the database is loaded and closed
* Can be used to temporarily store database elements * Can be used to temporarily store database elements
*/ */
var loadedCipherKey: LoadedKey? var binaryCache: BinaryCache
private set(value) { private set(value) {
mDatabaseKDB?.loadedCipherKey = value mDatabaseKDB?.binaryCache = value
mDatabaseKDBX?.loadedCipherKey = value mDatabaseKDBX?.binaryCache = value
} }
get() { get() {
return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache()
} }
fun setCacheDirectory(cacheDirectory: File) {
binaryCache.cacheDirectory = cacheDirectory
}
private val iconsManager: IconsManager private val iconsManager: IconsManager
get() { get() {
return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager() return mDatabaseKDB?.iconsManager ?: mDatabaseKDBX?.iconsManager ?: IconsManager(binaryCache)
} }
fun doForEachStandardIcons(action: (IconImageStandard) -> Unit) { fun doForEachStandardIcons(action: (IconImageStandard) -> Unit) {
@@ -125,9 +133,8 @@ class Database {
return iconsManager.getIcon(iconId) return iconsManager.getIcon(iconId)
} }
fun buildNewCustomIcon(cacheDirectory: File, fun buildNewCustomIcon(result: (IconImageCustom?, BinaryData?) -> Unit) {
result: (IconImageCustom?, BinaryData?) -> Unit) { mDatabaseKDBX?.buildNewCustomIcon(null, result)
mDatabaseKDBX?.buildNewCustomIcon(cacheDirectory, null, result)
} }
fun isCustomIconBinaryDuplicate(binaryData: BinaryData): Boolean { fun isCustomIconBinaryDuplicate(binaryData: BinaryData): Boolean {
@@ -136,7 +143,7 @@ class Database {
fun removeCustomIcon(customIcon: IconImageCustom) { fun removeCustomIcon(customIcon: IconImageCustom) {
iconDrawableFactory.clearFromCache(customIcon) iconDrawableFactory.clearFromCache(customIcon)
iconsManager.removeCustomIcon(customIcon.uuid) iconsManager.removeCustomIcon(binaryCache, customIcon.uuid)
} }
val allowName: Boolean val allowName: Boolean
@@ -219,7 +226,7 @@ class Database {
// Default compression not necessary if stored in header // Default compression not necessary if stored in header
mDatabaseKDBX?.let { mDatabaseKDBX?.let {
return it.compressionAlgorithm == CompressionAlgorithm.GZip return it.compressionAlgorithm == CompressionAlgorithm.GZip
&& it.kdbxVersion.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong() && it.kdbxVersion.isBefore(FILE_VERSION_32_4)
} }
return false return false
} }
@@ -232,12 +239,9 @@ class Database {
val allowNoMasterKey: Boolean val allowNoMasterKey: Boolean
get() = mDatabaseKDBX != null get() = mDatabaseKDBX != null
val allowEncryptionAlgorithmModification: Boolean fun getEncryptionAlgorithmName(): String {
get() = availableEncryptionAlgorithms.size > 1 return mDatabaseKDB?.encryptionAlgorithm?.toString()
?: mDatabaseKDBX?.encryptionAlgorithm?.toString()
fun getEncryptionAlgorithmName(resources: Resources): String {
return mDatabaseKDB?.encryptionAlgorithm?.getName(resources)
?: mDatabaseKDBX?.encryptionAlgorithm?.getName(resources)
?: "" ?: ""
} }
@@ -250,7 +254,7 @@ class Database {
algorithm?.let { algorithm?.let {
mDatabaseKDBX?.encryptionAlgorithm = algorithm mDatabaseKDBX?.encryptionAlgorithm = algorithm
mDatabaseKDBX?.setDataEngine(algorithm.cipherEngine) mDatabaseKDBX?.setDataEngine(algorithm.cipherEngine)
mDatabaseKDBX?.dataCipher = algorithm.dataCipher mDatabaseKDBX?.cipherUuid = algorithm.uuid
} }
} }
@@ -272,8 +276,8 @@ class Database {
} }
} }
fun getKeyDerivationName(resources: Resources): String { fun getKeyDerivationName(): String {
return kdfEngine?.getName(resources) ?: "" return kdfEngine?.toString() ?: ""
} }
var numberKeyEncryptionRounds: Long var numberKeyEncryptionRounds: Long
@@ -381,25 +385,12 @@ class Database {
fun createData(databaseUri: Uri, databaseName: String, rootName: String) { fun createData(databaseUri: Uri, databaseName: String, rootName: String) {
val newDatabase = DatabaseKDBX(databaseName, rootName) val newDatabase = DatabaseKDBX(databaseName, rootName)
newDatabase.loadedCipherKey = LoadedKey.generateNewCipherKey()
setDatabaseKDBX(newDatabase) setDatabaseKDBX(newDatabase)
this.fileUri = databaseUri this.fileUri = databaseUri
// Set Database state // Set Database state
this.loaded = true this.loaded = true
} }
class LoadedKey(val key: Key, val iv: ByteArray): Serializable {
companion object {
const val BINARY_CIPHER = "Blowfish/CBC/PKCS5Padding"
fun generateNewCipherKey(): LoadedKey {
val iv = ByteArray(8)
SecureRandom().nextBytes(iv)
return LoadedKey(KeyGenerator.getInstance("Blowfish").generateKey(), iv)
}
}
}
@Throws(LoadDatabaseException::class) @Throws(LoadDatabaseException::class)
private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri, private fun readDatabaseStream(contentResolver: ContentResolver, uri: Uri,
openDatabaseKDB: (InputStream) -> DatabaseKDB, openDatabaseKDB: (InputStream) -> DatabaseKDB,
@@ -453,6 +444,7 @@ class Database {
readOnly: Boolean, readOnly: Boolean,
contentResolver: ContentResolver, contentResolver: ContentResolver,
cacheDirectory: File, cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
tempCipherKey: LoadedKey, tempCipherKey: LoadedKey,
fixDuplicateUUID: Boolean, fixDuplicateUUID: Boolean,
progressTaskUpdater: ProgressTaskUpdater?) { progressTaskUpdater: ProgressTaskUpdater?) {
@@ -474,7 +466,7 @@ class Database {
// Read database stream for the first time // Read database stream for the first time
readDatabaseStream(contentResolver, uri, readDatabaseStream(contentResolver, uri,
{ databaseInputStream -> { databaseInputStream ->
DatabaseInputKDB(cacheDirectory) DatabaseInputKDB(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream, .openDatabase(databaseInputStream,
mainCredential.masterPassword, mainCredential.masterPassword,
keyFileInputStream, keyFileInputStream,
@@ -483,7 +475,7 @@ class Database {
fixDuplicateUUID) fixDuplicateUUID)
}, },
{ databaseInputStream -> { databaseInputStream ->
DatabaseInputKDBX(cacheDirectory) DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream, .openDatabase(databaseInputStream,
mainCredential.masterPassword, mainCredential.masterPassword,
keyFileInputStream, keyFileInputStream,
@@ -507,6 +499,7 @@ class Database {
@Throws(LoadDatabaseException::class) @Throws(LoadDatabaseException::class)
fun reloadData(contentResolver: ContentResolver, fun reloadData(contentResolver: ContentResolver,
cacheDirectory: File, cacheDirectory: File,
isRAMSufficient: (memoryWanted: Long) -> Boolean,
tempCipherKey: LoadedKey, tempCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?) { progressTaskUpdater: ProgressTaskUpdater?) {
@@ -515,14 +508,14 @@ class Database {
fileUri?.let { oldDatabaseUri -> fileUri?.let { oldDatabaseUri ->
readDatabaseStream(contentResolver, oldDatabaseUri, readDatabaseStream(contentResolver, oldDatabaseUri,
{ databaseInputStream -> { databaseInputStream ->
DatabaseInputKDB(cacheDirectory) DatabaseInputKDB(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream, .openDatabase(databaseInputStream,
masterKey, masterKey,
tempCipherKey, tempCipherKey,
progressTaskUpdater) progressTaskUpdater)
}, },
{ databaseInputStream -> { databaseInputStream ->
DatabaseInputKDBX(cacheDirectory) DatabaseInputKDBX(cacheDirectory, isRAMSufficient)
.openDatabase(databaseInputStream, .openDatabase(databaseInputStream,
masterKey, masterKey,
tempCipherKey, tempCipherKey,
@@ -576,8 +569,7 @@ class Database {
val attachmentPool: AttachmentPool val attachmentPool: AttachmentPool
get() { get() {
// Binary pool is functionally only in KDBX return mDatabaseKDB?.attachmentPool ?: mDatabaseKDBX?.attachmentPool ?: AttachmentPool(binaryCache)
return mDatabaseKDBX?.binaryPool ?: AttachmentPool()
} }
val allowMultipleAttachments: Boolean val allowMultipleAttachments: Boolean
@@ -589,11 +581,10 @@ class Database {
return false return false
} }
fun buildNewBinaryAttachment(cacheDirectory: File, fun buildNewBinaryAttachment(compressed: Boolean = false,
compressed: Boolean = false,
protected: Boolean = false): BinaryData? { protected: Boolean = false): BinaryData? {
return mDatabaseKDB?.buildNewAttachment(cacheDirectory) return mDatabaseKDB?.buildNewAttachment()
?: mDatabaseKDBX?.buildNewAttachment(cacheDirectory, compressed, protected) ?: mDatabaseKDBX?.buildNewAttachment( false, compressed, protected)
} }
fun removeAttachmentIfNotUsed(attachment: Attachment) { fun removeAttachmentIfNotUsed(attachment: Attachment) {
@@ -668,6 +659,7 @@ class Database {
} }
fun clear(filesDirectory: File? = null) { fun clear(filesDirectory: File? = null) {
binaryCache.clear()
iconsManager.clearCache() iconsManager.clearCache()
iconDrawableFactory.clearCache() iconDrawableFactory.clearCache()
// Delete the cache of the database if present // Delete the cache of the database if present

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.element
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.AttachmentPool import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
@@ -311,7 +311,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun getAttachments(attachmentPool: AttachmentPool, inHistory: Boolean = false): List<Attachment> { fun getAttachments(attachmentPool: AttachmentPool, inHistory: Boolean = false): List<Attachment> {
val attachments = ArrayList<Attachment>() val attachments = ArrayList<Attachment>()
entryKDB?.getAttachment()?.let { entryKDB?.getAttachment(attachmentPool)?.let {
attachments.add(it) attachments.add(it)
} }
entryKDBX?.getAttachments(attachmentPool, inHistory)?.let { entryKDBX?.getAttachments(attachmentPool, inHistory)?.let {
@@ -336,7 +336,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
} }
private fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) { private fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
entryKDB?.putAttachment(attachment) entryKDB?.putAttachment(attachment, attachmentPool)
entryKDBX?.putAttachment(attachment, attachmentPool) entryKDBX?.putAttachment(attachment, attachmentPool)
} }

View File

@@ -17,9 +17,9 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.binary
class AttachmentPool : BinaryPool<Int>() { class AttachmentPool(binaryCache: BinaryCache) : BinaryPool<Int>(binaryCache) {
/** /**
* Utility method to find an unused key in the pool * Utility method to find an unused key in the pool

View File

@@ -17,51 +17,62 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.binary
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Database import android.util.Base64
import com.kunzisoft.keepass.stream.readAllBytes import android.util.Base64InputStream
import android.util.Base64OutputStream
import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.binary.BinaryCache.Companion.UNKNOWN
import java.io.* import java.io.*
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
class BinaryByte : BinaryData { class BinaryByte : BinaryData {
private var mDataByte: ByteArray = ByteArray(0) private var mDataByteId: String
/** private fun getByteArray(binaryCache: BinaryCache): ByteArray {
* Empty protected binary val keyData = binaryCache.getByteArray(mDataByteId)
*/ mDataByteId = keyData.key
constructor() : super() return keyData.data
}
constructor(byteArray: ByteArray, constructor() : super() {
mDataByteId = UNKNOWN
}
constructor(id: String,
compressed: Boolean = false, compressed: Boolean = false,
protected: Boolean = false) : super(compressed, protected) { protected: Boolean = false) : super(compressed, protected) {
this.mDataByte = byteArray mDataByteId = id
} }
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
val byteArray = ByteArray(parcel.readInt()) mDataByteId = parcel.readString() ?: UNKNOWN
parcel.readByteArray(byteArray) }
mDataByte = byteArray
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(mDataByteId)
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream { override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
return ByteArrayInputStream(mDataByte) return Base64InputStream(ByteArrayInputStream(getByteArray(binaryCache)), Base64.NO_WRAP)
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
return ByteOutputStream() return BinaryCountingOutputStream(Base64OutputStream(ByteOutputStream(binaryCache), Base64.NO_WRAP))
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun compress(cipherKey: Database.LoadedKey) { override fun compress(binaryCache: BinaryCache) {
if (!isCompressed) { if (!isCompressed) {
GZIPOutputStream(getOutputDataStream(cipherKey)).use { outputStream -> GZIPOutputStream(getOutputDataStream(binaryCache)).use { outputStream ->
getInputDataStream(cipherKey).use { inputStream -> getInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
} }
@@ -72,10 +83,10 @@ class BinaryByte : BinaryData {
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun decompress(cipherKey: Database.LoadedKey) { override fun decompress(binaryCache: BinaryCache) {
if (isCompressed) { if (isCompressed) {
getUnGzipInputDataStream(cipherKey).use { inputStream -> getUnGzipInputDataStream(binaryCache).use { inputStream ->
getOutputDataStream(cipherKey).use { outputStream -> getOutputDataStream(binaryCache).use { outputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
} }
@@ -86,36 +97,8 @@ class BinaryByte : BinaryData {
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun clear() { override fun clear(binaryCache: BinaryCache) {
mDataByte = ByteArray(0) binaryCache.removeByteArray(mDataByteId)
}
override fun dataExists(): Boolean {
return mDataByte.isNotEmpty()
}
override fun getSize(): Long {
return mDataByte.size.toLong()
}
/**
* Hash of the raw encrypted file in temp folder, only to compare binary data
*/
override fun binaryHash(): Int {
return if (dataExists())
mDataByte.contentHashCode()
else
0
}
override fun toString(): String {
return mDataByte.toString()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeInt(mDataByte.size)
dest.writeByteArray(mDataByte)
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@@ -123,32 +106,29 @@ class BinaryByte : BinaryData {
if (other !is BinaryByte) return false if (other !is BinaryByte) return false
if (!super.equals(other)) return false if (!super.equals(other)) return false
if (!mDataByte.contentEquals(other.mDataByte)) return false if (mDataByteId != other.mDataByteId) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = super.hashCode() var result = super.hashCode()
result = 31 * result + mDataByte.contentHashCode() result = 31 * result + mDataByteId.hashCode()
return result return result
} }
/** /**
* Custom OutputStream to calculate the size and hash of binary file * Custom OutputStream to calculate the size and hash of binary file
*/ */
private inner class ByteOutputStream : ByteArrayOutputStream() { private inner class ByteOutputStream(private val binaryCache: BinaryCache) : ByteArrayOutputStream() {
override fun close() { override fun close() {
mDataByte = this.toByteArray() binaryCache.setByteArray(mDataByteId, this.toByteArray())
super.close() super.close()
} }
} }
companion object { companion object {
private val TAG = BinaryByte::class.java.name private val TAG = BinaryByte::class.java.name
// Max Parcelable / 2
const val MAX_BINARY_BYTES = 524288
@JvmField @JvmField
val CREATOR: Parcelable.Creator<BinaryByte> = object : Parcelable.Creator<BinaryByte> { val CREATOR: Parcelable.Creator<BinaryByte> = object : Parcelable.Creator<BinaryByte> {

View File

@@ -0,0 +1,81 @@
package com.kunzisoft.keepass.database.element.binary
import java.io.File
import java.util.*
class BinaryCache {
/**
* Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements
*/
var loadedCipherKey: LoadedKey = LoadedKey.generateNewCipherKey()
lateinit var cacheDirectory: File
private val voidBinary = KeyByteArray(UNKNOWN, ByteArray(0))
fun getBinaryData(binaryId: String,
smallSize: Boolean = false,
compression: Boolean = false,
protection: Boolean = false): BinaryData {
return if (smallSize) {
BinaryByte(binaryId, compression, protection)
} else {
val fileInCache = File(cacheDirectory, binaryId)
return BinaryFile(fileInCache, compression, protection)
}
}
// Similar to file storage but much faster
private val byteArrayList = HashMap<String, ByteArray>()
fun getByteArray(key: String): KeyByteArray {
if (key == UNKNOWN) {
return voidBinary
}
if (!byteArrayList.containsKey(key)) {
val newItem = KeyByteArray(key, ByteArray(0))
byteArrayList[newItem.key] = newItem.data
return newItem
}
return KeyByteArray(key, byteArrayList[key]!!)
}
fun setByteArray(key: String, data: ByteArray): KeyByteArray {
if (key == UNKNOWN) {
return voidBinary
}
byteArrayList[key] = data
return KeyByteArray(key, data)
}
fun removeByteArray(key: String?) {
key?.let {
byteArrayList.remove(it)
}
}
fun clear() {
byteArrayList.clear()
}
companion object {
const val UNKNOWN = "UNKNOWN"
}
data class KeyByteArray(val key: String, val data: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is KeyByteArray) return false
if (key != other.key) return false
return true
}
override fun hashCode(): Int {
return key.hashCode()
}
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.binary
import android.app.ActivityManager
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import org.apache.commons.io.output.CountingOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
abstract class BinaryData : Parcelable {
var isCompressed: Boolean = false
protected set
var isProtected: Boolean = false
protected set
var isCorrupted: Boolean = false
private var mLength: Long = 0
private var mBinaryHash = 0
protected constructor(compressed: Boolean = false, protected: Boolean = false) {
this.isCompressed = compressed
this.isProtected = protected
this.mLength = 0
this.mBinaryHash = 0
}
protected constructor(parcel: Parcel) {
isCompressed = parcel.readByte().toInt() != 0
isProtected = parcel.readByte().toInt() != 0
isCorrupted = parcel.readByte().toInt() != 0
mLength = parcel.readLong()
mBinaryHash = parcel.readInt()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
dest.writeLong(mLength)
dest.writeInt(mBinaryHash)
}
@Throws(IOException::class)
abstract fun getInputDataStream(binaryCache: BinaryCache): InputStream
@Throws(IOException::class)
abstract fun getOutputDataStream(binaryCache: BinaryCache): OutputStream
@Throws(IOException::class)
fun getUnGzipInputDataStream(binaryCache: BinaryCache): InputStream {
return if (isCompressed) {
GZIPInputStream(getInputDataStream(binaryCache))
} else {
getInputDataStream(binaryCache)
}
}
@Throws(IOException::class)
fun getGzipOutputDataStream(binaryCache: BinaryCache): OutputStream {
return if (isCompressed) {
GZIPOutputStream(getOutputDataStream(binaryCache))
} else {
getOutputDataStream(binaryCache)
}
}
@Throws(IOException::class)
abstract fun compress(binaryCache: BinaryCache)
@Throws(IOException::class)
abstract fun decompress(binaryCache: BinaryCache)
@Throws(IOException::class)
fun dataExists(): Boolean {
return mLength > 0
}
@Throws(IOException::class)
fun getSize(): Long {
return mLength
}
@Throws(IOException::class)
fun binaryHash(): Int {
return mBinaryHash
}
@Throws(IOException::class)
abstract fun clear(binaryCache: BinaryCache)
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BinaryData) return false
if (isCompressed != other.isCompressed) return false
if (isProtected != other.isProtected) return false
if (isCorrupted != other.isCorrupted) return false
return true
}
override fun hashCode(): Int {
var result = isCompressed.hashCode()
result = 31 * result + isProtected.hashCode()
result = 31 * result + isCorrupted.hashCode()
result = 31 * result + mLength.hashCode()
result = 31 * result + mBinaryHash
return result
}
/**
* Custom OutputStream to calculate the size and hash of binary file
*/
protected inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) {
private val mMessageDigest: MessageDigest
init {
mLength = 0
mMessageDigest = MessageDigest.getInstance("MD5")
mBinaryHash = 0
}
override fun beforeWrite(n: Int) {
super.beforeWrite(n)
mLength = byteCount
}
override fun write(idx: Int) {
super.write(idx)
mMessageDigest.update(idx.toByte())
}
override fun write(bts: ByteArray) {
super.write(bts)
mMessageDigest.update(bts)
}
override fun write(bts: ByteArray, st: Int, end: Int) {
super.write(bts, st, end)
mMessageDigest.update(bts, st, end)
}
override fun close() {
super.close()
mLength = byteCount
val bytes = mMessageDigest.digest()
mBinaryHash = ByteBuffer.wrap(bytes).int
}
}
companion object {
private val TAG = BinaryData::class.java.name
fun canMemoryBeAllocatedInRAM(context: Context, memoryWanted: Long): Boolean {
val memoryInfo = ActivityManager.MemoryInfo()
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo(memoryInfo)
val availableMemory = memoryInfo.availMem
return availableMemory > memoryWanted * 3
}
}
}

View File

@@ -17,19 +17,15 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.binary
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import android.util.Base64 import android.util.Base64
import android.util.Base64InputStream import android.util.Base64InputStream
import android.util.Base64OutputStream import android.util.Base64OutputStream
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.stream.readAllBytes
import org.apache.commons.io.output.CountingOutputStream
import java.io.* import java.io.*
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherInputStream import javax.crypto.CipherInputStream
@@ -39,44 +35,43 @@ import javax.crypto.spec.IvParameterSpec
class BinaryFile : BinaryData { class BinaryFile : BinaryData {
private var mDataFile: File? = null private var mDataFile: File? = null
private var mLength: Long = 0
private var mBinaryHash = 0
// Cipher to encrypt temp file // Cipher to encrypt temp file
@Transient @Transient
private var cipherEncryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER) private var cipherEncryption: Cipher = Cipher.getInstance(LoadedKey.BINARY_CIPHER)
@Transient @Transient
private var cipherDecryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER) private var cipherDecryption: Cipher = Cipher.getInstance(LoadedKey.BINARY_CIPHER)
constructor() : super()
constructor(dataFile: File, constructor(dataFile: File,
compressed: Boolean = false, compressed: Boolean = false,
protected: Boolean = false) : super(compressed, protected) { protected: Boolean = false) : super(compressed, protected) {
this.mDataFile = dataFile this.mDataFile = dataFile
this.mLength = 0
this.mBinaryHash = 0
} }
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
parcel.readString()?.let { parcel.readString()?.let {
mDataFile = File(it) mDataFile = File(it)
} }
mLength = parcel.readLong() }
mBinaryHash = parcel.readInt()
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(mDataFile?.absolutePath)
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream { override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
return buildInputStream(mDataFile, cipherKey) return buildInputStream(mDataFile, binaryCache)
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
return buildOutputStream(mDataFile, cipherKey) return buildOutputStream(mDataFile, binaryCache)
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream { private fun buildInputStream(file: File?, binaryCache: BinaryCache): InputStream {
val cipherKey = binaryCache.loadedCipherKey
return when { return when {
file != null && file.length() > 0 -> { file != null && file.length() > 0 -> {
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
@@ -87,7 +82,8 @@ class BinaryFile : BinaryData {
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream { private fun buildOutputStream(file: File?, binaryCache: BinaryCache): OutputStream {
val cipherKey = binaryCache.loadedCipherKey
return when { return when {
file != null -> { file != null -> {
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
@@ -98,14 +94,14 @@ class BinaryFile : BinaryData {
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun compress(cipherKey: Database.LoadedKey) { override fun compress(binaryCache: BinaryCache) {
mDataFile?.let { concreteDataFile -> mDataFile?.let { concreteDataFile ->
// To compress, create a new binary with file // To compress, create a new binary with file
if (!isCompressed) { if (!isCompressed) {
// Encrypt the new gzipped temp file // Encrypt the new gzipped temp file
val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
getInputDataStream(cipherKey).use { inputStream -> getInputDataStream(binaryCache).use { inputStream ->
GZIPOutputStream(buildOutputStream(fileBinaryCompress, cipherKey)).use { outputStream -> GZIPOutputStream(buildOutputStream(fileBinaryCompress, binaryCache)).use { outputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
} }
@@ -123,13 +119,13 @@ class BinaryFile : BinaryData {
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun decompress(cipherKey: Database.LoadedKey) { override fun decompress(binaryCache: BinaryCache) {
mDataFile?.let { concreteDataFile -> mDataFile?.let { concreteDataFile ->
if (isCompressed) { if (isCompressed) {
// Encrypt the new ungzipped temp file // Encrypt the new ungzipped temp file
val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
getUnGzipInputDataStream(cipherKey).use { inputStream -> getUnGzipInputDataStream(binaryCache).use { inputStream ->
buildOutputStream(fileBinaryDecompress, cipherKey).use { outputStream -> buildOutputStream(fileBinaryDecompress, binaryCache).use { outputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
} }
@@ -146,39 +142,15 @@ class BinaryFile : BinaryData {
} }
} }
@Throws(IOException::class) override fun clear(binaryCache: BinaryCache) {
override fun clear() {
if (mDataFile != null && !mDataFile!!.delete()) if (mDataFile != null && !mDataFile!!.delete())
throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath) throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath)
} }
override fun dataExists(): Boolean {
return mDataFile != null && mLength > 0
}
override fun getSize(): Long {
return mLength
}
/**
* Hash of the raw encrypted file in temp folder, only to compare binary data
*/
@Throws(FileNotFoundException::class)
override fun binaryHash(): Int {
return mBinaryHash
}
override fun toString(): String { override fun toString(): String {
return mDataFile.toString() return mDataFile.toString()
} }
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(mDataFile?.absolutePath)
dest.writeLong(mLength)
dest.writeInt(mBinaryHash)
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is BinaryFile) return false if (other !is BinaryFile) return false
@@ -190,53 +162,10 @@ class BinaryFile : BinaryData {
override fun hashCode(): Int { override fun hashCode(): Int {
var result = super.hashCode() var result = super.hashCode()
result = 31 * result + (mDataFile?.hashCode() ?: 0) result = 31 * result + (mDataFile?.hashCode() ?: 0)
result = 31 * result + mLength.hashCode()
result = 31 * result + mBinaryHash
return result return result
} }
/**
* Custom OutputStream to calculate the size and hash of binary file
*/
private inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) {
private val mMessageDigest: MessageDigest
init {
mLength = 0
mMessageDigest = MessageDigest.getInstance("MD5")
mBinaryHash = 0
}
override fun beforeWrite(n: Int) {
super.beforeWrite(n)
mLength = byteCount
}
override fun write(idx: Int) {
super.write(idx)
mMessageDigest.update(idx.toByte())
}
override fun write(bts: ByteArray) {
super.write(bts)
mMessageDigest.update(bts)
}
override fun write(bts: ByteArray, st: Int, end: Int) {
super.write(bts, st, end)
mMessageDigest.update(bts, st, end)
}
override fun close() {
super.close()
mLength = byteCount
val bytes = mMessageDigest.digest()
mBinaryHash = ByteBuffer.wrap(bytes).int
}
}
companion object { companion object {
private val TAG = BinaryFile::class.java.name private val TAG = BinaryFile::class.java.name
@JvmField @JvmField

View File

@@ -17,20 +17,19 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.binary
import android.util.Log import android.util.Log
import java.io.File
import java.io.IOException import java.io.IOException
import kotlin.math.abs import kotlin.math.abs
abstract class BinaryPool<T> { abstract class BinaryPool<T>(private val mBinaryCache: BinaryCache) {
protected val pool = LinkedHashMap<T, BinaryData>() protected val pool = LinkedHashMap<T, BinaryData>()
// To build unique file id // To build unique file id
private var creationId: String = System.currentTimeMillis().toString() private var creationId: Long = System.currentTimeMillis()
private var poolId: String = abs(javaClass.simpleName.hashCode()).toString() private var poolId: Int = abs(javaClass.simpleName.hashCode())
private var binaryFileIncrement = 0L private var binaryFileIncrement = 0L
/** /**
@@ -196,7 +195,7 @@ abstract class BinaryPool<T> {
* Different from doForEach, provide an ordered index to each binary * Different from doForEach, provide an ordered index to each binary
*/ */
private fun doForEachBinaryWithoutDuplication(action: (keyBinary: KeyBinary<T>) -> Unit, private fun doForEachBinaryWithoutDuplication(action: (keyBinary: KeyBinary<T>) -> Unit,
conditionToAdd: (binary: BinaryData) -> Boolean) { conditionToAdd: (binary: BinaryData) -> Boolean) {
orderedBinariesWithoutDuplication(conditionToAdd).forEach { keyBinary -> orderedBinariesWithoutDuplication(conditionToAdd).forEach { keyBinary ->
action.invoke(keyBinary) action.invoke(keyBinary)
} }
@@ -227,7 +226,7 @@ abstract class BinaryPool<T> {
@Throws(IOException::class) @Throws(IOException::class)
fun clear() { fun clear() {
doForEachBinary { _, binary -> doForEachBinary { _, binary ->
binary.clear() binary.clear(mBinaryCache)
} }
pool.clear() pool.clear()
} }

View File

@@ -1,8 +1,8 @@
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.binary
import java.util.* import java.util.*
class CustomIconPool : BinaryPool<UUID>() { class CustomIconPool(binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
override fun findUnusedKey(): UUID { override fun findUnusedKey(): UUID {
var newUUID = UUID.randomUUID() var newUUID = UUID.randomUUID()

View File

@@ -0,0 +1,18 @@
package com.kunzisoft.keepass.database.element.binary
import java.io.Serializable
import java.security.Key
import java.security.SecureRandom
import javax.crypto.KeyGenerator
class LoadedKey(val key: Key, val iv: ByteArray): Serializable {
companion object {
const val BINARY_CIPHER = "Blowfish/CBC/PKCS5Padding"
fun generateNewCipherKey(): LoadedKey {
val iv = ByteArray(8)
SecureRandom().nextBytes(iv)
return LoadedKey(KeyGenerator.getInstance("Blowfish").generateKey(), iv)
}
}
}

View File

@@ -1,126 +0,0 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Database
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
abstract class BinaryData : Parcelable {
var isCompressed: Boolean = false
protected set
var isProtected: Boolean = false
protected set
var isCorrupted: Boolean = false
/**
* Empty protected binary
*/
protected constructor()
protected constructor(compressed: Boolean = false, protected: Boolean = false) {
this.isCompressed = compressed
this.isProtected = protected
}
protected constructor(parcel: Parcel) {
isCompressed = parcel.readByte().toInt() != 0
isProtected = parcel.readByte().toInt() != 0
isCorrupted = parcel.readByte().toInt() != 0
}
@Throws(IOException::class)
abstract fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream
@Throws(IOException::class)
abstract fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream
@Throws(IOException::class)
fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream {
return if (isCompressed) {
GZIPInputStream(getInputDataStream(cipherKey))
} else {
getInputDataStream(cipherKey)
}
}
@Throws(IOException::class)
fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
return if (isCompressed) {
GZIPOutputStream(getOutputDataStream(cipherKey))
} else {
getOutputDataStream(cipherKey)
}
}
@Throws(IOException::class)
abstract fun compress(cipherKey: Database.LoadedKey)
@Throws(IOException::class)
abstract fun decompress(cipherKey: Database.LoadedKey)
@Throws(IOException::class)
abstract fun clear()
abstract fun dataExists(): Boolean
abstract fun getSize(): Long
abstract fun binaryHash(): Int
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BinaryData) return false
if (isCompressed != other.isCompressed) return false
if (isProtected != other.isProtected) return false
if (isCorrupted != other.isCorrupted) return false
return true
}
override fun hashCode(): Int {
var result = isCompressed.hashCode()
result = 31 * result + isProtected.hashCode()
result = 31 * result + isCorrupted.hashCode()
return result
}
companion object {
private val TAG = BinaryData::class.java.name
}
}

View File

@@ -19,23 +19,20 @@
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.encrypt.aes.AESTransformer
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -45,9 +42,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private var kdfListV3: MutableList<KdfEngine> = ArrayList() private var kdfListV3: MutableList<KdfEngine> = ArrayList()
// Only to generate unique file name
private var binaryPool = AttachmentPool()
override val version: String override val version: String
get() = "KeePass 1" get() = "KeePass 1"
@@ -80,6 +74,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
get() { get() {
val list = ArrayList<EncryptionAlgorithm>() val list = ArrayList<EncryptionAlgorithm>()
list.add(EncryptionAlgorithm.AESRijndael) list.add(EncryptionAlgorithm.AESRijndael)
list.add(EncryptionAlgorithm.Twofish)
return list return list
} }
@@ -145,24 +140,11 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
} }
@Throws(IOException::class) @Throws(IOException::class)
fun makeFinalKey(masterSeed: ByteArray, masterSeed2: ByteArray, numRounds: Long) { fun makeFinalKey(masterSeed: ByteArray, transformSeed: ByteArray, numRounds: Long) {
// Write checksum Checksum
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here.")
}
val nos = NullOutputStream()
val dos = DigestOutputStream(nos, messageDigest)
// Encrypt the master key a few times to make brute-force key-search harder // Encrypt the master key a few times to make brute-force key-search harder
dos.write(masterSeed) val transformedKey = AESTransformer.transformKey(transformSeed, masterKey, numRounds) ?: ByteArray(0)
dos.write(AESKeyTransformerFactory.transformMasterKey(masterSeed2, masterKey, numRounds) ?: ByteArray(0)) // Write checksum Checksum
finalKey = HashManager.hashSha256(masterSeed, transformedKey)
finalKey = messageDigest.digest()
} }
override fun createGroup(): GroupKDB { override fun createGroup(): GroupKDB {
@@ -275,11 +257,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
addEntryTo(entry, origParent) addEntryTo(entry, origParent)
} }
fun buildNewAttachment(cacheDirectory: File): BinaryData { fun buildNewAttachment(): BinaryData {
// Generate an unique new file // Generate an unique new file
return binaryPool.put { uniqueBinaryId -> return attachmentPool.put { uniqueBinaryId ->
val fileInCache = File(cacheDirectory, uniqueBinaryId) binaryCache.getBinaryData(uniqueBinaryId, false)
BinaryFile(fileInCache)
}.binary }.binary
} }

View File

@@ -22,16 +22,21 @@ package com.kunzisoft.keepass.database.element.database
import android.content.res.Resources import android.content.res.Resources
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.longTo8Bytes
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.AesEngine
import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
@@ -39,33 +44,31 @@ import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4 import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.VariantDictionary
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import org.w3c.dom.Node import org.w3c.dom.Node
import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.* import java.util.*
import javax.crypto.Mac
import javax.xml.XMLConstants import javax.xml.XMLConstants
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException import javax.xml.parsers.ParserConfigurationException
import kotlin.math.min
class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> { class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var hmacKey: ByteArray? = null var hmacKey: ByteArray? = null
private set private set
var dataCipher = AesEngine.CIPHER_UUID var cipherUuid = EncryptionAlgorithm.AESRijndael.uuid
private var dataEngine: CipherEngine = AesEngine() private var dataEngine: CipherEngine = AesEngine()
var compressionAlgorithm = CompressionAlgorithm.GZip var compressionAlgorithm = CompressionAlgorithm.GZip
var kdfParameters: KdfParameters? = null var kdfParameters: KdfParameters? = null
@@ -108,8 +111,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
val deletedObjects = ArrayList<DeletedObject>() val deletedObjects = ArrayList<DeletedObject>()
val customData = HashMap<String, String>() val customData = HashMap<String, String>()
var binaryPool = AttachmentPool()
var localizedAppName = "KeePassDX" var localizedAppName = "KeePassDX"
init { init {
@@ -186,7 +187,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
// Only in databaseV3.1, in databaseV4 the header is zipped during the save // Only in databaseV3.1, in databaseV4 the header is zipped during the save
if (kdbxVersion.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) { if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
compressAllBinaries() compressAllBinaries()
} }
} }
@@ -194,9 +195,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
// In databaseV4 the header is zipped during the save, so not necessary here // In databaseV4 the header is zipped during the save, so not necessary here
if (kdbxVersion.toKotlinLong() >= FILE_VERSION_32_4.toKotlinLong()) { if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
decompressAllBinaries()
} else {
when (newCompression) { when (newCompression) {
CompressionAlgorithm.None -> { CompressionAlgorithm.None -> {
decompressAllBinaries() decompressAllBinaries()
@@ -204,18 +203,18 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
} }
} }
} else {
decompressAllBinaries()
} }
} }
} }
} }
private fun compressAllBinaries() { private fun compressAllBinaries() {
binaryPool.doForEachBinary { _, binary -> attachmentPool.doForEachBinary { _, binary ->
try { try {
val cipherKey = loadedCipherKey
?: throw IOException("Unable to retrieve cipher key to compress binaries")
// To compress, create a new binary with file // To compress, create a new binary with file
binary.compress(cipherKey) binary.compress(binaryCache)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to compress $binary", e) Log.e(TAG, "Unable to compress $binary", e)
} }
@@ -223,11 +222,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
private fun decompressAllBinaries() { private fun decompressAllBinaries() {
binaryPool.doForEachBinary { _, binary -> attachmentPool.doForEachBinary { _, binary ->
try { try {
val cipherKey = loadedCipherKey binary.decompress(binaryCache)
?: throw IOException("Unable to retrieve cipher key to decompress binaries")
binary.decompress(cipherKey)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to decompress $binary", e) Log.e(TAG, "Unable to decompress $binary", e)
} }
@@ -310,17 +307,15 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return this.iconsManager.getIcon(iconId) return this.iconsManager.getIcon(iconId)
} }
fun buildNewCustomIcon(cacheDirectory: File, fun buildNewCustomIcon(customIconId: UUID? = null,
customIconId: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit) { result: (IconImageCustom, BinaryData?) -> Unit) {
iconsManager.buildNewCustomIcon(cacheDirectory, customIconId, result) iconsManager.buildNewCustomIcon(customIconId, result)
} }
fun addCustomIcon(cacheDirectory: File, fun addCustomIcon(customIconId: UUID? = null,
customIconId: UUID? = null, smallSize: Boolean,
dataSize: Int,
result: (IconImageCustom, BinaryData?) -> Unit) { result: (IconImageCustom, BinaryData?) -> Unit) {
iconsManager.addCustomIcon(cacheDirectory, customIconId, dataSize, result) iconsManager.addCustomIcon(customIconId, smallSize, result)
} }
fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean { fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean {
@@ -352,14 +347,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
masterKey = getFileKey(keyInputStream) masterKey = getFileKey(keyInputStream)
} }
val messageDigest: MessageDigest return HashManager.hashSha256(masterKey)
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-256 implementation")
}
return messageDigest.digest(masterKey)
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -370,13 +358,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters) var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters)
if (transformedMasterKey.size != 32) { if (transformedMasterKey.size != 32) {
transformedMasterKey = CryptoUtil.hashSha256(transformedMasterKey) transformedMasterKey = HashManager.hashSha256(transformedMasterKey)
} }
val cmpKey = ByteArray(65) val cmpKey = ByteArray(65)
System.arraycopy(masterSeed, 0, cmpKey, 0, 32) System.arraycopy(masterSeed, 0, cmpKey, 0, 32)
System.arraycopy(transformedMasterKey, 0, cmpKey, 32, 32) System.arraycopy(transformedMasterKey, 0, cmpKey, 32, 32)
finalKey = CryptoUtil.resizeKey(cmpKey, 0, 64, dataEngine.keyLength()) finalKey = resizeKey(cmpKey, dataEngine.keyLength())
val messageDigest: MessageDigest val messageDigest: MessageDigest
try { try {
@@ -391,6 +379,47 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
} }
private fun resizeKey(inBytes: ByteArray, cbOut: Int): ByteArray {
if (cbOut == 0) return ByteArray(0)
val messageDigest = if (cbOut <= 32) HashManager.getHash256() else HashManager.getHash512()
messageDigest.update(inBytes, 0, 64)
val hash: ByteArray = messageDigest.digest()
if (cbOut == hash.size) {
return hash
}
val ret = ByteArray(cbOut)
if (cbOut < hash.size) {
System.arraycopy(hash, 0, ret, 0, cbOut)
} else {
var pos = 0
var r: Long = 0
while (pos < cbOut) {
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size)
System.arraycopy(part, 0, ret, pos, copy)
pos += copy
r++
Arrays.fill(part, 0.toByte())
}
}
Arrays.fill(hash, 0.toByte())
return ret
}
override fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? { override fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
try { try {
val documentBuilderFactory = DocumentBuilderFactory.newInstance() val documentBuilderFactory = DocumentBuilderFactory.newInstance()
@@ -488,17 +517,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
private fun checkKeyFileHash(data: String, hash: String): Boolean { private fun checkKeyFileHash(data: String, hash: String): Boolean {
val digest: MessageDigest?
var success = false var success = false
try { try {
digest = MessageDigest.getInstance("SHA-256")
digest?.reset()
// hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key. // hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
val dataDigest = digest.digest(Hex.decodeHex(data.toCharArray())) val dataDigest = HashManager.hashSha256(Hex.decodeHex(data.toCharArray()))
.copyOfRange(0, 4) .copyOfRange(0, 4).toHexString()
.toHexString()
success = dataDigest == hash success = dataDigest == hash
} catch (e: NoSuchAlgorithmException) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
return success return success
@@ -641,13 +666,12 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return publicCustomData.size() > 0 return publicCustomData.size() > 0
} }
fun buildNewAttachment(cacheDirectory: File, fun buildNewAttachment(smallSize: Boolean,
compression: Boolean, compression: Boolean,
protection: Boolean, protection: Boolean,
binaryPoolId: Int? = null): BinaryData { binaryPoolId: Int? = null): BinaryData {
return binaryPool.put(binaryPoolId) { uniqueBinaryId -> return attachmentPool.put(binaryPoolId) { uniqueBinaryId ->
val fileInCache = File(cacheDirectory, uniqueBinaryId) binaryCache.getBinaryData(uniqueBinaryId, smallSize, compression, protection)
BinaryFile(fileInCache, compression, protection)
}.binary }.binary
} }
@@ -665,7 +689,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// Build binaries to remove with all binaries known // Build binaries to remove with all binaries known
val binariesToRemove = ArrayList<BinaryData>() val binariesToRemove = ArrayList<BinaryData>()
if (binaries.isEmpty()) { if (binaries.isEmpty()) {
binaryPool.doForEachBinary { _, binary -> attachmentPool.doForEachBinary { _, binary ->
binariesToRemove.add(binary) binariesToRemove.add(binary)
} }
} else { } else {
@@ -674,7 +698,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// Remove binaries from the list // Remove binaries from the list
rootGroup?.doForEachChild(object : NodeHandler<EntryKDBX>() { rootGroup?.doForEachChild(object : NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean { override fun operate(node: EntryKDBX): Boolean {
node.getAttachments(binaryPool, true).forEach { node.getAttachments(attachmentPool, true).forEach {
binariesToRemove.remove(it.binaryData) binariesToRemove.remove(it.binaryData)
} }
return binariesToRemove.isNotEmpty() return binariesToRemove.isNotEmpty()
@@ -683,9 +707,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// Effective removing // Effective removing
binariesToRemove.forEach { binariesToRemove.forEach {
try { try {
binaryPool.remove(it) attachmentPool.remove(it)
if (clear) if (clear)
it.clear() it.clear(binaryCache)
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Unable to clean binaries", e) Log.w(TAG, "Unable to clean binaries", e)
} }
@@ -701,7 +725,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
override fun clearCache() { override fun clearCache() {
try { try {
super.clearCache() super.clearCache()
binaryPool.clear() attachmentPool.clear()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to clear cache", e) Log.e(TAG, "Unable to clear cache", e)
} }

View File

@@ -19,15 +19,16 @@
*/ */
package com.kunzisoft.keepass.database.element.database package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.entry.EntryVersioned import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.group.GroupVersioned import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconsManager import com.kunzisoft.keepass.database.element.icon.IconsManager
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import org.apache.commons.codec.binary.Hex import org.apache.commons.codec.binary.Hex
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@@ -35,7 +36,6 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.UnsupportedEncodingException import java.io.UnsupportedEncodingException
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.* import java.util.*
abstract class DatabaseVersioned< abstract class DatabaseVersioned<
@@ -48,21 +48,22 @@ abstract class DatabaseVersioned<
// Algorithm used to encrypt the database // Algorithm used to encrypt the database
protected var algorithm: EncryptionAlgorithm? = null protected var algorithm: EncryptionAlgorithm? = null
abstract val kdfEngine: KdfEngine? abstract val kdfEngine: com.kunzisoft.keepass.database.crypto.kdf.KdfEngine?
abstract val kdfAvailableList: List<KdfEngine> abstract val kdfAvailableList: List<com.kunzisoft.keepass.database.crypto.kdf.KdfEngine>
var masterKey = ByteArray(32) var masterKey = ByteArray(32)
var finalKey: ByteArray? = null var finalKey: ByteArray? = null
protected set protected set
/** /**
* To manage binaries in faster way
* Cipher key generated when the database is loaded, and destroyed when the database is closed * Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements * Can be used to temporarily store database elements
*/ */
var loadedCipherKey: Database.LoadedKey? = null var binaryCache = BinaryCache()
val iconsManager = IconsManager(binaryCache)
val iconsManager = IconsManager() var attachmentPool = AttachmentPool(binaryCache)
var changeDuplicateId = false var changeDuplicateId = false
@@ -99,42 +100,21 @@ abstract class DatabaseVersioned<
protected fun getCompositeKey(key: String, keyfileInputStream: InputStream): ByteArray { protected fun getCompositeKey(key: String, keyfileInputStream: InputStream): ByteArray {
val fileKey = getFileKey(keyfileInputStream) val fileKey = getFileKey(keyfileInputStream)
val passwordKey = getPasswordKey(key) val passwordKey = getPasswordKey(key)
return HashManager.hashSha256(passwordKey, fileKey)
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not supported")
}
messageDigest.update(passwordKey)
return messageDigest.digest(fileKey)
} }
@Throws(IOException::class) @Throws(IOException::class)
protected fun getPasswordKey(key: String): ByteArray { protected fun getPasswordKey(key: String): ByteArray {
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not supported")
}
val bKey: ByteArray = try { val bKey: ByteArray = try {
key.toByteArray(charset(passwordEncoding)) key.toByteArray(charset(passwordEncoding))
} catch (e: UnsupportedEncodingException) { } catch (e: UnsupportedEncodingException) {
key.toByteArray() key.toByteArray()
} }
return HashManager.hashSha256(bKey)
messageDigest.update(bKey, 0, bKey.size)
return messageDigest.digest()
} }
@Throws(IOException::class) @Throws(IOException::class)
protected fun getFileKey(keyInputStream: InputStream): ByteArray { protected fun getFileKey(keyInputStream: InputStream): ByteArray {
val keyData = keyInputStream.readBytes() val keyData = keyInputStream.readBytes()
// Check XML key file // Check XML key file
@@ -152,13 +132,8 @@ abstract class DatabaseVersioned<
// Key is not base 64, treat it as binary data // Key is not base 64, treat it as binary data
} }
} }
// Hash file as binary data // Hash file as binary data
try { return HashManager.hashSha256(keyData)
return MessageDigest.getInstance("SHA-256").digest(keyData)
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not supported")
}
} }
protected open fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? { protected open fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {

View File

@@ -22,7 +22,8 @@ package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.BinaryData import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
@@ -56,7 +57,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
/** A string describing what is in binaryData */ /** A string describing what is in binaryData */
var binaryDescription = "" var binaryDescription = ""
var binaryData: BinaryData? = null private var binaryDataId: Int? = null
// Determine if this is a MetaStream entry // Determine if this is a MetaStream entry
val isMetaStream: Boolean val isMetaStream: Boolean
@@ -89,7 +90,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
url = parcel.readString() ?: url url = parcel.readString() ?: url
notes = parcel.readString() ?: notes notes = parcel.readString() ?: notes
binaryDescription = parcel.readString() ?: binaryDescription binaryDescription = parcel.readString() ?: binaryDescription
binaryData = parcel.readParcelable(BinaryData::class.java.classLoader) binaryDataId = parcel.readInt()
} }
override fun readParentParcelable(parcel: Parcel): GroupKDB? { override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -108,7 +109,9 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeString(url) dest.writeString(url)
dest.writeString(notes) dest.writeString(notes)
dest.writeString(binaryDescription) dest.writeString(binaryDescription)
dest.writeParcelable(binaryData, flags) binaryDataId?.let {
dest.writeInt(it)
}
} }
fun updateWith(source: EntryKDB) { fun updateWith(source: EntryKDB) {
@@ -119,7 +122,7 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
url = source.url url = source.url
notes = source.notes notes = source.notes
binaryDescription = source.binaryDescription binaryDescription = source.binaryDescription
binaryData = source.binaryData binaryDataId = source.binaryDataId
} }
override var username = "" override var username = ""
@@ -138,26 +141,39 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
override val type: Type override val type: Type
get() = Type.ENTRY get() = Type.ENTRY
fun getAttachment(): Attachment? { fun getAttachment(attachmentPool: AttachmentPool): Attachment? {
val binary = binaryData binaryDataId?.let { poolId ->
return if (binary != null) attachmentPool[poolId]?.let { binary ->
Attachment(binaryDescription, binary) return Attachment(binaryDescription, binary)
else null }
}
return null
} }
fun containsAttachment(): Boolean { fun containsAttachment(): Boolean {
return binaryData != null return binaryDataId != null
} }
fun putAttachment(attachment: Attachment) { fun getBinary(attachmentPool: AttachmentPool): BinaryData? {
this.binaryDataId?.let {
return attachmentPool[it]
}
return null
}
fun putBinary(binaryData: BinaryData, attachmentPool: AttachmentPool) {
this.binaryDataId = attachmentPool.put(binaryData)
}
fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
this.binaryDescription = attachment.name this.binaryDescription = attachment.name
this.binaryData = attachment.binaryData this.binaryDataId = attachmentPool.put(attachment.binaryData)
} }
fun removeAttachment(attachment: Attachment? = null) { fun removeAttachment(attachment: Attachment? = null) {
if (attachment == null || this.binaryDescription == attachment.name) { if (attachment == null || this.binaryDescription == attachment.name) {
this.binaryDescription = "" this.binaryDescription = ""
this.binaryData = null this.binaryDataId = null
} }
} }

View File

@@ -21,9 +21,10 @@ package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.database.AttachmentPool import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
@@ -32,7 +33,6 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashMap import kotlin.collections.LinkedHashMap
@@ -338,8 +338,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
override fun touch(modified: Boolean, touchParents: Boolean) { override fun touch(modified: Boolean, touchParents: Boolean) {
super.touch(modified, touchParents) super.touch(modified, touchParents)
// TODO unsigned long usageCount.plusOne()
usageCount = UnsignedLong(usageCount.toKotlinLong() + 1)
} }
companion object { companion object {

View File

@@ -20,22 +20,19 @@
package com.kunzisoft.keepass.database.element.icon package com.kunzisoft.keepass.database.element.icon
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.database.element.database.BinaryByte import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.database.BinaryByte.Companion.MAX_BINARY_BYTES import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.database.BinaryData import com.kunzisoft.keepass.database.element.binary.CustomIconPool
import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.database.element.database.CustomIconPool
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
import java.io.File
import java.util.* import java.util.*
class IconsManager { class IconsManager(private val binaryCache: BinaryCache) {
private val standardCache = List(NB_ICONS) { private val standardCache = List(NB_ICONS) {
IconImageStandard(it) IconImageStandard(it)
} }
private val customCache = CustomIconPool() private val customCache = CustomIconPool(binaryCache)
fun getIcon(iconId: Int): IconImageStandard { fun getIcon(iconId: Int): IconImageStandard {
val searchIconId = if (IconImageStandard.isCorrectIconId(iconId)) iconId else KEY_ID val searchIconId = if (IconImageStandard.isCorrectIconId(iconId)) iconId else KEY_ID
@@ -52,25 +49,18 @@ class IconsManager {
* Custom * Custom
*/ */
fun buildNewCustomIcon(cacheDirectory: File, fun buildNewCustomIcon(key: UUID? = null,
key: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit) { result: (IconImageCustom, BinaryData?) -> Unit) {
// Create a binary file for a brand new custom icon // Create a binary file for a brand new custom icon
addCustomIcon(cacheDirectory, key, -1, result) addCustomIcon(key, false, result)
} }
fun addCustomIcon(cacheDirectory: File, fun addCustomIcon(key: UUID? = null,
key: UUID? = null, smallSize: Boolean,
dataSize: Int,
result: (IconImageCustom, BinaryData?) -> Unit) { result: (IconImageCustom, BinaryData?) -> Unit) {
val keyBinary = customCache.put(key) { uniqueBinaryId -> val keyBinary = customCache.put(key) { uniqueBinaryId ->
// Create a byte array for better performance with small data // Create a byte array for better performance with small data
if (dataSize in 1..MAX_BINARY_BYTES) { binaryCache.getBinaryData(uniqueBinaryId, smallSize)
BinaryByte()
} else {
val fileInCache = File(cacheDirectory, uniqueBinaryId)
BinaryFile(fileInCache)
}
} }
result.invoke(IconImageCustom(keyBinary.keys.first()), keyBinary.binary) result.invoke(IconImageCustom(keyBinary.keys.first()), keyBinary.binary)
} }
@@ -83,11 +73,11 @@ class IconsManager {
return customCache.isBinaryDuplicate(binaryData) return customCache.isBinaryDuplicate(binaryData)
} }
fun removeCustomIcon(iconUuid: UUID) { fun removeCustomIcon(binaryCache: BinaryCache, iconUuid: UUID) {
val binary = customCache[iconUuid] val binary = customCache[iconUuid]
customCache.remove(iconUuid) customCache.remove(iconUuid)
try { try {
binary?.clear() binary?.clear(binaryCache)
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, "Unable to remove custom icon binary", e) Log.w(TAG, "Unable to remove custom icon binary", e)
} }

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.security
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.engine.TwofishEngine
import com.kunzisoft.keepass.utils.ObjectNameResource
import java.util.UUID
enum class EncryptionAlgorithm : ObjectNameResource {
AESRijndael,
Twofish,
ChaCha20;
val cipherEngine: CipherEngine
get() {
return when (this) {
AESRijndael -> AesEngine()
Twofish -> TwofishEngine()
ChaCha20 -> ChaCha20Engine()
}
}
val dataCipher: UUID
get() {
return when (this) {
AESRijndael -> AesEngine.CIPHER_UUID
Twofish -> TwofishEngine.CIPHER_UUID
ChaCha20 -> ChaCha20Engine.CIPHER_UUID
}
}
override fun getName(resources: Resources): String {
return when (this) {
AESRijndael -> resources.getString(R.string.encryption_rijndael)
Twofish -> resources.getString(R.string.encryption_twofish)
ChaCha20 -> resources.getString(R.string.encryption_chacha20)
}
}
}

View File

@@ -21,9 +21,9 @@
package com.kunzisoft.keepass.database.file package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.stream.readBytesLength
import com.kunzisoft.keepass.stream.readBytes4ToUInt
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import com.kunzisoft.keepass.utils.readBytesLength
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream

View File

@@ -19,30 +19,26 @@
*/ */
package com.kunzisoft.keepass.database.file package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.crypto.CrsAlgorithm import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.crypto.keyDerivation.AesKdf import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.AesKdf
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.exception.VersionDatabaseException import com.kunzisoft.keepass.database.exception.VersionDatabaseException
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.CopyInputStream
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.VariantDictionary
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.DigestInputStream import java.security.DigestInputStream
import java.security.InvalidKeyException
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader() { class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader() {
var innerRandomStreamKey: ByteArray = ByteArray(32) var innerRandomStreamKey: ByteArray = ByteArray(32)
@@ -140,33 +136,27 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
*/ */
@Throws(IOException::class, VersionDatabaseException::class) @Throws(IOException::class, VersionDatabaseException::class)
fun loadFromFile(inputStream: InputStream): HeaderAndHash { fun loadFromFile(inputStream: InputStream): HeaderAndHash {
val messageDigest: MessageDigest val messageDigest: MessageDigest = HashManager.getHash256()
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-256 implementation")
}
val headerBOS = ByteArrayOutputStream() val headerBOS = ByteArrayOutputStream()
val copyInputStream = CopyInputStream(inputStream, headerBOS) val copyInputStream = CopyInputStream(inputStream, headerBOS)
val digestInputStream = DigestInputStream(copyInputStream, messageDigest) val digestInputStream = DigestInputStream(copyInputStream, messageDigest)
val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream)
val sig1 = littleEndianDataInputStream.readUInt() val sig1 = digestInputStream.readBytes4ToUInt()
val sig2 = littleEndianDataInputStream.readUInt() val sig2 = digestInputStream.readBytes4ToUInt()
if (!matchesHeader(sig1, sig2)) { if (!matchesHeader(sig1, sig2)) {
throw VersionDatabaseException() throw VersionDatabaseException()
} }
version = littleEndianDataInputStream.readUInt() // Erase previous value version = digestInputStream.readBytes4ToUInt() // Erase previous value
if (!validVersion(version)) { if (!validVersion(version)) {
throw VersionDatabaseException() throw VersionDatabaseException()
} }
var done = false var done = false
while (!done) { while (!done) {
done = readHeaderField(littleEndianDataInputStream) done = readHeaderField(digestInputStream)
} }
val hash = messageDigest.digest() val hash = messageDigest.digest()
@@ -174,13 +164,13 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean { private fun readHeaderField(dis: InputStream): Boolean {
val fieldID = dis.read().toByte() val fieldID = dis.read().toByte()
val fieldSize: Int = if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) { val fieldSize: Int = if (version.isBefore(FILE_VERSION_32_4)) {
dis.readUShort() dis.readBytes2ToUShort()
} else { } else {
dis.readUInt().toKotlinInt() dis.readBytes4ToUInt().toKotlinInt()
} }
var fieldData: ByteArray? = null var fieldData: ByteArray? = null
@@ -204,20 +194,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
PwDbHeaderV4Fields.TransformSeed -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_32_4))
transformSeed = fieldData transformSeed = fieldData
PwDbHeaderV4Fields.TransformRounds -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_32_4))
setTransformRound(fieldData) setTransformRound(fieldData)
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_32_4))
innerRandomStreamKey = fieldData innerRandomStreamKey = fieldData
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.toKotlinLong() < FILE_VERSION_32_4.toKotlinLong()) PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_32_4))
setRandomStreamID(fieldData) setRandomStreamID(fieldData)
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData) PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
@@ -244,14 +234,14 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid cipher ID.") throw IOException("Invalid cipher ID.")
} }
databaseV4.dataCipher = bytes16ToUuid(pbId) databaseV4.cipherUuid = bytes16ToUuid(pbId)
} }
private fun setTransformRound(roundsByte: ByteArray) { private fun setTransformRound(roundsByte: ByteArray) {
assignAesKdfEngineIfNotExists() assignAesKdfEngineIfNotExists()
val rounds = bytes64ToLong(roundsByte) val rounds = bytes64ToULong(roundsByte)
databaseV4.kdfParameters?.setUInt64(AesKdf.PARAM_ROUNDS, rounds) databaseV4.kdfParameters?.setUInt64(AesKdf.PARAM_ROUNDS, rounds)
databaseV4.numberKeyEncryptionRounds = rounds databaseV4.numberKeyEncryptionRounds = rounds.toKotlinLong()
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -323,23 +313,5 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean { fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2) return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2)
} }
@Throws(IOException::class)
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
val blockKey = HmacBlockStream.getHmacKey64(key, UnsignedLong.MAX_VALUE)
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("No HmacAlogirthm")
} catch (e: InvalidKeyException) {
throw IOException("Invalid Hmac Key")
}
return hmac.doFinal(header)
}
} }
} }

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.file;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
import java.util.Date;
public class DateKDBXUtil {
private static final DateTime dotNetEpoch = new DateTime(1, 1, 1, 0, 0, 0, DateTimeZone.UTC);
private static final DateTime javaEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeZone.UTC);
private static final long epochOffset;
static {
epochOffset = (javaEpoch.getMillis() - dotNetEpoch.getMillis()) / 1000L;
}
public static Date convertKDBX4Time(long seconds) {
DateTime dt = dotNetEpoch.plus(seconds * 1000L);
// Switch corrupted dates to a more recent date that won't cause issues on the client
if (dt.isBefore(javaEpoch)) {
return javaEpoch.toDate();
}
return dt.toDate();
}
public static long convertDateToKDBX4Time(DateTime dt) {
Duration duration = new Duration( javaEpoch, dt );
long seconds = ( duration.getMillis() / 1000L );
return seconds + epochOffset;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.file
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.Duration
import java.util.*
object DateKDBXUtil {
private val dotNetEpoch = DateTime(1, 1, 1, 0, 0, 0, DateTimeZone.UTC)
private val javaEpoch = DateTime(1970, 1, 1, 0, 0, 0, DateTimeZone.UTC)
private val epochOffset = (javaEpoch.millis - dotNetEpoch.millis) / 1000L
fun convertKDBX4Time(seconds: Long): Date {
val dt = dotNetEpoch.plus(seconds * 1000L)
// Switch corrupted dates to a more recent date that won't cause issues on the client
return if (dt.isBefore(javaEpoch)) {
javaEpoch.toDate()
} else dt.toDate()
}
fun convertDateToKDBX4Time(dt: DateTime?): Long {
val duration = Duration(javaEpoch, dt)
val seconds = duration.millis / 1000L
return seconds + epochOffset
}
}

View File

@@ -19,15 +19,21 @@
*/ */
package com.kunzisoft.keepass.database.file.input package com.kunzisoft.keepass.database.file.input
import com.kunzisoft.keepass.database.element.Database import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>> abstract class DatabaseInput<D : DatabaseVersioned<*, *, *, *>>
(protected val cacheDirectory: File) { (protected val cacheDirectory: File,
protected val isRAMSufficient: (memoryWanted: Long) -> Boolean) {
private var startTimeKey = System.currentTimeMillis()
private var startTimeContent = System.currentTimeMillis()
/** /**
* Load a versioned database file, return contents in a new DatabaseVersioned. * Load a versioned database file, return contents in a new DatabaseVersioned.
@@ -43,15 +49,39 @@ abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>>
abstract fun openDatabase(databaseInputStream: InputStream, abstract fun openDatabase(databaseInputStream: InputStream,
password: String?, password: String?,
keyfileInputStream: InputStream?, keyfileInputStream: InputStream?,
loadedCipherKey: Database.LoadedKey, loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?, progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean = false): PwDb fixDuplicateUUID: Boolean = false): D
@Throws(LoadDatabaseException::class) @Throws(LoadDatabaseException::class)
abstract fun openDatabase(databaseInputStream: InputStream, abstract fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray, masterKey: ByteArray,
loadedCipherKey: Database.LoadedKey, loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?, progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean = false): PwDb fixDuplicateUUID: Boolean = false): D
protected fun startKeyTimer(progressTaskUpdater: ProgressTaskUpdater?) {
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
Log.d(TAG, "Start retrieving database key...")
startTimeKey = System.currentTimeMillis()
}
protected fun stopKeyTimer() {
Log.d(TAG, "Stop retrieving database key... ${System.currentTimeMillis() - startTimeKey} ms")
}
protected fun startContentTimer(progressTaskUpdater: ProgressTaskUpdater?) {
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
Log.d(TAG, "Start decrypting database content...")
startTimeContent = System.currentTimeMillis()
}
protected fun stopContentTimer() {
Log.d(TAG, "Stop retrieving database content... ${System.currentTimeMillis() - startTimeContent} ms")
}
companion object {
private val TAG = DatabaseInput::class.java.name
}
} }

View File

@@ -20,34 +20,34 @@
package com.kunzisoft.keepass.database.file.input package com.kunzisoft.keepass.database.file.input
import com.kunzisoft.keepass.R import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.DatabaseKDB import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.*
import java.io.* import java.io.*
import java.security.* import java.security.DigestInputStream
import java.security.MessageDigest
import java.util.* import java.util.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException import javax.crypto.CipherInputStream
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
/** /**
* Load a KDB database file. * Load a KDB database file.
*/ */
class DatabaseInputKDB(cacheDirectory: File) class DatabaseInputKDB(cacheDirectory: File,
: DatabaseInput<DatabaseKDB>(cacheDirectory) { isRAMSufficient: (memoryWanted: Long) -> Boolean)
: DatabaseInput<DatabaseKDB>(cacheDirectory, isRAMSufficient) {
private lateinit var mDatabase: DatabaseKDB private lateinit var mDatabase: DatabaseKDB
@@ -55,11 +55,11 @@ class DatabaseInputKDB(cacheDirectory: File)
override fun openDatabase(databaseInputStream: InputStream, override fun openDatabase(databaseInputStream: InputStream,
password: String?, password: String?,
keyfileInputStream: InputStream?, keyfileInputStream: InputStream?,
loadedCipherKey: Database.LoadedKey, loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?, progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDB { fixDuplicateUUID: Boolean): DatabaseKDB {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) { return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.retrieveMasterKey(password, keyfileInputStream) mDatabase.retrieveMasterKey(password, keyfileInputStream)
} }
} }
@@ -67,11 +67,11 @@ class DatabaseInputKDB(cacheDirectory: File)
@Throws(LoadDatabaseException::class) @Throws(LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream, override fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray, masterKey: ByteArray,
loadedCipherKey: Database.LoadedKey, loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?, progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDB { fixDuplicateUUID: Boolean): DatabaseKDB {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) { return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.masterKey = masterKey mDatabase.masterKey = masterKey
} }
} }
@@ -83,6 +83,7 @@ class DatabaseInputKDB(cacheDirectory: File)
assignMasterKey: (() -> Unit)? = null): DatabaseKDB { assignMasterKey: (() -> Unit)? = null): DatabaseKDB {
try { try {
startKeyTimer(progressTaskUpdater)
// Load entire file, most of it's encrypted. // Load entire file, most of it's encrypted.
val fileSize = databaseInputStream.available() val fileSize = databaseInputStream.available()
@@ -105,8 +106,8 @@ class DatabaseInputKDB(cacheDirectory: File)
throw VersionDatabaseException() throw VersionDatabaseException()
} }
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
mDatabase = DatabaseKDB() mDatabase = DatabaseKDB()
mDatabase.binaryCache.cacheDirectory = cacheDirectory
mDatabase.changeDuplicateId = fixDuplicateUUID mDatabase.changeDuplicateId = fixDuplicateUUID
assignMasterKey?.invoke() assignMasterKey?.invoke()
@@ -130,45 +131,23 @@ class DatabaseInputKDB(cacheDirectory: File)
header.transformSeed, header.transformSeed,
mDatabase.numberKeyEncryptionRounds) mDatabase.numberKeyEncryptionRounds)
progressTaskUpdater?.updateMessage(R.string.decrypting_db) stopKeyTimer()
// Initialize Rijndael algorithm startContentTimer(progressTaskUpdater)
val cipher: Cipher = try { val cipher: Cipher = try {
when { mDatabase.encryptionAlgorithm
mDatabase.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> { .cipherEngine.getCipher(Cipher.DECRYPT_MODE,
CipherFactory.getInstance("AES/CBC/PKCS5Padding") mDatabase.finalKey ?: ByteArray(0),
} header.encryptionIV)
mDatabase.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> { } catch (e: Exception) {
CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING") throw IOException("Algorithm not supported.", e)
}
else -> throw IOException("Encryption algorithm is not supported")
}
} catch (e1: NoSuchAlgorithmException) {
throw IOException("No such algorithm")
} catch (e1: NoSuchPaddingException) {
throw IOException("No such pdading")
}
try {
cipher.init(Cipher.DECRYPT_MODE,
SecretKeySpec(mDatabase.finalKey, "AES"),
IvParameterSpec(header.encryptionIV))
} catch (e1: InvalidKeyException) {
throw IOException("Invalid key")
} catch (e1: InvalidAlgorithmParameterException) {
throw IOException("Invalid algorithm parameter.")
}
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-256 algorithm")
} }
// Decrypt content // Decrypt content
val messageDigest: MessageDigest = HashManager.getHash256()
val cipherInputStream = BufferedInputStream( val cipherInputStream = BufferedInputStream(
DigestInputStream( DigestInputStream(
BetterCipherInputStream(databaseInputStream, cipher), CipherInputStream(databaseInputStream, cipher),
messageDigest messageDigest
) )
) )
@@ -224,7 +203,7 @@ class DatabaseInputKDB(cacheDirectory: File)
} }
0x0003 -> { 0x0003 -> {
newGroup?.let { group -> newGroup?.let { group ->
group.creationTime = cipherInputStream.readBytes5ToDate() group.creationTime = DateInstant(cipherInputStream.readBytes5ToDate())
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
var iconId = cipherInputStream.readBytes4ToUInt().toKotlinInt() var iconId = cipherInputStream.readBytes4ToUInt().toKotlinInt()
@@ -237,7 +216,7 @@ class DatabaseInputKDB(cacheDirectory: File)
} }
0x0004 -> { 0x0004 -> {
newGroup?.let { group -> newGroup?.let { group ->
group.lastModificationTime = cipherInputStream.readBytes5ToDate() group.lastModificationTime = DateInstant(cipherInputStream.readBytes5ToDate())
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
entry.title = cipherInputStream.readBytesToString(fieldSize) entry.title = cipherInputStream.readBytesToString(fieldSize)
@@ -245,7 +224,7 @@ class DatabaseInputKDB(cacheDirectory: File)
} }
0x0005 -> { 0x0005 -> {
newGroup?.let { group -> newGroup?.let { group ->
group.lastAccessTime = cipherInputStream.readBytes5ToDate() group.lastAccessTime = DateInstant(cipherInputStream.readBytes5ToDate())
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
entry.url = cipherInputStream.readBytesToString(fieldSize) entry.url = cipherInputStream.readBytesToString(fieldSize)
@@ -253,7 +232,7 @@ class DatabaseInputKDB(cacheDirectory: File)
} }
0x0006 -> { 0x0006 -> {
newGroup?.let { group -> newGroup?.let { group ->
group.expiryTime = cipherInputStream.readBytes5ToDate() group.expiryTime = DateInstant(cipherInputStream.readBytes5ToDate())
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
entry.username = cipherInputStream.readBytesToString(fieldSize) entry.username = cipherInputStream.readBytesToString(fieldSize)
@@ -280,22 +259,22 @@ class DatabaseInputKDB(cacheDirectory: File)
group.groupFlags = cipherInputStream.readBytes4ToUInt().toKotlinInt() group.groupFlags = cipherInputStream.readBytes4ToUInt().toKotlinInt()
} ?: } ?:
newEntry?.let { entry -> newEntry?.let { entry ->
entry.creationTime = cipherInputStream.readBytes5ToDate() entry.creationTime = DateInstant(cipherInputStream.readBytes5ToDate())
} }
} }
0x000A -> { 0x000A -> {
newEntry?.let { entry -> newEntry?.let { entry ->
entry.lastModificationTime = cipherInputStream.readBytes5ToDate() entry.lastModificationTime = DateInstant(cipherInputStream.readBytes5ToDate())
} }
} }
0x000B -> { 0x000B -> {
newEntry?.let { entry -> newEntry?.let { entry ->
entry.lastAccessTime = cipherInputStream.readBytes5ToDate() entry.lastAccessTime = DateInstant(cipherInputStream.readBytes5ToDate())
} }
} }
0x000C -> { 0x000C -> {
newEntry?.let { entry -> newEntry?.let { entry ->
entry.expiryTime = cipherInputStream.readBytes5ToDate() entry.expiryTime = DateInstant(cipherInputStream.readBytes5ToDate())
} }
} }
0x000D -> { 0x000D -> {
@@ -306,11 +285,9 @@ class DatabaseInputKDB(cacheDirectory: File)
0x000E -> { 0x000E -> {
newEntry?.let { entry -> newEntry?.let { entry ->
if (fieldSize > 0) { if (fieldSize > 0) {
val binaryAttachment = mDatabase.buildNewAttachment(cacheDirectory) val binaryData = mDatabase.buildNewAttachment()
entry.binaryData = binaryAttachment entry.putBinary(binaryData, mDatabase.attachmentPool)
val cipherKey = mDatabase.loadedCipherKey BufferedOutputStream(binaryData.getOutputDataStream(mDatabase.binaryCache)).use { outputStream ->
?: throw IOException("Unable to retrieve cipher key to load binaries")
BufferedOutputStream(binaryAttachment.getOutputDataStream(cipherKey)).use { outputStream ->
cipherInputStream.readBytes(fieldSize) { buffer -> cipherInputStream.readBytes(fieldSize) { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
} }
@@ -343,6 +320,8 @@ class DatabaseInputKDB(cacheDirectory: File)
} }
constructTreeFromIndex() constructTreeFromIndex()
stopContentTimer()
} catch (e: LoadDatabaseException) { } catch (e: LoadDatabaseException) {
mDatabase.clearCache() mDatabase.clearCache()
throw e throw e

View File

@@ -21,15 +21,16 @@ package com.kunzisoft.keepass.database.file.input
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.R import com.kunzisoft.encrypt.StreamCipher
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.crypto.StreamCipherFactory import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.HmacBlock
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -41,13 +42,13 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HmacBlockInputStream
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.UnsignedLong
import org.bouncycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory import org.xmlpull.v1.XmlPullParserFactory
@@ -61,10 +62,12 @@ import java.util.*
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherInputStream import javax.crypto.CipherInputStream
import javax.crypto.Mac
import kotlin.math.min import kotlin.math.min
class DatabaseInputKDBX(cacheDirectory: File) class DatabaseInputKDBX(cacheDirectory: File,
: DatabaseInput<DatabaseKDBX>(cacheDirectory) { isRAMSufficient: (memoryWanted: Long) -> Boolean)
: DatabaseInput<DatabaseKDBX>(cacheDirectory, isRAMSufficient) {
private var randomStream: StreamCipher? = null private var randomStream: StreamCipher? = null
private lateinit var mDatabase: DatabaseKDBX private lateinit var mDatabase: DatabaseKDBX
@@ -97,11 +100,11 @@ class DatabaseInputKDBX(cacheDirectory: File)
override fun openDatabase(databaseInputStream: InputStream, override fun openDatabase(databaseInputStream: InputStream,
password: String?, password: String?,
keyfileInputStream: InputStream?, keyfileInputStream: InputStream?,
loadedCipherKey: Database.LoadedKey, loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?, progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDBX { fixDuplicateUUID: Boolean): DatabaseKDBX {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) { return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.retrieveMasterKey(password, keyfileInputStream) mDatabase.retrieveMasterKey(password, keyfileInputStream)
} }
} }
@@ -109,11 +112,11 @@ class DatabaseInputKDBX(cacheDirectory: File)
@Throws(LoadDatabaseException::class) @Throws(LoadDatabaseException::class)
override fun openDatabase(databaseInputStream: InputStream, override fun openDatabase(databaseInputStream: InputStream,
masterKey: ByteArray, masterKey: ByteArray,
loadedCipherKey: Database.LoadedKey, loadedCipherKey: LoadedKey,
progressTaskUpdater: ProgressTaskUpdater?, progressTaskUpdater: ProgressTaskUpdater?,
fixDuplicateUUID: Boolean): DatabaseKDBX { fixDuplicateUUID: Boolean): DatabaseKDBX {
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) { return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
mDatabase.loadedCipherKey = loadedCipherKey mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
mDatabase.masterKey = masterKey mDatabase.masterKey = masterKey
} }
} }
@@ -124,8 +127,9 @@ class DatabaseInputKDBX(cacheDirectory: File)
fixDuplicateUUID: Boolean, fixDuplicateUUID: Boolean,
assignMasterKey: (() -> Unit)? = null): DatabaseKDBX { assignMasterKey: (() -> Unit)? = null): DatabaseKDBX {
try { try {
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) startKeyTimer(progressTaskUpdater)
mDatabase = DatabaseKDBX() mDatabase = DatabaseKDBX()
mDatabase.binaryCache.cacheDirectory = cacheDirectory
mDatabase.changeDuplicateId = fixDuplicateUUID mDatabase.changeDuplicateId = fixDuplicateUUID
@@ -140,26 +144,27 @@ class DatabaseInputKDBX(cacheDirectory: File)
assignMasterKey?.invoke() assignMasterKey?.invoke()
mDatabase.makeFinalKey(header.masterSeed) mDatabase.makeFinalKey(header.masterSeed)
progressTaskUpdater?.updateMessage(R.string.decrypting_db) stopKeyTimer()
startContentTimer(progressTaskUpdater)
val engine: CipherEngine val engine: CipherEngine
val cipher: Cipher val cipher: Cipher
try { try {
engine = CipherFactory.getInstance(mDatabase.dataCipher) engine = EncryptionAlgorithm.getFrom(mDatabase.cipherUuid).cipherEngine
mDatabase.setDataEngine(engine) mDatabase.setDataEngine(engine)
mDatabase.encryptionAlgorithm = engine.getPwEncryptionAlgorithm() mDatabase.encryptionAlgorithm = engine.getEncryptionAlgorithm()
cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV) cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV)
} catch (e: Exception) { } catch (e: Exception) {
throw InvalidAlgorithmDatabaseException(e) throw InvalidAlgorithmDatabaseException(e)
} }
val isPlain: InputStream val plainInputStream: InputStream
if (mDatabase.kdbxVersion.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
val decrypted = attachCipherStream(databaseInputStream, cipher) val dataDecrypted = CipherInputStream(databaseInputStream, cipher)
val dataDecrypted = LittleEndianDataInputStream(decrypted)
val storedStartBytes: ByteArray? val storedStartBytes: ByteArray?
try { try {
storedStartBytes = dataDecrypted.readBytes(32) storedStartBytes = dataDecrypted.readBytesLength(32)
if (storedStartBytes.size != 32) { if (storedStartBytes.size != 32) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
@@ -171,47 +176,57 @@ class DatabaseInputKDBX(cacheDirectory: File)
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
isPlain = HashedBlockInputStream(dataDecrypted) plainInputStream = HashedBlockInputStream(dataDecrypted)
} else { // KDBX 4 } else { // KDBX 4
val isData = LittleEndianDataInputStream(databaseInputStream) val storedHash = databaseInputStream.readBytesLength(32)
val storedHash = isData.readBytes(32) if (!storedHash.contentEquals(hashOfHeader)) {
if (!Arrays.equals(storedHash, hashOfHeader)) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException() val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException()
val headerHmac = DatabaseHeaderKDBX.computeHeaderHmac(pbHeader, hmacKey)
val storedHmac = isData.readBytes(32) val blockKey = HmacBlock.getHmacKey64(hmacKey, UnsignedLong.MAX_BYTES)
val hmac: Mac = HmacBlock.getHmacSha256(blockKey)
val headerHmac = hmac.doFinal(pbHeader)
val storedHmac = databaseInputStream.readBytesLength(32)
if (storedHmac.size != 32) { if (storedHmac.size != 32) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
// Mac doesn't match // Mac doesn't match
if (!Arrays.equals(headerHmac, storedHmac)) { if (!headerHmac.contentEquals(storedHmac)) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
val hmIs = HmacBlockInputStream(isData, true, hmacKey) val hmIs = HmacBlockInputStream(databaseInputStream, true, hmacKey)
isPlain = attachCipherStream(hmIs, cipher) plainInputStream = CipherInputStream(hmIs, cipher)
} }
val inputStreamXml: InputStream val inputStreamXml: InputStream = when (mDatabase.compressionAlgorithm) {
inputStreamXml = when (mDatabase.compressionAlgorithm) { CompressionAlgorithm.GZip -> GZIPInputStream(plainInputStream)
CompressionAlgorithm.GZip -> GZIPInputStream(isPlain) else -> plainInputStream
else -> isPlain
} }
if (mDatabase.kdbxVersion.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (!mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
loadInnerHeader(inputStreamXml, header) readInnerHeader(inputStreamXml, header)
} }
try { try {
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) randomStream = CrsAlgorithm.getCipher(header.innerRandomStream, header.innerRandomStreamKey)
} catch (e: Exception) { } catch (e: Exception) {
throw LoadDatabaseException(e) throw LoadDatabaseException(e)
} }
readDocumentStreamed(createPullParser(inputStreamXml)) val xmlPullParserFactory = XmlPullParserFactory.newInstance().apply {
isNamespaceAware = false
}
val xmlPullParser = xmlPullParserFactory.newPullParser().apply {
setInput(inputStreamXml, null)
}
readDocumentStreamed(xmlPullParser)
stopContentTimer()
} catch (e: LoadDatabaseException) { } catch (e: LoadDatabaseException) {
throw e throw e
@@ -231,63 +246,55 @@ class DatabaseInputKDBX(cacheDirectory: File)
return mDatabase return mDatabase
} }
private fun attachCipherStream(inputStream: InputStream, cipher: Cipher): InputStream {
return CipherInputStream(inputStream, cipher)
}
@Throws(IOException::class) @Throws(IOException::class)
private fun loadInnerHeader(inputStream: InputStream, header: DatabaseHeaderKDBX) { private fun readInnerHeader(dataInputStream: InputStream,
val lis = LittleEndianDataInputStream(inputStream) header: DatabaseHeaderKDBX) {
while (true) { var readStream = true
if (!readInnerHeader(lis, header)) break while (readStream) {
} val fieldId = dataInputStream.read().toByte()
}
@Throws(IOException::class) val size = dataInputStream.readBytes4ToUInt().toKotlinInt()
private fun readInnerHeader(dataInputStream: LittleEndianDataInputStream, if (size < 0) throw IOException("Corrupted file")
header: DatabaseHeaderKDBX): Boolean {
val fieldId = dataInputStream.read().toByte()
val size = dataInputStream.readUInt().toKotlinInt() var data = ByteArray(0)
if (size < 0) throw IOException("Corrupted file") try {
if (size > 0) {
var data = ByteArray(0) if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) {
if (size > 0) { data = dataInputStream.readBytesLength(size)
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) { }
// TODO OOM here }
data = dataInputStream.readBytes(size) } catch (e: Exception) {
// OOM only if corrupted file
throw IOException("Corrupted file")
} }
}
var result = true readStream = true
when (fieldId) { when (fieldId) {
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> {
result = false readStream = false
} }
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> {
header.setRandomStreamID(data) header.setRandomStreamID(data)
} }
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> {
header.innerRandomStreamKey = data header.innerRandomStreamKey = data
} }
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> { DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
// Read in a file // Read in a file
val protectedFlag = dataInputStream.read().toByte() == DatabaseHeaderKDBX.KdbxBinaryFlags.Protected val protectedFlag = dataInputStream.read().toByte() == DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
val byteLength = size - 1 val byteLength = size - 1
// No compression at this level // No compression at this level
val protectedBinary = mDatabase.buildNewAttachment(cacheDirectory, false, protectedFlag) val protectedBinary = mDatabase.buildNewAttachment(
val cipherKey = mDatabase.loadedCipherKey isRAMSufficient.invoke(byteLength.toLong()), false, protectedFlag)
?: throw IOException("Unable to retrieve cipher key to load binaries") protectedBinary.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
protectedBinary.getOutputDataStream(cipherKey).use { outputStream -> dataInputStream.readBytes(byteLength) { buffer ->
dataInputStream.readBytes(byteLength) { buffer -> outputStream.write(buffer)
outputStream.write(buffer) }
} }
} }
} }
} }
return result
} }
private enum class KdbContext { private enum class KdbContext {
@@ -703,12 +710,11 @@ class DatabaseInputKDBX(cacheDirectory: File)
} else if (ctx == KdbContext.CustomIcons && name.equals(DatabaseKDBXXML.ElemCustomIcons, ignoreCase = true)) { } else if (ctx == KdbContext.CustomIcons && name.equals(DatabaseKDBXXML.ElemCustomIcons, ignoreCase = true)) {
return KdbContext.Meta return KdbContext.Meta
} else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) { } else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) {
if (customIconID != DatabaseVersioned.UUID_ZERO && customIconData != null) { val iconData = customIconData
mDatabase.addCustomIcon(cacheDirectory, customIconID, customIconData!!.size) { _, binary -> if (customIconID != DatabaseVersioned.UUID_ZERO && iconData != null) {
mDatabase.loadedCipherKey?.let { cipherKey -> mDatabase.addCustomIcon(customIconID, isRAMSufficient.invoke(iconData.size.toLong())) { _, binary ->
binary?.getOutputDataStream(cipherKey)?.use { outputStream -> binary?.getOutputDataStream(mDatabase.binaryCache)?.use { outputStream ->
outputStream.write(customIconData) outputStream.write(iconData)
}
} }
} }
} }
@@ -784,7 +790,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
return KdbContext.Entry return KdbContext.Entry
} else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) { } else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) {
if (ctxBinaryName != null && ctxBinaryValue != null) { if (ctxBinaryName != null && ctxBinaryValue != null) {
ctxEntry?.putAttachment(Attachment(ctxBinaryName!!, ctxBinaryValue!!), mDatabase.binaryPool) ctxEntry?.putAttachment(Attachment(ctxBinaryName!!, ctxBinaryValue!!), mDatabase.attachmentPool)
} }
ctxBinaryName = null ctxBinaryName = null
ctxBinaryValue = null ctxBinaryValue = null
@@ -837,7 +843,13 @@ class DatabaseInputKDBX(cacheDirectory: File)
val sDate = readString(xpp) val sDate = readString(xpp)
var utcDate: Date? = null var utcDate: Date? = null
if (mDatabase.kdbxVersion.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
try {
utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate)
} catch (e: ParseException) {
// Catch with null test below
}
} else {
var buf = Base64.decode(sDate, BASE_64_FLAG) var buf = Base64.decode(sDate, BASE_64_FLAG)
if (buf.size != 8) { if (buf.size != 8) {
val buf8 = ByteArray(8) val buf8 = ByteArray(8)
@@ -847,14 +859,6 @@ class DatabaseInputKDBX(cacheDirectory: File)
val seconds = bytes64ToLong(buf) val seconds = bytes64ToLong(buf)
utcDate = DateKDBXUtil.convertKDBX4Time(seconds) utcDate = DateKDBXUtil.convertKDBX4Time(seconds)
} else {
try {
utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate)
} catch (e: ParseException) {
// Catch with null test below
}
} }
return utcDate ?: Date(0L) return utcDate ?: Date(0L)
@@ -978,11 +982,14 @@ class DatabaseInputKDBX(cacheDirectory: File)
xpp.next() // Consume end tag xpp.next() // Consume end tag
val id = Integer.parseInt(ref) val id = Integer.parseInt(ref)
// A ref is not necessarily an index in Database V3.1 // A ref is not necessarily an index in Database V3.1
var binaryRetrieve = mDatabase.binaryPool[id] var binaryRetrieve = mDatabase.attachmentPool[id]
// Create empty binary if not retrieved in pool // Create empty binary if not retrieved in pool
if (binaryRetrieve == null) { if (binaryRetrieve == null) {
binaryRetrieve = mDatabase.buildNewAttachment(cacheDirectory, binaryRetrieve = mDatabase.buildNewAttachment(
compression = false, protection = false, binaryPoolId = id) smallSize = false,
compression = false,
protection = false,
binaryPoolId = id)
} }
return binaryRetrieve return binaryRetrieve
} }
@@ -1018,17 +1025,16 @@ class DatabaseInputKDBX(cacheDirectory: File)
return null return null
// Build the new binary and compress // Build the new binary and compress
val binaryAttachment = mDatabase.buildNewAttachment(cacheDirectory, compressed, protected, binaryId) val binaryAttachment = mDatabase.buildNewAttachment(
val binaryCipherKey = mDatabase.loadedCipherKey isRAMSufficient.invoke(base64.length.toLong()), compressed, protected, binaryId)
?: throw IOException("Unable to retrieve cipher key to load binaries")
try { try {
binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream -> binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
outputStream.write(Base64.decode(base64, BASE_64_FLAG)) outputStream.write(Base64.decode(base64, BASE_64_FLAG))
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to read base 64 attachment", e) Log.e(TAG, "Unable to read base 64 attachment", e)
binaryAttachment.isCorrupted = true binaryAttachment.isCorrupted = true
binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream -> binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
outputStream.write(base64.toByteArray()) outputStream.write(base64.toByteArray())
} }
} }
@@ -1057,9 +1063,7 @@ class DatabaseInputKDBX(cacheDirectory: File)
val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected) val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected)
if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) { if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) {
Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data -> Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data ->
val plainText = ByteArray(data.size) return randomStream?.processBytes(data)
randomStream?.processBytes(data, 0, data.size, plainText, 0)
return plainText
} }
return ByteArray(0) return ByteArray(0)
} }
@@ -1083,17 +1087,6 @@ class DatabaseInputKDBX(cacheDirectory: File)
private val TAG = DatabaseInputKDBX::class.java.name private val TAG = DatabaseInputKDBX::class.java.name
private val DEFAULT_HISTORY_DAYS = UnsignedInt(365) private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
@Throws(XmlPullParserException::class)
private fun createPullParser(readerStream: InputStream): XmlPullParser {
val xmlPullParserFactory = XmlPullParserFactory.newInstance()
xmlPullParserFactory.isNamespaceAware = false
val xpp = xmlPullParserFactory.newPullParser()
xpp.setInput(readerStream, null)
return xpp
}
} }
} }

View File

@@ -19,8 +19,8 @@
*/ */
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.utils.uIntTo4Bytes
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.uIntTo4Bytes
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream

View File

@@ -19,30 +19,29 @@
*/ */
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.crypto.HmacBlock
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.utils.UnsignedLong import com.kunzisoft.keepass.stream.MacOutputStream
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.utils.*
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.security.DigestOutputStream import java.security.DigestOutputStream
import java.security.InvalidKeyException
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class) class DatabaseHeaderOutputKDBX @Throws(IOException::class)
constructor(private val databaseKDBX: DatabaseKDBX, constructor(private val databaseKDBX: DatabaseKDBX,
private val header: DatabaseHeaderKDBX, private val header: DatabaseHeaderKDBX,
outputStream: OutputStream) { outputStream: OutputStream) {
private val los: LittleEndianDataOutputStream
private val mos: MacOutputStream private val mos: MacOutputStream
private val dos: DigestOutputStream private val dos: DigestOutputStream
lateinit var headerHmac: ByteArray lateinit var headerHmac: ByteArray
@@ -51,14 +50,6 @@ constructor(private val databaseKDBX: DatabaseKDBX,
private set private set
init { init {
val md: MessageDigest
try {
md = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("SHA-256 not implemented here.", e)
}
try { try {
databaseKDBX.makeFinalKey(header.masterSeed) databaseKDBX.makeFinalKey(header.masterSeed)
} catch (e: IOException) { } catch (e: IOException) {
@@ -66,34 +57,26 @@ constructor(private val databaseKDBX: DatabaseKDBX,
} }
val hmacKey = databaseKDBX.hmacKey ?: throw DatabaseOutputException("HmacKey is not defined") val hmacKey = databaseKDBX.hmacKey ?: throw DatabaseOutputException("HmacKey is not defined")
val hmac: Mac val blockKey = HmacBlock.getHmacKey64(hmacKey, UnsignedLong.MAX_BYTES)
try { val hmac: Mac = HmacBlock.getHmacSha256(blockKey)
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, UnsignedLong.MAX_VALUE), "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException(e)
} catch (e: InvalidKeyException) {
throw DatabaseOutputException(e)
}
dos = DigestOutputStream(outputStream, md) val messageDigest: MessageDigest = HashManager.getHash256()
dos = DigestOutputStream(outputStream, messageDigest)
mos = MacOutputStream(dos, hmac) mos = MacOutputStream(dos, hmac)
los = LittleEndianDataOutputStream(mos)
} }
@Throws(IOException::class) @Throws(IOException::class)
fun output() { fun output() {
los.writeUInt(DatabaseHeader.PWM_DBSIG_1) mos.write4BytesUInt(DatabaseHeader.PWM_DBSIG_1)
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2) mos.write4BytesUInt(DatabaseHeaderKDBX.DBSIG_2)
los.writeUInt(header.version) mos.write4BytesUInt(header.version)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.cipherUuid))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm))) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (header.version.isBefore(FILE_VERSION_32_4)) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
} else { } else {
@@ -104,7 +87,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
} }
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (header.version.isBefore(FILE_VERSION_32_4)) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id))
@@ -112,14 +95,13 @@ constructor(private val databaseKDBX: DatabaseKDBX,
if (databaseKDBX.containsPublicCustomData()) { if (databaseKDBX.containsPublicCustomData()) {
val bos = ByteArrayOutputStream() val bos = ByteArrayOutputStream()
val los = LittleEndianDataOutputStream(bos) VariantDictionary.serialize(databaseKDBX.publicCustomData, bos)
VariantDictionary.serialize(databaseKDBX.publicCustomData, los)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray()) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray())
} }
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue)
los.flush() mos.flush()
hashOfHeader = dos.messageDigest.digest() hashOfHeader = dos.messageDigest.digest()
headerHmac = mos.mac headerHmac = mos.mac
} }
@@ -127,11 +109,11 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class) @Throws(IOException::class)
private fun writeHeaderField(fieldId: Byte, pbData: ByteArray?) { private fun writeHeaderField(fieldId: Byte, pbData: ByteArray?) {
// Write the field id // Write the field id
los.write(fieldId.toInt()) mos.write(fieldId.toInt())
if (pbData != null) { if (pbData != null) {
writeHeaderFieldSize(pbData.size) writeHeaderFieldSize(pbData.size)
los.write(pbData) mos.write(pbData)
} else { } else {
writeHeaderFieldSize(0) writeHeaderFieldSize(0)
} }
@@ -139,10 +121,10 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class) @Throws(IOException::class)
private fun writeHeaderFieldSize(size: Int) { private fun writeHeaderFieldSize(size: Int) {
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (header.version.isBefore(FILE_VERSION_32_4)) {
los.writeUShort(size) mos.write2BytesUShort(size)
} else { } else {
los.writeInt(size) mos.write4BytesUInt(UnsignedInt(size))
} }
} }

View File

@@ -19,16 +19,16 @@
*/ */
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.write2BytesUShort
import com.kunzisoft.keepass.utils.write4BytesUInt
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDB import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
@@ -37,8 +37,6 @@ import java.security.*
import java.util.* import java.util.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherOutputStream import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB, class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
outputStream: OutputStream) outputStream: OutputStream)
@@ -67,30 +65,21 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
val finalKey = getFinalKey(header) val finalKey = getFinalKey(header)
val cipher: Cipher val cipher: Cipher = try {
cipher = try { mDatabaseKDB.encryptionAlgorithm
when { .cipherEngine.getCipher(Cipher.ENCRYPT_MODE,
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael-> finalKey ?: ByteArray(0),
CipherFactory.getInstance("AES/CBC/PKCS5Padding") header.encryptionIV)
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish ->
CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING")
else ->
throw Exception()
}
} catch (e: Exception) { } catch (e: Exception) {
throw DatabaseOutputException("Algorithm not supported.", e) throw IOException("Algorithm not supported.", e)
} }
try { try {
cipher.init(Cipher.ENCRYPT_MODE,
SecretKeySpec(finalKey, "AES"),
IvParameterSpec(header.encryptionIV))
val cos = CipherOutputStream(mOutputStream, cipher) val cos = CipherOutputStream(mOutputStream, cipher)
val bos = BufferedOutputStream(cos) val bos = BufferedOutputStream(cos)
outputPlanGroupAndEntries(bos) outputPlanGroupAndEntries(bos)
bos.flush() bos.flush()
bos.close() bos.close()
} catch (e: InvalidKeyException) { } catch (e: InvalidKeyException) {
throw DatabaseOutputException("Invalid key", e) throw DatabaseOutputException("Invalid key", e)
} catch (e: InvalidAlgorithmParameterException) { } catch (e: InvalidAlgorithmParameterException) {
@@ -116,11 +105,11 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
header.signature2 = DatabaseHeaderKDB.DBSIG_2 header.signature2 = DatabaseHeaderKDB.DBSIG_2
header.flags = DatabaseHeaderKDB.FLAG_SHA2 header.flags = DatabaseHeaderKDB.FLAG_SHA2
when { when (mDatabaseKDB.encryptionAlgorithm) {
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> { EncryptionAlgorithm.AESRijndael -> {
header.flags = UnsignedInt(header.flags.toKotlinInt() or DatabaseHeaderKDB.FLAG_RIJNDAEL.toKotlinInt()) header.flags = UnsignedInt(header.flags.toKotlinInt() or DatabaseHeaderKDB.FLAG_RIJNDAEL.toKotlinInt())
} }
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> { EncryptionAlgorithm.Twofish -> {
header.flags = UnsignedInt(header.flags.toKotlinInt() or DatabaseHeaderKDB.FLAG_TWOFISH.toKotlinInt()) header.flags = UnsignedInt(header.flags.toKotlinInt() or DatabaseHeaderKDB.FLAG_TWOFISH.toKotlinInt())
} }
else -> throw DatabaseOutputException("Unsupported algorithm.") else -> throw DatabaseOutputException("Unsupported algorithm.")
@@ -133,26 +122,11 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
setIVs(header) setIVs(header)
// Content checksum
val messageDigest: MessageDigest?
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("SHA-256 not implemented here.", e)
}
// Header checksum // Header checksum
val headerDigest: MessageDigest val headerDigest: MessageDigest = HashManager.getHash256()
try {
headerDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("SHA-256 not implemented here.", e)
}
var nos = NullOutputStream()
val headerDos = DigestOutputStream(nos, headerDigest)
// Output header for the purpose of calculating the header checksum // Output header for the purpose of calculating the header checksum
val headerDos = DigestOutputStream(NullOutputStream(), headerDigest)
var pho = DatabaseHeaderOutputKDB(header, headerDos) var pho = DatabaseHeaderOutputKDB(header, headerDos)
try { try {
pho.outputStart() pho.outputStart()
@@ -165,9 +139,11 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
val headerHash = headerDigest.digest() val headerHash = headerDigest.digest()
headerHashBlock = getHeaderHashBuffer(headerHash) headerHashBlock = getHeaderHashBuffer(headerHash)
// Content checksum
val messageDigest: MessageDigest = HashManager.getHash256()
// Output database for the purpose of calculating the content checksum // Output database for the purpose of calculating the content checksum
nos = NullOutputStream() val dos = DigestOutputStream(NullOutputStream(), messageDigest)
val dos = DigestOutputStream(nos, messageDigest)
val bos = BufferedOutputStream(dos) val bos = BufferedOutputStream(dos)
try { try {
outputPlanGroupAndEntries(bos) outputPlanGroupAndEntries(bos)
@@ -177,7 +153,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
throw DatabaseOutputException("Failed to generate checksum.", e) throw DatabaseOutputException("Failed to generate checksum.", e)
} }
header.contentsHash = messageDigest!!.digest() header.contentsHash = messageDigest.digest()
// Output header for real output, containing content hash // Output header for real output, containing content hash
pho = DatabaseHeaderOutputKDB(header, outputStream) pho = DatabaseHeaderOutputKDB(header, outputStream)
@@ -195,17 +171,19 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
return header return header
} }
@Suppress("CAST_NEVER_SUCCEEDS") class NullOutputStream : OutputStream() {
override fun write(oneByte: Int) {}
}
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
fun outputPlanGroupAndEntries(outputStream: OutputStream) { fun outputPlanGroupAndEntries(outputStream: OutputStream) {
val littleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
// useHeaderHash // useHeaderHash
if (headerHashBlock != null) { if (headerHashBlock != null) {
try { try {
littleEndianDataOutputStream.writeUShort(0x0000) outputStream.write2BytesUShort(0x0000)
littleEndianDataOutputStream.writeInt(headerHashBlock!!.size) outputStream.write4BytesUInt(UnsignedInt(headerHashBlock!!.size))
littleEndianDataOutputStream.write(headerHashBlock!!) outputStream.write(headerHashBlock!!)
} catch (e: IOException) { } catch (e: IOException) {
throw DatabaseOutputException("Failed to output header hash.", e) throw DatabaseOutputException("Failed to output header hash.", e)
} }
@@ -217,7 +195,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
} }
// Entries // Entries
mDatabaseKDB.doForEachEntryInIndex { entry -> mDatabaseKDB.doForEachEntryInIndex { entry ->
EntryOutputKDB(entry, outputStream, mDatabaseKDB.loadedCipherKey).output() EntryOutputKDB(mDatabaseKDB, entry, outputStream).output()
} }
} }
@@ -252,24 +230,22 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun writeExtData(headerDigest: ByteArray, os: OutputStream) { private fun writeExtData(headerDigest: ByteArray, outputStream: OutputStream) {
val los = LittleEndianDataOutputStream(os) writeExtDataField(outputStream, 0x0001, headerDigest, headerDigest.size)
writeExtDataField(los, 0x0001, headerDigest, headerDigest.size)
val headerRandom = ByteArray(32) val headerRandom = ByteArray(32)
val rand = SecureRandom() val rand = SecureRandom()
rand.nextBytes(headerRandom) rand.nextBytes(headerRandom)
writeExtDataField(los, 0x0002, headerRandom, headerRandom.size) writeExtDataField(outputStream, 0x0002, headerRandom, headerRandom.size)
writeExtDataField(los, 0xFFFF, null, 0) writeExtDataField(outputStream, 0xFFFF, null, 0)
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun writeExtDataField(los: LittleEndianDataOutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) { private fun writeExtDataField(outputStream: OutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) {
los.writeUShort(fieldType) outputStream.write2BytesUShort(fieldType)
los.writeInt(fieldSize) outputStream.write4BytesUInt(UnsignedInt(fieldSize))
if (data != null) { if (data != null) {
los.write(data) outputStream.write(data)
} }
} }
} }

View File

@@ -22,12 +22,12 @@ package com.kunzisoft.keepass.database.file.output
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import android.util.Xml import android.util.Xml
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.encrypt.StreamCipher
import com.kunzisoft.keepass.crypto.CrsAlgorithm import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.element.DeletedObject import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
@@ -41,11 +41,12 @@ import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.exception.UnknownKDF
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.HashedBlockOutputStream
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.stream.HmacBlockOutputStream
import org.bouncycastle.crypto.StreamCipher import com.kunzisoft.keepass.utils.*
import org.joda.time.DateTime import org.joda.time.DateTime
import org.xmlpull.v1.XmlSerializer import org.xmlpull.v1.XmlSerializer
import java.io.IOException import java.io.IOException
@@ -75,15 +76,14 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
try { try {
try { try {
engine = CipherFactory.getInstance(mDatabaseKDBX.dataCipher) engine = EncryptionAlgorithm.getFrom(mDatabaseKDBX.cipherUuid).cipherEngine
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException("No such cipher", e) throw DatabaseOutputException("No such cipher", e)
} }
header = outputHeader(mOutputStream) header = outputHeader(mOutputStream)
val osPlain: OutputStream val osPlain: OutputStream = if (header!!.version.isBefore(FILE_VERSION_32_4)) {
osPlain = if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) {
val cos = attachStreamEncryptor(header!!, mOutputStream) val cos = attachStreamEncryptor(header!!, mOutputStream)
cos.write(header!!.streamStartBytes) cos.write(header!!.streamStartBytes)
@@ -95,19 +95,19 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!)) attachStreamEncryptor(header!!, HmacBlockOutputStream(mOutputStream, mDatabaseKDBX.hmacKey!!))
} }
val osXml: OutputStream val xmlOutputStream: OutputStream
try { try {
osXml = when(mDatabaseKDBX.compressionAlgorithm) { xmlOutputStream = when(mDatabaseKDBX.compressionAlgorithm) {
CompressionAlgorithm.GZip -> GZIPOutputStream(osPlain) CompressionAlgorithm.GZip -> GZIPOutputStream(osPlain)
else -> osPlain else -> osPlain
} }
if (header!!.version.toKotlinLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (!header!!.version.isBefore(FILE_VERSION_32_4)) {
outputInnerHeader(mDatabaseKDBX, header!!, osXml) outputInnerHeader(mDatabaseKDBX, header!!, xmlOutputStream)
} }
outputDatabase(osXml) outputDatabase(xmlOutputStream)
osXml.close() xmlOutputStream.close()
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
throw DatabaseOutputException(e) throw DatabaseOutputException(e)
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
@@ -122,45 +122,42 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IOException::class) @Throws(IOException::class)
private fun outputInnerHeader(database: DatabaseKDBX, private fun outputInnerHeader(database: DatabaseKDBX,
header: DatabaseHeaderKDBX, header: DatabaseHeaderKDBX,
outputStream: OutputStream) { dataOutputStream: OutputStream) {
val dataOutputStream = LittleEndianDataOutputStream(outputStream)
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID) dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID)
dataOutputStream.writeInt(4) dataOutputStream.write4BytesUInt(UnsignedInt(4))
if (header.innerRandomStream == null) if (header.innerRandomStream == null)
throw IOException("Can't write innerRandomStream") throw IOException("Can't write innerRandomStream")
dataOutputStream.writeUInt(header.innerRandomStream!!.id) dataOutputStream.write4BytesUInt(header.innerRandomStream!!.id)
val streamKeySize = header.innerRandomStreamKey.size val streamKeySize = header.innerRandomStreamKey.size
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey) dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey)
dataOutputStream.writeInt(streamKeySize) dataOutputStream.write4BytesUInt(UnsignedInt(streamKeySize))
dataOutputStream.write(header.innerRandomStreamKey) dataOutputStream.write(header.innerRandomStreamKey)
database.loadedCipherKey?.let { binaryCipherKey -> val binaryCache = database.binaryCache
database.binaryPool.doForEachOrderedBinaryWithoutDuplication { _, binary -> database.attachmentPool.doForEachOrderedBinaryWithoutDuplication { _, binary ->
// Force decompression to add binary in header // Force decompression to add binary in header
binary.decompress(binaryCipherKey) binary.decompress(binaryCache)
// Write type binary // Write type binary
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary) dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
// Write size // Write size
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1)) dataOutputStream.write4BytesUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
// Write protected flag // Write protected flag
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
if (binary.isProtected) { if (binary.isProtected) {
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
} }
dataOutputStream.writeByte(flag) dataOutputStream.writeByte(flag)
binary.getInputDataStream(binaryCipherKey).use { inputStream -> binary.getInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
dataOutputStream.write(buffer) dataOutputStream.write(buffer)
}
} }
} }
} ?: Log.e(TAG, "Unable to retrieve cipher key to write head binaries") }
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader) dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
dataOutputStream.writeInt(0) dataOutputStream.write4BytesUInt(UnsignedInt(0))
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -270,7 +267,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID) writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
// Seem to work properly if always in meta // Seem to work properly if always in meta
if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) if (header!!.version.isBefore(FILE_VERSION_32_4))
writeMetaBinaries() writeMetaBinaries()
writeCustomData(mDatabaseKDBX.customData) writeCustomData(mDatabaseKDBX.customData)
@@ -282,8 +279,6 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
private fun attachStreamEncryptor(header: DatabaseHeaderKDBX, os: OutputStream): CipherOutputStream { private fun attachStreamEncryptor(header: DatabaseHeaderKDBX, os: OutputStream): CipherOutputStream {
val cipher: Cipher val cipher: Cipher
try { try {
//mDatabaseKDBX.makeFinalKey(header.masterSeed, mDatabaseKDBX.kdfParameters);
cipher = engine!!.getCipher(Cipher.ENCRYPT_MODE, mDatabaseKDBX.finalKey!!, header.encryptionIV) cipher = engine!!.getCipher(Cipher.ENCRYPT_MODE, mDatabaseKDBX.finalKey!!, header.encryptionIV)
} catch (e: Exception) { } catch (e: Exception) {
throw DatabaseOutputException("Invalid algorithm.", e) throw DatabaseOutputException("Invalid algorithm.", e)
@@ -314,7 +309,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
Log.e(TAG, "Unable to retrieve header", unknownKDF) Log.e(TAG, "Unable to retrieve header", unknownKDF)
} }
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (header.version.isBefore(FILE_VERSION_32_4)) {
header.innerRandomStream = CrsAlgorithm.Salsa20 header.innerRandomStream = CrsAlgorithm.Salsa20
header.innerRandomStreamKey = ByteArray(32) header.innerRandomStreamKey = ByteArray(32)
} else { } else {
@@ -324,12 +319,12 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
random.nextBytes(header.innerRandomStreamKey) random.nextBytes(header.innerRandomStreamKey)
try { try {
randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) randomStream = CrsAlgorithm.getCipher(header.innerRandomStream, header.innerRandomStreamKey)
} catch (e: Exception) { } catch (e: Exception) {
throw DatabaseOutputException(e) throw DatabaseOutputException(e)
} }
if (header.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (header.version.isBefore(FILE_VERSION_32_4)) {
random.nextBytes(header.streamStartBytes) random.nextBytes(header.streamStartBytes)
} }
@@ -338,21 +333,20 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
override fun outputHeader(outputStream: OutputStream): DatabaseHeaderKDBX { override fun outputHeader(outputStream: OutputStream): DatabaseHeaderKDBX {
val header = DatabaseHeaderKDBX(mDatabaseKDBX)
setIVs(header)
val pho = DatabaseHeaderOutputKDBX(mDatabaseKDBX, header, outputStream)
try { try {
val header = DatabaseHeaderKDBX(mDatabaseKDBX)
setIVs(header)
val pho = DatabaseHeaderOutputKDBX(mDatabaseKDBX, header, outputStream)
pho.output() pho.output()
hashOfHeader = pho.hashOfHeader
headerHmac = pho.headerHmac
return header
} catch (e: IOException) { } catch (e: IOException) {
throw DatabaseOutputException("Failed to output the header.", e) throw DatabaseOutputException("Failed to output the header.", e)
} }
hashOfHeader = pho.hashOfHeader
headerHmac = pho.headerHmac
return header
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -429,7 +423,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: Date) { private fun writeObject(name: String, value: Date) {
if (header!!.version.toKotlinLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toKotlinLong()) { if (header!!.version.isBefore(FILE_VERSION_32_4)) {
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value)) writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
} else { } else {
val dt = DateTime(value) val dt = DateTime(value)
@@ -494,31 +488,30 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
// With kdbx4, don't use this method because binaries are in header file // With kdbx4, don't use this method because binaries are in header file
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() { private fun writeMetaBinaries() {
mDatabaseKDBX.loadedCipherKey?.let { binaryCipherKey -> 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) val binaryCache = mDatabaseKDBX.binaryCache
mDatabaseKDBX.binaryPool.doForEachOrderedBinaryWithoutDuplication { index, binary -> mDatabaseKDBX.attachmentPool.doForEachOrderedBinaryWithoutDuplication { 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())
if (binary.getSize() > 0) { if (binary.getSize() > 0) {
if (binary.isCompressed) { if (binary.isCompressed) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue) xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
} }
try { try {
// Write the XML // Write the XML
binary.getInputDataStream(binaryCipherKey).use { inputStream -> binary.getInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer -> inputStream.readAllBytes { buffer ->
xml.text(String(Base64.encode(buffer, BASE_64_FLAG))) xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Unable to write binary", e) Log.e(TAG, "Unable to write binary", e)
}
} }
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
} }
xml.endTag(null, DatabaseKDBXXML.ElemBinaries) xml.endTag(null, DatabaseKDBXXML.ElemBinary)
} ?: Log.e(TAG, "Unable to retrieve cipher key to write binaries") }
xml.endTag(null, DatabaseKDBXXML.ElemBinaries)
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
@@ -584,12 +577,8 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
if (protect) { if (protect) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue) xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
val data = value.toString().toByteArray() val data = value.toString().toByteArray()
val dataLength = data.size val encoded = randomStream?.processBytes(data) ?: ByteArray(0)
if (data.isNotEmpty()) { xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
val encoded = ByteArray(dataLength)
randomStream!!.processBytes(data, 0, dataLength, encoded, 0)
xml.text(String(Base64.encode(encoded, BASE_64_FLAG)))
}
} else { } else {
xml.text(value.toString()) xml.text(value.toString())
} }
@@ -612,7 +601,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
private fun writeEntryBinaries(binaries: LinkedHashMap<String, Int>) { private fun writeEntryBinaries(binaries: LinkedHashMap<String, Int>) {
for ((label, poolId) in binaries) { for ((label, poolId) in binaries) {
// Retrieve the right index with the poolId, don't use ref because of header in DatabaseV4 // Retrieve the right index with the poolId, don't use ref because of header in DatabaseV4
mDatabaseKDBX.binaryPool.getBinaryIndexFromKey(poolId)?.toString()?.let { indexString -> mDatabaseKDBX.attachmentPool.getBinaryIndexFromKey(poolId)?.toString()?.let { indexString ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary) xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.startTag(null, DatabaseKDBXXML.ElemKey) xml.startTag(null, DatabaseKDBXXML.ElemKey)
xml.text(safeXmlString(label)) xml.text(safeXmlString(label))
@@ -699,39 +688,38 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeCustomIconList() { private fun writeCustomIconList() {
mDatabaseKDBX.loadedCipherKey?.let { cipherKey -> var firstElement = true
var firstElement = true val binaryCache = mDatabaseKDBX.binaryCache
mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary -> mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary ->
if (binary.dataExists()) { if (binary.dataExists()) {
// Write the parent tag // Write the parent tag
if (firstElement) { if (firstElement) {
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons) xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
firstElement = false firstElement = false
}
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid)
var customImageData = ByteArray(0)
try {
binary.getInputDataStream(cipherKey).use { inputStream ->
customImageData = inputStream.readBytes()
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write custom icon", e)
} finally {
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
String(Base64.encode(customImageData, BASE_64_FLAG)))
}
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
} }
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid)
var customImageData = ByteArray(0)
try {
binary.getInputDataStream(binaryCache).use { inputStream ->
customImageData = inputStream.readBytes()
}
} catch (e: Exception) {
Log.e(TAG, "Unable to write custom icon", e)
} finally {
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
String(Base64.encode(customImageData, BASE_64_FLAG)))
}
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
} }
// Close the parent tag }
if (!firstElement) { // Close the parent tag
xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons) if (!firstElement) {
} xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons)
} ?: Log.e(TAG, "Unable to retrieve cipher key to write custom icons") }
} }
private fun safeXmlString(text: String): String { private fun safeXmlString(text: String): String {

View File

@@ -19,13 +19,10 @@
*/ */
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import android.util.Log import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.nio.charset.Charset import java.nio.charset.Charset
@@ -33,9 +30,9 @@ import java.nio.charset.Charset
/** /**
* Output the GroupKDB to the stream * Output the GroupKDB to the stream
*/ */
class EntryOutputKDB(private val mEntry: EntryKDB, class EntryOutputKDB(private val mDatabase: DatabaseKDB,
private val mOutputStream: OutputStream, private val mEntry: EntryKDB,
private val mCipherKey: Database.LoadedKey?) { private val mOutputStream: OutputStream) {
//NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int //NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
@@ -59,15 +56,15 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
// Title // Title
//byte[] title = mEntry.title.getBytes("UTF-8"); //byte[] title = mEntry.title.getBytes("UTF-8");
mOutputStream.write(TITLE_FIELD_TYPE) mOutputStream.write(TITLE_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.title) writeStringToStream(mOutputStream, mEntry.title)
// URL // URL
mOutputStream.write(URL_FIELD_TYPE) mOutputStream.write(URL_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.url) writeStringToStream(mOutputStream, mEntry.url)
// Username // Username
mOutputStream.write(USERNAME_FIELD_TYPE) mOutputStream.write(USERNAME_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.username) writeStringToStream(mOutputStream, mEntry.username)
// Password // Password
mOutputStream.write(PASSWORD_FIELD_TYPE) mOutputStream.write(PASSWORD_FIELD_TYPE)
@@ -75,7 +72,7 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
// Additional // Additional
mOutputStream.write(ADDITIONAL_FIELD_TYPE) mOutputStream.write(ADDITIONAL_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.notes) writeStringToStream(mOutputStream, mEntry.notes)
// Create date // Create date
writeDate(CREATE_FIELD_TYPE, dateTo5Bytes(mEntry.creationTime.date)) writeDate(CREATE_FIELD_TYPE, dateTo5Bytes(mEntry.creationTime.date))
@@ -91,25 +88,23 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
// Binary description // Binary description
mOutputStream.write(BINARY_DESC_FIELD_TYPE) mOutputStream.write(BINARY_DESC_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.binaryDescription) writeStringToStream(mOutputStream, mEntry.binaryDescription)
// Binary // Binary
mCipherKey?.let { cipherKey -> mOutputStream.write(BINARY_DATA_FIELD_TYPE)
mOutputStream.write(BINARY_DATA_FIELD_TYPE) val binaryData = mEntry.getBinary(mDatabase.attachmentPool)
val binaryData = mEntry.binaryData val binaryDataLength = binaryData?.getSize() ?: 0L
val binaryDataLength = binaryData?.getSize() ?: 0L // Write data length
// Write data length mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength)))
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength))) // Write data
// Write data if (binaryDataLength > 0) {
if (binaryDataLength > 0) { binaryData?.getInputDataStream(mDatabase.binaryCache).use { inputStream ->
binaryData?.getInputDataStream(cipherKey).use { inputStream -> inputStream?.readAllBytes { buffer ->
inputStream?.readAllBytes { buffer -> mOutputStream.write(buffer)
mOutputStream.write(buffer)
}
inputStream?.close()
} }
inputStream?.close()
} }
} ?: Log.e(TAG, "Unable to retrieve cipher key to write entry binary") }
// End // End
mOutputStream.write(END_FIELD_TYPE) mOutputStream.write(END_FIELD_TYPE)

View File

@@ -19,13 +19,13 @@
*/ */
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.dateTo5Bytes
import com.kunzisoft.keepass.utils.uIntTo4Bytes
import com.kunzisoft.keepass.utils.uShortTo2Bytes
import com.kunzisoft.keepass.utils.writeStringToStream
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.stream.dateTo5Bytes
import com.kunzisoft.keepass.stream.uIntTo4Bytes
import com.kunzisoft.keepass.stream.uShortTo2Bytes
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
@@ -47,7 +47,7 @@ class GroupOutputKDB(private val mGroup: GroupKDB,
// Name // Name
mOutputStream.write(NAME_FIELD_TYPE) mOutputStream.write(NAME_FIELD_TYPE)
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mGroup.title) writeStringToStream(mOutputStream, mGroup.title)
// Create date // Create date
mOutputStream.write(CREATE_FIELD_TYPE) mOutputStream.write(CREATE_FIELD_TYPE)

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.search;
import java.util.UUID; import java.util.UUID;
import static com.kunzisoft.keepass.stream.StreamBytesUtilsKt.uuidTo16Bytes; import static com.kunzisoft.keepass.utils.StreamBytesUtilsKt.uuidTo16Bytes;
public class UuidUtil { public class UuidUtil {

View File

@@ -33,8 +33,8 @@ import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.database.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.icon.IconImageCustom import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageDraw import com.kunzisoft.keepass.database.element.icon.IconImageDraw
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -48,7 +48,7 @@ import kotlin.collections.HashMap
/** /**
* Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint * Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint
*/ */
class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedKey?, class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?,
private val retrieveCustomIconBinary : (iconId: UUID) -> BinaryData?) { private val retrieveCustomIconBinary : (iconId: UUID) -> BinaryData?) {
/** customIconMap /** customIconMap
@@ -69,7 +69,8 @@ class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedK
private fun getIconSuperDrawable(context: Context, iconDraw: IconImageDraw, width: Int, tintColor: Int = Color.WHITE): SuperDrawable { private fun getIconSuperDrawable(context: Context, iconDraw: IconImageDraw, width: Int, tintColor: Int = Color.WHITE): SuperDrawable {
val icon = iconDraw.getIconImageToDraw() val icon = iconDraw.getIconImageToDraw()
val customIconBinary = retrieveCustomIconBinary(icon.custom.uuid) val customIconBinary = retrieveCustomIconBinary(icon.custom.uuid)
if (customIconBinary != null && customIconBinary.dataExists()) { val binaryCache = retrieveBinaryCache()
if (binaryCache != null && customIconBinary != null && customIconBinary.dataExists()) {
getIconDrawable(context.resources, icon.custom, customIconBinary)?.let { getIconDrawable(context.resources, icon.custom, customIconBinary)?.let {
return SuperDrawable(it) return SuperDrawable(it)
} }
@@ -87,13 +88,13 @@ class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedK
*/ */
private fun getIconDrawable(resources: Resources, icon: IconImageCustom, iconCustomBinary: BinaryData?): Drawable? { private fun getIconDrawable(resources: Resources, icon: IconImageCustom, iconCustomBinary: BinaryData?): Drawable? {
val patternIcon = PatternIcon(resources) val patternIcon = PatternIcon(resources)
val cipherKey = retrieveCipherKey() val binaryManager = retrieveBinaryCache()
if (cipherKey != null) { if (binaryManager != null) {
val draw: Drawable? = customIconMap[icon.uuid]?.get() val draw: Drawable? = customIconMap[icon.uuid]?.get()
if (draw == null) { if (draw == null) {
iconCustomBinary?.let { binaryFile -> iconCustomBinary?.let { binaryFile ->
try { try {
var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(cipherKey)) var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(binaryManager))
bitmap?.let { bitmapIcon -> bitmap?.let { bitmapIcon ->
bitmap = resize(bitmapIcon, patternIcon) bitmap = resize(bitmapIcon, patternIcon)
val createdDraw = BitmapDrawable(resources, bitmap) val createdDraw = BitmapDrawable(resources, bitmap)

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.model
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.database.BinaryByte import com.kunzisoft.keepass.database.element.binary.BinaryByte
import com.kunzisoft.keepass.utils.readEnum import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum import com.kunzisoft.keepass.utils.writeEnum

View File

@@ -29,6 +29,7 @@ import android.util.Log
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
@@ -48,6 +49,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
private val mainScope = CoroutineScope(Dispatchers.Main) private val mainScope = CoroutineScope(Dispatchers.Main)
private var mDatabase: Database? = Database.getInstance()
override fun retrieveChannelId(): String { override fun retrieveChannelId(): String {
return CHANNEL_ATTACHMENT_ID return CHANNEL_ATTACHMENT_ID
} }
@@ -285,11 +288,14 @@ class AttachmentFileNotificationService: LockNotificationService() {
// Add action to the list on start // Add action to the list on start
attachmentNotificationList.add(attachmentNotification) attachmentNotificationList.add(attachmentNotification)
mainScope.launch { mDatabase?.let { database ->
AttachmentFileAction(attachmentNotification, mainScope.launch {
contentResolver).apply { AttachmentFileAction(attachmentNotification,
listener = attachmentFileActionListener database,
}.executeAction() contentResolver).apply {
listener = attachmentFileActionListener
}.executeAction()
}
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -313,6 +319,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
private class AttachmentFileAction( private class AttachmentFileAction(
private val attachmentNotification: AttachmentNotification, private val attachmentNotification: AttachmentNotification,
private val database: Database,
private val contentResolver: ContentResolver) { private val contentResolver: ContentResolver) {
private val updateMinFrequency = 1000 private val updateMinFrequency = 1000
@@ -345,6 +352,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
when (streamDirection) { when (streamDirection) {
StreamDirection.UPLOAD -> { StreamDirection.UPLOAD -> {
BinaryDatabaseManager.uploadToDatabase( BinaryDatabaseManager.uploadToDatabase(
database,
attachmentNotification.uri, attachmentNotification.uri,
attachment.binaryData, attachment.binaryData,
contentResolver, contentResolver,
@@ -358,6 +366,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
} }
StreamDirection.DOWNLOAD -> { StreamDirection.DOWNLOAD -> {
BinaryDatabaseManager.downloadFromDatabase( BinaryDatabaseManager.downloadFromDatabase(
database,
attachmentNotification.uri, attachmentNotification.uri,
attachment.binaryData, attachment.binaryData,
contentResolver, contentResolver,

View File

@@ -31,10 +31,10 @@ import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService import com.kunzisoft.keepass.services.DatabaseTaskNotificationService
import com.kunzisoft.keepass.settings.preference.* import com.kunzisoft.keepass.settings.preference.*
import com.kunzisoft.keepass.settings.preferencedialogfragment.* import com.kunzisoft.keepass.settings.preferencedialogfragment.*
@@ -217,12 +217,12 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
if (mDatabase.loaded) { if (mDatabase.loaded) {
// Encryption Algorithm // Encryption Algorithm
mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply { mEncryptionAlgorithmPref = findPreference<DialogListExplanationPreference>(getString(R.string.encryption_algorithm_key))?.apply {
summary = mDatabase.getEncryptionAlgorithmName(resources) summary = mDatabase.getEncryptionAlgorithmName()
} }
// Key derivation function // Key derivation function
mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply { mKeyDerivationPref = findPreference<DialogListExplanationPreference>(getString(R.string.key_derivation_function_key))?.apply {
summary = mDatabase.getKeyDerivationName(resources) summary = mDatabase.getKeyDerivationName()
} }
// Round encryption // Round encryption
@@ -398,7 +398,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
mDatabase.encryptionAlgorithm = oldEncryption mDatabase.encryptionAlgorithm = oldEncryption
oldEncryption oldEncryption
} }
mEncryptionAlgorithmPref?.summary = algorithmToShow.getName(resources) mEncryptionAlgorithmPref?.summary = algorithmToShow.toString()
} }
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK -> { DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK -> {
val oldKeyDerivationEngine = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as KdfEngine val oldKeyDerivationEngine = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as KdfEngine
@@ -410,7 +410,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
mDatabase.kdfEngine = oldKeyDerivationEngine mDatabase.kdfEngine = oldKeyDerivationEngine
oldKeyDerivationEngine oldKeyDerivationEngine
} }
mKeyDerivationPref?.summary = kdfEngineToShow.getName(resources) mKeyDerivationPref?.summary = kdfEngineToShow.toString()
mRoundPref?.summary = kdfEngineToShow.defaultKeyRounds.toString() mRoundPref?.summary = kdfEngineToShow.defaultKeyRounds.toString()
// Disable memory and parallelism if not available // Disable memory and parallelism if not available

View File

@@ -207,7 +207,7 @@ object PreferencesUtil {
return try { return try {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
(prefs.getString(context.getString(R.string.app_timeout_key), (prefs.getString(context.getString(R.string.app_timeout_key),
context.getString(R.string.clipboard_timeout_default)) ?: "300000").toLong() context.getString(R.string.timeout_default)) ?: "300000").toLong()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
TimeoutHelper.DEFAULT_TIMEOUT TimeoutHelper.DEFAULT_TIMEOUT
} }

View File

@@ -23,7 +23,7 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import androidx.preference.DialogPreference import androidx.preference.DialogPreference
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
open class InputKdfNumberPreference @JvmOverloads constructor(context: Context, open class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,

View File

@@ -20,11 +20,11 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
@@ -58,15 +58,13 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat
if (positiveResult) { if (positiveResult) {
database?.let { database -> database?.let { database ->
if (database.allowEncryptionAlgorithmModification) { if (algorithmSelected != null) {
if (algorithmSelected != null) { val newAlgorithm = algorithmSelected
val newAlgorithm = algorithmSelected val oldAlgorithm = database.encryptionAlgorithm
val oldAlgorithm = database.encryptionAlgorithm database.encryptionAlgorithm = newAlgorithm
database.encryptionAlgorithm = newAlgorithm
if (oldAlgorithm != null && newAlgorithm != null) if (oldAlgorithm != null && newAlgorithm != null)
mProgressDatabaseTaskProvider?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm, mDatabaseAutoSaveEnable) mProgressDatabaseTaskProvider?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm, mDatabaseAutoSaveEnable)
}
} }
} }
} }

View File

@@ -20,12 +20,12 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment package com.kunzisoft.keepass.settings.preferencedialogfragment
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.preference.Preference import androidx.preference.Preference
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import android.view.View
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter
class DatabaseKeyDerivationPreferenceDialogFragmentCompat class DatabaseKeyDerivationPreferenceDialogFragmentCompat

View File

@@ -20,18 +20,15 @@
package com.kunzisoft.keepass.settings.preferencedialogfragment.adapter package com.kunzisoft.keepass.settings.preferencedialogfragment.adapter
import android.content.Context import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.RadioButton import android.widget.RadioButton
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.ObjectNameResource import java.util.*
import java.util.ArrayList class ListRadioItemAdapter<T>(private val context: Context)
class ListRadioItemAdapter<T : ObjectNameResource>(private val context: Context)
: RecyclerView.Adapter<ListRadioItemAdapter.ListRadioViewHolder>() { : RecyclerView.Adapter<ListRadioItemAdapter.ListRadioViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
@@ -48,7 +45,7 @@ class ListRadioItemAdapter<T : ObjectNameResource>(private val context: Context)
override fun onBindViewHolder(holder: ListRadioViewHolder, position: Int) { override fun onBindViewHolder(holder: ListRadioViewHolder, position: Int) {
val item = this.radioItemList[position] val item = this.radioItemList[position]
holder.radioButton.text = item.getName(context.resources) holder.radioButton.text = item.toString()
holder.radioButton.isChecked = radioItemUsed != null && radioItemUsed == item holder.radioButton.isChecked = radioItemUsed != null && radioItemUsed == item
holder.radioButton.setOnClickListener(OnItemClickListener(item)) holder.radioButton.setOnClickListener(OnItemClickListener(item))
} }

View File

@@ -1,247 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kunzisoft.keepass.stream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.NullCipher;
/**
* This class wraps an {@code InputStream} and a cipher so that {@code read()}
* methods return data that are read from the underlying {@code InputStream} and
* processed by the cipher.
* <p>
* The cipher must be initialized for the requested operation before being used
* by a {@code BetterCipherInputStream}. For example, if a cipher initialized for
* decryption is used with a {@code BetterCipherInputStream}, the {@code
* BetterCipherInputStream} tries to read the data an decrypt them before returning.
*/
public class BetterCipherInputStream extends FilterInputStream {
private final Cipher cipher;
private static final int I_DEFAULT_BUFFER_SIZE = 8 * 1024;
private final byte[] i_buffer;
private int index; // index of the bytes to return from o_buffer
private byte[] o_buffer;
private boolean finished;
/**
* Creates a new {@code BetterCipherInputStream} instance for an {@code
* InputStream} and a cipher.
*
* @param is
* the input stream to read data from.
* @param c
* the cipher to process the data with.
*/
public BetterCipherInputStream(InputStream is, Cipher c) {
this(is, c, I_DEFAULT_BUFFER_SIZE);
}
/**
* Creates a new {@code BetterCipherInputStream} instance for an {@code
* InputStream} and a cipher.
*
* @param is
* the input stream to read data from.
* @param c
* the cipher to process the data with.
* @param bufferSize
* size to buffer output from the cipher
*/
public BetterCipherInputStream(InputStream is, Cipher c, int bufferSize) {
super(is);
this.cipher = c;
i_buffer = new byte[bufferSize];
}
/**
* Creates a new {@code BetterCipherInputStream} instance for an {@code
* InputStream} without a cipher.
* <p>
* A {@code NullCipher} is created and used to process the data.
*
* @param is
* the input stream to read data from.
*/
protected BetterCipherInputStream(InputStream is) {
this(is, new NullCipher());
}
/**
* Reads the next byte from this cipher input stream.
*
* @return the next byte, or {@code -1} if the end of the stream is reached.
* @throws IOException
* if an error occurs.
*/
@Override
public int read() throws IOException {
if (finished) {
return ((o_buffer == null) || (index == o_buffer.length))
? -1
: o_buffer[index++] & 0xFF;
}
if ((o_buffer != null) && (index < o_buffer.length)) {
return o_buffer[index++] & 0xFF;
}
index = 0;
o_buffer = null;
int num_read;
while (o_buffer == null) {
if ((num_read = in.read(i_buffer)) == -1) {
try {
o_buffer = cipher.doFinal();
} catch (Exception e) {
throw new IOException(e.getMessage());
}
finished = true;
break;
}
o_buffer = cipher.update(i_buffer, 0, num_read);
}
return read();
}
/**
* Reads the next {@code b.length} bytes from this input stream into buffer
* {@code b}.
*
* @param b
* the buffer to be filled with data.
* @return the number of bytes filled into buffer {@code b}, or {@code -1}
* if the end of the stream is reached.
* @throws IOException
* if an error occurs.
*/
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* Reads the next {@code len} bytes from this input stream into buffer
* {@code b} starting at offset {@code off}.
* <p>
* if {@code b} is {@code null}, the next {@code len} bytes are read and
* discarded.
*
* @param b
* the buffer to be filled with data.
* @param off
* the offset to start in the buffer.
* @param len
* the maximum number of bytes to read.
* @return the number of bytes filled into buffer {@code b}, or {@code -1}
* of the of the stream is reached.
* @throws IOException
* if an error occurs.
* @throws NullPointerException
* if the underlying input stream is {@code null}.
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (in == null) {
throw new NullPointerException("Underlying input stream is null");
}
int read_b;
int i;
for (i=0; i<len; i++) {
if ((read_b = read()) == -1) {
return (i == 0) ? -1 : i;
}
if (b != null) {
b[off+i] = (byte) read_b;
}
}
return i;
}
/**
* Skips up to n bytes from this input stream.
* <p>
* The number of bytes skipped depends on the result of a call to
* {@link BetterCipherInputStream#available() available}. The smaller of n and the
* result are the number of bytes being skipped.
*
* @param n
* the number of bytes that should be skipped.
* @return the number of bytes actually skipped.
* @throws IOException
* if an error occurs
*/
@Override
public long skip(long n) throws IOException {
long i = 0;
int available = available();
if (available < n) {
n = available;
}
while ((i < n) && (read() != -1)) {
i++;
}
return i;
}
/**
* Returns the number of bytes available without blocking.
*
* @return the number of bytes available, currently zero.
* @throws IOException
* if an error occurs
*/
@Override
public int available() throws IOException {
return 0;
}
/**
* Closes this {@code BetterCipherInputStream}, also closes the underlying input
* stream and call {@code doFinal} on the cipher object.
*
* @throws IOException
* if an error occurs.
*/
@Override
public void close() throws IOException {
in.close();
try {
cipher.doFinal();
} catch (GeneralSecurityException ignore) {
//do like RI does
}
}
/**
* Returns whether this input stream supports {@code mark} and
* {@code reset}, which it does not.
*
* @return false, since this input stream does not support {@code mark} and
* {@code reset}.
*/
@Override
public boolean markSupported() {
return false;
}
}

View File

@@ -19,17 +19,17 @@
*/ */
package com.kunzisoft.keepass.stream package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.readBytes4ToUInt
import com.kunzisoft.keepass.utils.readBytesLength
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.* import java.util.*
class HashedBlockInputStream(inputStream: InputStream) : InputStream() { class HashedBlockInputStream(private val baseStream: InputStream) : InputStream() {
private val baseStream: LittleEndianDataInputStream = LittleEndianDataInputStream(inputStream)
private var bufferPos = 0 private var bufferPos = 0
private var buffer: ByteArray = ByteArray(0) private var buffer: ByteArray = ByteArray(0)
private var bufferIndex: Long = 0 private var bufferIndex: Long = 0
@@ -53,7 +53,6 @@ class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
if (!readHashedBlock()) { if (!readHashedBlock()) {
return length - remaining return length - remaining
} }
} }
// Copy from buffer out // Copy from buffer out
@@ -80,13 +79,13 @@ class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
bufferPos = 0 bufferPos = 0
val index = baseStream.readUInt() val index = baseStream.readBytes4ToUInt()
if (index.toKotlinLong() != bufferIndex) { if (index.toKotlinLong() != bufferIndex) {
throw IOException("Invalid data format") throw IOException("Invalid data format")
} }
bufferIndex++ bufferIndex++
val storedHash = baseStream.readBytes(32) val storedHash = baseStream.readBytesLength(32)
if (storedHash.size != HASH_SIZE) { if (storedHash.size != HASH_SIZE) {
throw IOException("Invalid data format") throw IOException("Invalid data format")
} }
@@ -104,24 +103,17 @@ class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
return false return false
} }
buffer = baseStream.readBytes(bufferSize) buffer = baseStream.readBytesLength(bufferSize)
if (buffer.size != bufferSize) { if (buffer.size != bufferSize) {
throw IOException("Invalid data format") throw IOException("Invalid data format")
} }
val messageDigest: MessageDigest val computedHash = HashManager.hashSha256(buffer)
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here.")
}
val computedHash = messageDigest.digest(buffer)
if (computedHash.size != HASH_SIZE) { if (computedHash.size != HASH_SIZE) {
throw IOException("Hash wrong size") throw IOException("Hash wrong size")
} }
if (!Arrays.equals(storedHash, computedHash)) { if (!storedHash.contentEquals(computedHash)) {
throw IOException("Hashes didn't match.") throw IOException("Hashes didn't match.")
} }
@@ -141,7 +133,7 @@ class HashedBlockInputStream(inputStream: InputStream) : InputStream() {
if (!readHashedBlock()) return -1 if (!readHashedBlock()) return -1
} }
val output = UnsignedInt.fromKotlinByte(buffer[bufferPos]).toKotlinInt() val output = buffer[bufferPos].toInt() and 0xFF
bufferPos++ bufferPos++
return output return output

View File

@@ -19,16 +19,18 @@
*/ */
package com.kunzisoft.keepass.stream package com.kunzisoft.keepass.stream
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.write4BytesUInt
import com.kunzisoft.keepass.utils.write8BytesLong
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import kotlin.math.min import kotlin.math.min
class HashedBlockOutputStream : OutputStream { class HashedBlockOutputStream : OutputStream {
private lateinit var baseStream: LittleEndianDataOutputStream private lateinit var baseStream: OutputStream
private lateinit var buffer: ByteArray private lateinit var buffer: ByteArray
private var bufferPos = 0 private var bufferPos = 0
private var bufferIndex: Long = 0 private var bufferIndex: Long = 0
@@ -47,7 +49,7 @@ class HashedBlockOutputStream : OutputStream {
} }
private fun init(os: OutputStream, bufferSize: Int) { private fun init(os: OutputStream, bufferSize: Int) {
baseStream = LittleEndianDataOutputStream(os) baseStream = os
buffer = ByteArray(bufferSize) buffer = ByteArray(bufferSize)
} }
@@ -99,31 +101,24 @@ class HashedBlockOutputStream : OutputStream {
@Throws(IOException::class) @Throws(IOException::class)
private fun writeHashedBlock() { private fun writeHashedBlock() {
baseStream.writeUInt(UnsignedInt.fromKotlinLong(bufferIndex)) baseStream.write4BytesUInt(UnsignedInt.fromKotlinLong(bufferIndex))
bufferIndex++ bufferIndex++
if (bufferPos > 0) { if (bufferPos > 0) {
val messageDigest: MessageDigest val messageDigest: MessageDigest = HashManager.getHash256()
try {
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("SHA-256 not implemented here.")
}
val hash: ByteArray
messageDigest.update(buffer, 0, bufferPos) messageDigest.update(buffer, 0, bufferPos)
hash = messageDigest.digest() val hash: ByteArray = messageDigest.digest()
baseStream.write(hash) baseStream.write(hash)
} else { } else {
// Write 32-bits of zeros // Write 32-bits of zeros
baseStream.writeLong(0L) baseStream.write8BytesLong(0L)
baseStream.writeLong(0L) baseStream.write8BytesLong(0L)
baseStream.writeLong(0L) baseStream.write8BytesLong(0L)
baseStream.writeLong(0L) baseStream.write8BytesLong(0L)
} }
baseStream.writeInt(bufferPos) baseStream.write4BytesUInt(UnsignedInt(bufferPos))
if (bufferPos > 0) { if (bufferPos > 0) {
baseStream.write(buffer, 0, bufferPos) baseStream.write(buffer, 0, bufferPos)

View File

@@ -19,21 +19,21 @@
*/ */
package com.kunzisoft.keepass.stream package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.bytes4ToUInt
import com.kunzisoft.keepass.utils.readBytesLength
import com.kunzisoft.keepass.utils.uLongTo8Bytes
import com.kunzisoft.keepass.database.crypto.HmacBlock
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.* import java.util.*
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean, private val key: ByteArray) : InputStream() { class HmacBlockInputStream(private val baseStream: InputStream, private val verify: Boolean, private val key: ByteArray) : InputStream() {
private val baseStream: LittleEndianDataInputStream = LittleEndianDataInputStream(baseStream)
private var buffer: ByteArray = ByteArray(0) private var buffer: ByteArray = ByteArray(0)
private var bufferPos = 0 private var bufferPos = 0
private var blockIndex: Long = 0 private var blockIndex = UnsignedLong(0L)
private var endOfStream = false private var endOfStream = false
@Throws(IOException::class) @Throws(IOException::class)
@@ -44,7 +44,7 @@ class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean,
if (!readSafeBlock()) return -1 if (!readSafeBlock()) return -1
} }
val output = UnsignedInt.fromKotlinByte(buffer[bufferPos]).toKotlinInt() val output = (buffer[bufferPos]).toInt() and 0xFF
bufferPos++ bufferPos++
return output return output
@@ -67,8 +67,6 @@ class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean,
} }
val copy = (buffer.size - bufferPos).coerceAtMost(remaining) val copy = (buffer.size - bufferPos).coerceAtMost(remaining)
assert(copy > 0)
System.arraycopy(buffer, bufferPos, outBuffer, offset, copy) System.arraycopy(buffer, bufferPos, outBuffer, offset, copy)
offset += copy offset += copy
bufferPos += copy bufferPos += copy
@@ -88,35 +86,25 @@ class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean,
private fun readSafeBlock(): Boolean { private fun readSafeBlock(): Boolean {
if (endOfStream) return false if (endOfStream) return false
val storedHmac = baseStream.readBytes(32) val storedHmac = baseStream.readBytesLength(32)
if (storedHmac.size != 32) { if (storedHmac.size != 32) {
throw IOException("File corrupted") throw IOException("File corrupted")
} }
val pbBlockIndex = longTo8Bytes(blockIndex) val pbBlockSize = baseStream.readBytesLength(4)
val pbBlockSize = baseStream.readBytes(4)
if (pbBlockSize.size != 4) { if (pbBlockSize.size != 4) {
throw IOException("File corrupted") throw IOException("File corrupted")
} }
val blockSize = bytes4ToUInt(pbBlockSize) val blockSize = bytes4ToUInt(pbBlockSize)
bufferPos = 0 bufferPos = 0
buffer = baseStream.readBytes(blockSize.toKotlinInt()) buffer = baseStream.readBytesLength(blockSize.toKotlinInt())
if (verify) { if (verify) {
val cmpHmac: ByteArray val pbBlockIndex = uLongTo8Bytes(blockIndex)
val blockKey = HmacBlockStream.getHmacKey64(key, blockIndex)
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("Invalid Hmac")
} catch (e: InvalidKeyException) {
throw IOException("Invalid Hmac")
}
val blockKey = HmacBlock.getHmacKey64(key, pbBlockIndex)
val hmac: Mac = HmacBlock.getHmacSha256(blockKey)
hmac.update(pbBlockIndex) hmac.update(pbBlockIndex)
hmac.update(pbBlockSize) hmac.update(pbBlockSize)
@@ -124,16 +112,16 @@ class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean,
hmac.update(buffer) hmac.update(buffer)
} }
cmpHmac = hmac.doFinal() val cmpHmac: ByteArray = hmac.doFinal()
Arrays.fill(blockKey, 0.toByte()) Arrays.fill(blockKey, 0.toByte())
if (!Arrays.equals(cmpHmac, storedHmac)) { if (!cmpHmac.contentEquals(storedHmac)) {
throw IOException("Invalid Hmac") throw IOException("Invalid Hmac")
} }
} }
blockIndex++ blockIndex.plusOne()
if (blockSize.toKotlinLong() == 0L) { if (blockSize.toKotlinLong() == 0L) {
endOfStream = true endOfStream = true

View File

@@ -20,23 +20,21 @@
package com.kunzisoft.keepass.stream package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.utils.uIntTo4Bytes
import com.kunzisoft.keepass.utils.uLongTo8Bytes
import com.kunzisoft.keepass.database.crypto.HmacBlock
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class HmacBlockOutputStream(outputStream: OutputStream, class HmacBlockOutputStream(private val baseStream: OutputStream,
private val key: ByteArray) private val key: ByteArray)
: OutputStream() { : OutputStream() {
private val baseStream: LittleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
private val buffer = ByteArray(DEFAULT_BUFFER_SIZE) private val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
private var bufferPos = 0 private var bufferPos = 0
private var blockIndex: Long = 0 private var blockIndex = UnsignedLong(0L)
@Throws(IOException::class) @Throws(IOException::class)
override fun close() { override fun close() {
@@ -87,23 +85,11 @@ class HmacBlockOutputStream(outputStream: OutputStream,
@Throws(IOException::class) @Throws(IOException::class)
private fun writeSafeBlock() { private fun writeSafeBlock() {
val bufBlockIndex = longTo8Bytes(blockIndex) val bufBlockIndex = uLongTo8Bytes(blockIndex)
val blockSizeBuf = uIntTo4Bytes(UnsignedInt(bufferPos)) val blockSizeBuf = uIntTo4Bytes(UnsignedInt(bufferPos))
val blockHmac: ByteArray val blockKey = HmacBlock.getHmacKey64(key, bufBlockIndex)
val blockKey = HmacBlockStream.getHmacKey64(key, blockIndex) val hmac: Mac = HmacBlock.getHmacSha256(blockKey)
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("Invalid Hmac")
} catch (e: InvalidKeyException) {
throw IOException("Invalid HMAC")
}
hmac.update(bufBlockIndex) hmac.update(bufBlockIndex)
hmac.update(blockSizeBuf) hmac.update(blockSizeBuf)
@@ -111,8 +97,7 @@ class HmacBlockOutputStream(outputStream: OutputStream,
hmac.update(buffer, 0, bufferPos) hmac.update(buffer, 0, bufferPos)
} }
blockHmac = hmac.doFinal() val blockHmac: ByteArray = hmac.doFinal()
baseStream.write(blockHmac) baseStream.write(blockHmac)
baseStream.write(blockSizeBuf) baseStream.write(blockSizeBuf)
@@ -120,7 +105,7 @@ class HmacBlockOutputStream(outputStream: OutputStream,
baseStream.write(buffer, 0, bufferPos) baseStream.write(buffer, 0, bufferPos)
} }
blockIndex++ blockIndex.plusOne()
bufferPos = 0 bufferPos = 0
} }
} }

View File

@@ -1,114 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.InputStream
/**
* Little endian version of the DataInputStream
*/
class LittleEndianDataInputStream(private val baseStream: InputStream) : InputStream() {
/**
* Read a 32-bit value and return it as a long, so that it can
* be interpreted as an unsigned integer.
*/
@Throws(IOException::class)
fun readUInt(): UnsignedInt {
return baseStream.readBytes4ToUInt()
}
@Throws(IOException::class)
fun readUShort(): Int {
val buf = ByteArray(2)
if (baseStream.read(buf, 0, 2) != 2)
throw IOException("Unable to read UShort value")
return bytes2ToUShort(buf)
}
@Throws(IOException::class)
override fun available(): Int {
return baseStream.available()
}
@Throws(IOException::class)
override fun close() {
baseStream.close()
}
override fun mark(readlimit: Int) {
baseStream.mark(readlimit)
}
override fun markSupported(): Boolean {
return baseStream.markSupported()
}
@Throws(IOException::class)
override fun read(): Int {
return baseStream.read()
}
@Throws(IOException::class)
override fun read(b: ByteArray, offset: Int, length: Int): Int {
return baseStream.read(b, offset, length)
}
@Throws(IOException::class)
override fun read(b: ByteArray): Int {
return baseStream.read(b)
}
@Synchronized
@Throws(IOException::class)
override fun reset() {
baseStream.reset()
}
@Throws(IOException::class)
override fun skip(n: Long): Long {
return baseStream.skip(n)
}
@Throws(IOException::class)
fun readBytes(length: Int): ByteArray {
// TODO Exception max length < buffer size
val buf = ByteArray(length)
var count = 0
while (count < length) {
val read = read(buf, count, length - count)
// Reached end
if (read == -1) {
// Stop early
val early = ByteArray(count)
System.arraycopy(buf, 0, early, 0, count)
return early
}
count += read
}
return buf
}
}

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2019 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.stream
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.OutputStream
/**
* Little Endian version of the DataOutputStream
* @author bpellin
*/
class LittleEndianDataOutputStream(private val baseStream: OutputStream) : OutputStream() {
@Throws(IOException::class)
fun writeUInt(uInt: UnsignedInt) {
baseStream.write(uIntTo4Bytes(uInt))
}
@Throws(IOException::class)
override fun close() {
baseStream.close()
}
@Throws(IOException::class)
override fun flush() {
baseStream.flush()
}
@Throws(IOException::class)
override fun write(buffer: ByteArray, offset: Int, count: Int) {
baseStream.write(buffer, offset, count)
}
@Throws(IOException::class)
override fun write(buffer: ByteArray) {
baseStream.write(buffer)
}
@Throws(IOException::class)
override fun write(oneByte: Int) {
baseStream.write(oneByte)
}
@Throws(IOException::class)
fun writeByte(byte: Byte) {
baseStream.write(byte.toInt())
}
@Throws(IOException::class)
fun writeLong(value: Long) {
baseStream.write(longTo8Bytes(value))
}
@Throws(IOException::class)
fun writeInt(value: Int) {
baseStream.write(uIntTo4Bytes(UnsignedInt(value)))
}
@Throws(IOException::class)
fun writeUShort(value: Int) {
baseStream.write(uShortTo2Bytes(value))
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.stream
import java.io.IOException
import java.io.OutputStream
class NullOutputStream : OutputStream() {
@Throws(IOException::class)
override fun close() {
super.close()
}
@Throws(IOException::class)
override fun flush() {
super.flush()
}
@Throws(IOException::class)
override fun write(buffer: ByteArray, offset: Int, count: Int) {
super.write(buffer, offset, count)
}
@Throws(IOException::class)
override fun write(buffer: ByteArray) {
super.write(buffer)
}
@Throws(IOException::class)
override fun write(oneByte: Int) {
}
}

View File

@@ -5,9 +5,10 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.stream.readAllBytes import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@@ -21,41 +22,42 @@ import kotlin.math.pow
object BinaryDatabaseManager { object BinaryDatabaseManager {
fun downloadFromDatabase(attachmentToUploadUri: Uri, fun downloadFromDatabase(database: Database,
attachmentToUploadUri: Uri,
binaryData: BinaryData, binaryData: BinaryData,
contentResolver: ContentResolver, contentResolver: ContentResolver,
update: ((percent: Int)->Unit)? = null, update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false }, canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) { bufferSize: Int = DEFAULT_BUFFER_SIZE) {
UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream -> UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream ->
downloadFromDatabase(outputStream, binaryData, update, canceled, bufferSize) downloadFromDatabase(database.binaryCache, outputStream, binaryData, update, canceled, bufferSize)
} }
} }
private fun downloadFromDatabase(outputStream: OutputStream, private fun downloadFromDatabase(binaryCache: BinaryCache,
outputStream: OutputStream,
binaryData: BinaryData, binaryData: BinaryData,
update: ((percent: Int)->Unit)? = null, update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false }, canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) { bufferSize: Int = DEFAULT_BUFFER_SIZE) {
val fileSize = binaryData.getSize() val fileSize = binaryData.getSize()
var dataDownloaded = 0L var dataDownloaded = 0L
Database.getInstance().loadedCipherKey?.let { binaryCipherKey -> binaryData.getUnGzipInputDataStream(binaryCache).use { inputStream ->
binaryData.getUnGzipInputDataStream(binaryCipherKey).use { inputStream -> inputStream.readAllBytes(bufferSize, canceled) { buffer ->
inputStream.readAllBytes(bufferSize, canceled) { buffer -> outputStream.write(buffer)
outputStream.write(buffer) dataDownloaded += buffer.size
dataDownloaded += buffer.size try {
try { val percentDownload = (100 * dataDownloaded / fileSize).toInt()
val percentDownload = (100 * dataDownloaded / fileSize).toInt() update?.invoke(percentDownload)
update?.invoke(percentDownload) } catch (e: Exception) {
} catch (e: Exception) { Log.w(TAG, "Unable to call update callback during download", e)
Log.w(TAG, "Unable to call update callback during download", e)
}
} }
} }
} }
} }
fun uploadToDatabase(attachmentFromDownloadUri: Uri, fun uploadToDatabase(database: Database,
attachmentFromDownloadUri: Uri,
binaryData: BinaryData, binaryData: BinaryData,
contentResolver: ContentResolver, contentResolver: ContentResolver,
update: ((percent: Int)->Unit)? = null, update: ((percent: Int)->Unit)? = null,
@@ -63,34 +65,34 @@ object BinaryDatabaseManager {
bufferSize: Int = DEFAULT_BUFFER_SIZE) { bufferSize: Int = DEFAULT_BUFFER_SIZE) {
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0 val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream -> UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream ->
uploadToDatabase(inputStream, fileSize, binaryData, update, canceled, bufferSize) uploadToDatabase(database.binaryCache, inputStream, fileSize, binaryData, update, canceled, bufferSize)
} }
} }
private fun uploadToDatabase(inputStream: InputStream, private fun uploadToDatabase(binaryCache: BinaryCache,
inputStream: InputStream,
fileSize: Long, fileSize: Long,
binaryData: BinaryData, binaryData: BinaryData,
update: ((percent: Int)->Unit)? = null, update: ((percent: Int)->Unit)? = null,
canceled: ()-> Boolean = { false }, canceled: ()-> Boolean = { false },
bufferSize: Int = DEFAULT_BUFFER_SIZE) { bufferSize: Int = DEFAULT_BUFFER_SIZE) {
var dataUploaded = 0L var dataUploaded = 0L
Database.getInstance().loadedCipherKey?.let { binaryCipherKey -> binaryData.getGzipOutputDataStream(binaryCache).use { outputStream ->
binaryData.getGzipOutputDataStream(binaryCipherKey).use { outputStream -> inputStream.readAllBytes(bufferSize, canceled) { buffer ->
inputStream.readAllBytes(bufferSize, canceled) { buffer -> outputStream.write(buffer)
outputStream.write(buffer) dataUploaded += buffer.size
dataUploaded += buffer.size try {
try { val percentDownload = (100 * dataUploaded / fileSize).toInt()
val percentDownload = (100 * dataUploaded / fileSize).toInt() update?.invoke(percentDownload)
update?.invoke(percentDownload) } catch (e: Exception) {
} catch (e: Exception) { Log.w(TAG, "Unable to call update callback during upload", e)
Log.w(TAG, "Unable to call update callback during upload", e)
}
} }
} }
} }
} }
fun resizeBitmapAndStoreDataInBinaryFile(contentResolver: ContentResolver, fun resizeBitmapAndStoreDataInBinaryFile(contentResolver: ContentResolver,
database: Database,
bitmapUri: Uri?, bitmapUri: Uri?,
binaryData: BinaryData?) { binaryData: BinaryData?) {
try { try {
@@ -103,6 +105,7 @@ object BinaryDatabaseManager {
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray() val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
val byteArrayInputStream = ByteArrayInputStream(bitmapData) val byteArrayInputStream = ByteArrayInputStream(bitmapData)
uploadToDatabase( uploadToDatabase(
database.binaryCache,
byteArrayInputStream, byteArrayInputStream,
bitmapData.size.toLong(), bitmapData.size.toLong(),
binaryData binaryData
@@ -118,7 +121,6 @@ object BinaryDatabaseManager {
/** /**
* reduces the size of the image * reduces the size of the image
* @param image
* @param maxSize * @param maxSize
* @return * @return
*/ */
@@ -136,20 +138,18 @@ object BinaryDatabaseManager {
return Bitmap.createScaledBitmap(this, width, height, true) return Bitmap.createScaledBitmap(this, width, height, true)
} }
fun loadBitmap(binaryData: BinaryData, fun loadBitmap(database: Database,
binaryCipherKey: Database.LoadedKey?, binaryData: BinaryData,
maxWidth: Int, maxWidth: Int,
actionOnFinish: (Bitmap?) -> Unit) { actionOnFinish: (Bitmap?) -> Unit) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val asyncResult: Deferred<Bitmap?> = async { val asyncResult: Deferred<Bitmap?> = async {
runCatching { runCatching {
binaryCipherKey?.let { binaryKey -> val bitmap: Bitmap? = decodeSampledBitmap(binaryData,
val bitmap: Bitmap? = decodeSampledBitmap(binaryData, database.binaryCache,
binaryKey, maxWidth)
maxWidth) bitmap
bitmap
}
}.getOrNull() }.getOrNull()
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@@ -160,13 +160,13 @@ object BinaryDatabaseManager {
} }
private fun decodeSampledBitmap(binaryData: BinaryData, private fun decodeSampledBitmap(binaryData: BinaryData,
binaryCipherKey: Database.LoadedKey, binaryCache: BinaryCache,
maxWidth: Int): Bitmap? { maxWidth: Int): Bitmap? {
// First decode with inJustDecodeBounds=true to check dimensions // First decode with inJustDecodeBounds=true to check dimensions
return BitmapFactory.Options().run { return BitmapFactory.Options().run {
try { try {
inJustDecodeBounds = true inJustDecodeBounds = true
binaryData.getUnGzipInputDataStream(binaryCipherKey).use { binaryData.getUnGzipInputDataStream(binaryCache).use {
BitmapFactory.decodeStream(it, null, this) BitmapFactory.decodeStream(it, null, this)
} }
// Calculate inSampleSize // Calculate inSampleSize
@@ -178,7 +178,7 @@ object BinaryDatabaseManager {
// Decode bitmap with inSampleSize set // Decode bitmap with inSampleSize set
inJustDecodeBounds = false inJustDecodeBounds = false
binaryData.getUnGzipInputDataStream(binaryCipherKey).use { binaryData.getUnGzipInputDataStream(binaryCache).use {
BitmapFactory.decodeStream(it, null, this) BitmapFactory.decodeStream(it, null, this)
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -17,13 +17,12 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package com.kunzisoft.keepass.stream package com.kunzisoft.keepass.utils
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils.bytesToString
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
import java.nio.charset.Charset
import java.util.* import java.util.*
/** /**
@@ -94,7 +93,7 @@ fun InputStream.readBytes2ToUShort(): Int {
} }
@Throws(IOException::class) @Throws(IOException::class)
fun InputStream.readBytes5ToDate(): DateInstant { fun InputStream.readBytes5ToDate(): Date {
return bytes5ToDate(readBytesLength(5)) return bytes5ToDate(readBytesLength(5))
} }
@@ -118,6 +117,36 @@ fun InputStream.readBytesLength(length: Int): ByteArray {
return buf return buf
} }
@Throws(IOException::class)
fun OutputStream.write4BytesUInt(value: UnsignedInt) {
this.write(uIntTo4Bytes(value))
}
@Throws(IOException::class)
fun OutputStream.writeBooleanByte(value: Boolean) {
this.writeByte(if (value) 1.toByte() else 0.toByte())
}
@Throws(IOException::class)
fun OutputStream.writeByte(byte: Byte) {
this.write(byte.toInt())
}
@Throws(IOException::class)
fun OutputStream.write8BytesLong(value: Long) {
this.write(longTo8Bytes(value))
}
@Throws(IOException::class)
fun OutputStream.write8BytesLong(value: UnsignedLong) {
this.write(uLongTo8Bytes(value))
}
@Throws(IOException::class)
fun OutputStream.write2BytesUShort(value: Int) {
this.write(uShortTo2Bytes(value))
}
/** /**
* Read an unsigned 16-bit value. * Read an unsigned 16-bit value.
*/ */
@@ -126,6 +155,20 @@ fun bytes2ToUShort(buf: ByteArray): Int {
+ (buf[1].toInt() and 0xFF shl 8)) + (buf[1].toInt() and 0xFF shl 8))
} }
/**
* Read a 64 bit to unsigned long
*/
fun bytes64ToULong(buf: ByteArray): UnsignedLong {
return UnsignedLong((buf[0].toLong() and 0xFF)
+ (buf[1].toLong() and 0xFF shl 8)
+ (buf[2].toLong() and 0xFF shl 16)
+ (buf[3].toLong() and 0xFF shl 24)
+ (buf[4].toLong() and 0xFF shl 32)
+ (buf[5].toLong() and 0xFF shl 40)
+ (buf[6].toLong() and 0xFF shl 48)
+ (buf[7].toLong() and 0xFF shl 56))
}
/** /**
* Read a 64 bit long * Read a 64 bit long
*/ */
@@ -168,17 +211,17 @@ fun bytes16ToUuid(buf: ByteArray): UUID {
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked * Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
* to a java.util.Date instance. * to a java.util.Date instance.
*/ */
fun bytes5ToDate(buf: ByteArray, calendar: Calendar = Calendar.getInstance()): DateInstant { fun bytes5ToDate(buf: ByteArray, calendar: Calendar = Calendar.getInstance()): Date {
val dateSize = 5 val dateSize = 5
val cDate = ByteArray(dateSize) val cDate = ByteArray(dateSize)
System.arraycopy(buf, 0, cDate, 0, dateSize) System.arraycopy(buf, 0, cDate, 0, dateSize)
val readOffset = 0 val readOffset = 0
val dw1 = UnsignedInt.fromKotlinByte(cDate[readOffset]).toKotlinInt() val dw1 = cDate[readOffset].toInt() and 0xFF
val dw2 = UnsignedInt.fromKotlinByte(cDate[readOffset + 1]).toKotlinInt() val dw2 = cDate[readOffset + 1].toInt() and 0xFF
val dw3 = UnsignedInt.fromKotlinByte(cDate[readOffset + 2]).toKotlinInt() val dw3 = cDate[readOffset + 2].toInt() and 0xFF
val dw4 = UnsignedInt.fromKotlinByte(cDate[readOffset + 3]).toKotlinInt() val dw4 = cDate[readOffset + 3].toInt() and 0xFF
val dw5 = UnsignedInt.fromKotlinByte(cDate[readOffset + 4]).toKotlinInt() val dw5 = cDate[readOffset + 4].toInt() and 0xFF
// Unpack 5 byte structure to date and time // Unpack 5 byte structure to date and time
val year = dw1 shl 6 or (dw2 shr 2) val year = dw1 shl 6 or (dw2 shr 2)
@@ -193,7 +236,18 @@ fun bytes5ToDate(buf: ByteArray, calendar: Calendar = Calendar.getInstance()): D
// File format is a 1 based day, java Calendar uses a 1 based day // File format is a 1 based day, java Calendar uses a 1 based day
calendar.set(year, month - 1, day, hour, minute, second) calendar.set(year, month - 1, day, hour, minute, second)
return DateInstant(calendar.time) return calendar.time
}
/**
* Write an unsigned 16-bit value
*/
fun uShortTo2Bytes(value: Int): ByteArray {
val buf = ByteArray(2)
buf[0] = (value and 0x00FF).toByte()
buf[1] = (value and 0xFF00 shr 8).toByte()
return buf
} }
/** /**
@@ -207,14 +261,8 @@ fun uIntTo4Bytes(value: UnsignedInt): ByteArray {
return buf return buf
} }
/** fun uLongTo8Bytes(value: UnsignedLong): ByteArray {
* Write an unsigned 16-bit value return longTo8Bytes(value.toKotlinLong())
*/
fun uShortTo2Bytes(value: Int): ByteArray {
val buf = ByteArray(2)
buf[0] = (value and 0x00FF).toByte()
buf[1] = (value and 0xFF00 shr 8).toByte()
return buf
} }
fun longTo8Bytes(value: Long): ByteArray { fun longTo8Bytes(value: Long): ByteArray {
@@ -258,3 +306,48 @@ fun dateTo5Bytes(date: Date, calendar: Calendar = Calendar.getInstance()): ByteA
return buf return buf
} }
private val defaultCharset = Charset.forName("UTF-8")
private val CRLFbuf = byteArrayOf(0x0D, 0x0A)
private val CRLF = String(CRLFbuf)
private val SEP = System.getProperty("line.separator")
private val REPLACE = SEP != CRLF
fun bytesToString(buf: ByteArray, replaceCRLF: Boolean = true): String {
// length of null-terminated string (i.e. distance to null) within a byte buffer.
var len = 0
while (buf[len].toInt() != 0) {
len++
}
// Get string
var jstring = String(buf, 0, len, defaultCharset)
if (replaceCRLF && REPLACE) {
jstring = jstring.replace(CRLF, SEP!!)
}
return jstring
}
@Throws(IOException::class)
fun writeStringToStream(outputStream: OutputStream, string: String?): Int {
var str = string
if (str == null) {
// Write out a null character
outputStream.write(uIntTo4Bytes(UnsignedInt(1)))
outputStream.write(0x00)
return 0
}
if (REPLACE) {
str = str.replace(SEP!!, CRLF)
}
val initial = str.toByteArray(defaultCharset)
val length = initial.size + 1
outputStream.write(uIntTo4Bytes(UnsignedInt(length)))
outputStream.write(initial)
outputStream.write(0x00)
return length
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
import com.kunzisoft.keepass.stream.uIntTo4Bytes
import java.io.IOException
import java.io.OutputStream
import java.nio.charset.Charset
/**
* Tools for slicing and dicing Java and KeePass data types.
*/
object StringDatabaseKDBUtils {
private val defaultCharset = Charset.forName("UTF-8")
private val CRLFbuf = byteArrayOf(0x0D, 0x0A)
private val CRLF = String(CRLFbuf)
private val SEP = System.getProperty("line.separator")
private val REPLACE = SEP != CRLF
fun bytesToString(buf: ByteArray, replaceCRLF: Boolean = true): String {
// length of null-terminated string (i.e. distance to null) within a byte buffer.
var len = 0
while (buf[len].toInt() != 0) {
len++
}
// Get string
var jstring = String(buf, 0, len, defaultCharset)
if (replaceCRLF && REPLACE) {
jstring = jstring.replace(CRLF, SEP!!)
}
return jstring
}
@Throws(IOException::class)
fun writeStringToStream(outputStream: OutputStream, string: String?): Int {
var str = string
if (str == null) {
// Write out a null character
outputStream.write(uIntTo4Bytes(UnsignedInt(1)))
outputStream.write(0x00)
return 0
}
if (REPLACE) {
str = str.replace(SEP!!, CRLF)
}
val initial = str.toByteArray(defaultCharset)
val length = initial.size + 1
outputStream.write(uIntTo4Bytes(UnsignedInt(length)))
outputStream.write(initial)
outputStream.write(0x00)
return length
}
}

View File

@@ -19,6 +19,8 @@
*/ */
package com.kunzisoft.keepass.utils package com.kunzisoft.keepass.utils
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
class UnsignedInt(private var unsignedValue: Int) { class UnsignedInt(private var unsignedValue: Int) {
constructor(unsignedValue: UnsignedInt) : this(unsignedValue.toKotlinInt()) constructor(unsignedValue: UnsignedInt) : this(unsignedValue.toKotlinInt())
@@ -44,6 +46,11 @@ class UnsignedInt(private var unsignedValue: Int) {
return (unsignedValue and 0xFF).toByte() return (unsignedValue and 0xFF).toByte()
} }
fun isBefore(value: UnsignedInt): Boolean {
return toKotlinLong() < value.toKotlinLong()
}
override fun toString():String { override fun toString():String {
return toKotlinLong().toString() return toKotlinLong().toString()
} }
@@ -69,13 +76,6 @@ class UnsignedInt(private var unsignedValue: Int) {
val MAX_VALUE = UnsignedInt(UINT_MAX_VALUE.toInt()) val MAX_VALUE = UnsignedInt(UINT_MAX_VALUE.toInt())
/**
* Convert a byte to an unsigned byte
*/
fun fromKotlinByte(value: Byte): UnsignedInt {
return UnsignedInt(value.toInt() and 0xFF)
}
@Throws(NumberFormatException::class) @Throws(NumberFormatException::class)
fun fromKotlinLong(value: Long): UnsignedInt { fun fromKotlinLong(value: Long): UnsignedInt {
if (value > UINT_MAX_VALUE) if (value > UINT_MAX_VALUE)

View File

@@ -19,10 +19,12 @@
*/ */
package com.kunzisoft.keepass.utils package com.kunzisoft.keepass.utils
class UnsignedLong(private var unsignedValue: Long) { class UnsignedLong(value: Long) {
private var unsignedValue: Long = value
/** /**
* Convert an unsigned Integer to Long * Convert an unsigned Long to Kotlin Long
*/ */
fun toKotlinLong(): Long { fun toKotlinLong(): Long {
return unsignedValue return unsignedValue
@@ -43,7 +45,15 @@ class UnsignedLong(private var unsignedValue: Long) {
return unsignedValue.hashCode() return unsignedValue.hashCode()
} }
fun plusOne() {
if (unsignedValue >= 0L)
unsignedValue++
else
unsignedValue--
}
companion object { companion object {
const val MAX_VALUE: Long = -1 private const val MAX_VALUE: Long = -1
val MAX_BYTES = longTo8Bytes(MAX_VALUE)
} }
} }

View File

@@ -322,8 +322,8 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
* ------------- * -------------
*/ */
fun setAttachmentCipherKey(cipherKey: Database.LoadedKey?) { fun setAttachmentCipherKey(database: Database?) {
attachmentsAdapter.binaryCipherKey = cipherKey attachmentsAdapter.database = database
} }
private fun showAttachments(show: Boolean) { private fun showAttachments(show: Boolean) {

View File

@@ -1,3 +0,0 @@
openssl-0.9.8l
aes-src-29-04-09.zip
sha2-07-01-07.zip

View File

@@ -23,8 +23,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/background_repeat"
android:backgroundTint="?android:attr/textColor"
tools:targetApi="o"> tools:targetApi="o">
<com.kunzisoft.keepass.view.SpecialModeView <com.kunzisoft.keepass.view.SpecialModeView
@@ -38,14 +36,15 @@
android:id="@+id/activity_password_coordinator_layout" android:id="@+id/activity_password_coordinator_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:background="@drawable/background_repeat"
android:backgroundTint="?android:attr/textColor"
app:layout_constraintTop_toBottomOf="@+id/special_mode_view" app:layout_constraintTop_toBottomOf="@+id/special_mode_view"
app:layout_constraintBottom_toTopOf="@+id/activity_password_info_container"> app:layout_constraintBottom_toTopOf="@+id/activity_password_footer">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar" android:id="@+id/app_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout" android:id="@+id/toolbar_layout"
@@ -167,7 +166,7 @@
</RelativeLayout> </RelativeLayout>
<!-- File Input --> <!-- File Input -->
<androidx.constraintlayout.widget.ConstraintLayout <RelativeLayout
android:id="@+id/container_key_file" android:id="@+id/container_key_file"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@@ -176,24 +175,22 @@
android:id="@+id/keyfile_checkox" android:id="@+id/keyfile_checkox"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:paddingBottom="20dp" android:paddingBottom="20dp"
android:contentDescription="@string/content_description_keyfile_checkbox" android:contentDescription="@string/content_description_keyfile_checkbox"
android:focusable="false" android:focusable="false"
android:layout_alignBottom="@+id/keyfile_selection"
android:gravity="center_vertical"/> android:gravity="center_vertical"/>
<com.kunzisoft.keepass.view.KeyFileSelectionView <com.kunzisoft.keepass.view.KeyFileSelectionView
android:id="@+id/keyfile_selection" android:id="@+id/keyfile_selection"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" android:minHeight="48dp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_toRightOf="@+id/keyfile_checkox"
app:layout_constraintStart_toEndOf="@+id/keyfile_checkox" android:layout_toEndOf="@+id/keyfile_checkox"
app:layout_constraintEnd_toEndOf="parent"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:importantForAutofill="no" /> android:importantForAutofill="no" />
</androidx.constraintlayout.widget.ConstraintLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>
@@ -209,41 +206,44 @@
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<LinearLayout <LinearLayout
android:id="@+id/activity_password_info_container" android:id="@+id/activity_password_footer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/activity_password_open_button"> app:layout_constraintBottom_toBottomOf="parent">
<androidx.appcompat.widget.AppCompatTextView <LinearLayout
android:id="@+id/activity_password_info_text" android:id="@+id/activity_password_info_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:orientation="vertical">
android:minHeight="24dp" <androidx.appcompat.widget.AppCompatTextView
android:paddingStart="24dp" android:id="@+id/activity_password_info_text"
android:paddingLeft="24dp" android:layout_width="match_parent"
android:paddingEnd="24dp" android:layout_height="wrap_content"
android:paddingRight="24dp" android:gravity="center"
style="@style/KeepassDXStyle.TextAppearance.Tiny" android:minHeight="24dp"
android:text="@string/warning_database_link_revoked" android:paddingStart="24dp"
android:textColor="?attr/textColorInverse" android:paddingLeft="24dp"
android:background="?attr/colorAccent" android:paddingEnd="24dp"
app:layout_constraintBottom_toTopOf="@+id/activity_password_info_delimiter" android:paddingRight="24dp"
android:layout_gravity="bottom"/> style="@style/KeepassDXStyle.TextAppearance.Tiny"
<View android:text="@string/warning_database_link_revoked"
android:id="@+id/activity_password_info_delimiter" android:textColor="?attr/textColorInverse"
android:background="?attr/colorAccent"
app:layout_constraintBottom_toTopOf="@+id/activity_password_info_delimiter"
android:layout_gravity="bottom"/>
<View
android:id="@+id/activity_password_info_delimiter"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?attr/colorAccentLight"/>
</LinearLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/activity_password_open_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="wrap_content"
android:background="?attr/colorAccentLight"/> android:focusable="true"
android:text="@string/menu_open" />
</LinearLayout> </LinearLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/activity_password_open_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:focusable="true"
android:text="@string/menu_open" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -107,7 +107,6 @@
<string name="progress_create">Creant nova base de dades…</string> <string name="progress_create">Creant nova base de dades…</string>
<string name="progress_title">Treballant…</string> <string name="progress_title">Treballant…</string>
<string name="content_description_remove_from_list">Elimina</string> <string name="content_description_remove_from_list">Elimina</string>
<string name="encryption_rijndael">Rijndael (AES)</string>
<string name="root">Arrel</string> <string name="root">Arrel</string>
<string name="rounds">Passades d\'encriptació</string> <string name="rounds">Passades d\'encriptació</string>
<string name="rounds_explanation">Més passades d\'encriptació donen protecció addicional contra atacs de força bruta, però poden alentir molt la càrrega i el desat de la base de dades.</string> <string name="rounds_explanation">Més passades d\'encriptació donen protecció addicional contra atacs de força bruta, però poden alentir molt la càrrega i el desat de la base de dades.</string>
@@ -118,7 +117,6 @@
<string name="special">Especial</string> <string name="special">Especial</string>
<string name="search">Títol/descripció d\'entrada</string> <string name="search">Títol/descripció d\'entrada</string>
<string name="search_results">Resultats de cerca</string> <string name="search_results">Resultats de cerca</string>
<string name="encryption_twofish">Twofish</string>
<string name="underline">Subratllat</string> <string name="underline">Subratllat</string>
<string name="unsupported_db_version">Versió de la base de dades no suportada.</string> <string name="unsupported_db_version">Versió de la base de dades no suportada.</string>
<string name="uppercase">Majúscules</string> <string name="uppercase">Majúscules</string>

View File

@@ -116,7 +116,6 @@
<string name="protection">Ochrana</string> <string name="protection">Ochrana</string>
<string name="read_only_warning">Ke změně v databáze potřebuje KeePassDX oprávnění pro zápis.</string> <string name="read_only_warning">Ke změně v databáze potřebuje KeePassDX oprávnění pro zápis.</string>
<string name="content_description_remove_from_list">Odstranit</string> <string name="content_description_remove_from_list">Odstranit</string>
<string name="encryption_rijndael">Rijndael (AES)</string>
<string name="root">Kořen</string> <string name="root">Kořen</string>
<string name="rounds">Transformační průchody</string> <string name="rounds">Transformační průchody</string>
<string name="rounds_explanation">Vyšší počet šifrovacích průchodů zvýší odolnost proti útoku zkoušením všech možných hesel, ale může výrazně zpomalit načítání a ukládání.</string> <string name="rounds_explanation">Vyšší počet šifrovacích průchodů zvýší odolnost proti útoku zkoušením všech možných hesel, ale může výrazně zpomalit načítání a ukládání.</string>
@@ -127,7 +126,6 @@
<string name="special">Speciální</string> <string name="special">Speciální</string>
<string name="search">Hledat</string> <string name="search">Hledat</string>
<string name="search_results">Výsledky hledání</string> <string name="search_results">Výsledky hledání</string>
<string name="encryption_twofish">Twofish</string>
<string name="underline">Podtržítko</string> <string name="underline">Podtržítko</string>
<string name="unsupported_db_version">Nepodporovaná verze databáze.</string> <string name="unsupported_db_version">Nepodporovaná verze databáze.</string>
<string name="uppercase">Velká písmena</string> <string name="uppercase">Velká písmena</string>
@@ -294,8 +292,6 @@
<string name="html_text_dev_feature_upgrade">Pamatujte na aktualizaci aplikace instalováním nových verzí.</string> <string name="html_text_dev_feature_upgrade">Pamatujte na aktualizaci aplikace instalováním nových verzí.</string>
<string name="download">Stáhnout</string> <string name="download">Stáhnout</string>
<string name="contribute">Přispět</string> <string name="contribute">Přispět</string>
<string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string>
<string name="style_choose_title">Vzhled aplikace</string> <string name="style_choose_title">Vzhled aplikace</string>
<string name="style_choose_summary">Motiv vzhledu aplikace</string> <string name="style_choose_summary">Motiv vzhledu aplikace</string>
<string name="icon_pack_choose_title">Sada ikon</string> <string name="icon_pack_choose_title">Sada ikon</string>
@@ -539,8 +535,6 @@
<string name="temp_advanced_unlock_enable_summary">Za účelem rozšířeného odemknutí neukládat žádný šifrovaný obsah</string> <string name="temp_advanced_unlock_enable_summary">Za účelem rozšířeného odemknutí neukládat žádný šifrovaný obsah</string>
<string name="temp_advanced_unlock_enable_title">Přechodné rozšířené odemknutí</string> <string name="temp_advanced_unlock_enable_title">Přechodné rozšířené odemknutí</string>
<string name="advanced_unlock_tap_delete">Pro odstranění klíčů rozšířeného odemknutí klepnout</string> <string name="advanced_unlock_tap_delete">Pro odstranění klíčů rozšířeného odemknutí klepnout</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="education_advanced_unlock_summary">Abyste rychle odemknuli databázi, propojte své heslo s naskenovanou biometrikou nebo údaji zámku zařízení.</string> <string name="education_advanced_unlock_summary">Abyste rychle odemknuli databázi, propojte své heslo s naskenovanou biometrikou nebo údaji zámku zařízení.</string>
<string name="temp_advanced_unlock_timeout_title">Vypršení pokročilého odemknutí</string> <string name="temp_advanced_unlock_timeout_title">Vypršení pokročilého odemknutí</string>
<string name="content">Obsah</string> <string name="content">Obsah</string>

View File

@@ -115,7 +115,6 @@
<string name="protection">Beskyttelse</string> <string name="protection">Beskyttelse</string>
<string name="read_only_warning">KeePassDX behøver skrivetilladelse for at ændre i databasen.</string> <string name="read_only_warning">KeePassDX behøver skrivetilladelse for at ændre i databasen.</string>
<string name="content_description_remove_from_list">Fjern</string> <string name="content_description_remove_from_list">Fjern</string>
<string name="encryption_rijndael">Rijndael (AES)</string>
<string name="root">Rod</string> <string name="root">Rod</string>
<string name="rounds">Transformationsrunder</string> <string name="rounds">Transformationsrunder</string>
<string name="rounds_explanation">Yderligere krypteringsrunder giver højere beskyttelse mod brute-force angreb, men kan virkelig forsinke læsnings- og skrivehastigheden.</string> <string name="rounds_explanation">Yderligere krypteringsrunder giver højere beskyttelse mod brute-force angreb, men kan virkelig forsinke læsnings- og skrivehastigheden.</string>
@@ -126,7 +125,6 @@
<string name="special">Speciel</string> <string name="special">Speciel</string>
<string name="search">Søg</string> <string name="search">Søg</string>
<string name="search_results">Søgeresultater</string> <string name="search_results">Søgeresultater</string>
<string name="encryption_twofish">Twofish</string>
<string name="underline">Understregning</string> <string name="underline">Understregning</string>
<string name="unsupported_db_version">Database-versionen er ikke understøttet.</string> <string name="unsupported_db_version">Database-versionen er ikke understøttet.</string>
<string name="uppercase">Store bogstaver</string> <string name="uppercase">Store bogstaver</string>
@@ -293,8 +291,6 @@
<string name="html_text_dev_feature_upgrade">Glem ikke at holde appen opdateret ved at installere nye versioner.</string> <string name="html_text_dev_feature_upgrade">Glem ikke at holde appen opdateret ved at installere nye versioner.</string>
<string name="download">Hent</string> <string name="download">Hent</string>
<string name="contribute">Bidrag</string> <string name="contribute">Bidrag</string>
<string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string>
<string name="style_choose_title">Tema</string> <string name="style_choose_title">Tema</string>
<string name="style_choose_summary">Tema, der bruges i programmet</string> <string name="style_choose_summary">Tema, der bruges i programmet</string>
<string name="icon_pack_choose_title">Ikonpakke</string> <string name="icon_pack_choose_title">Ikonpakke</string>

View File

@@ -128,7 +128,6 @@
<string name="read_only">Schreibgeschützt</string> <string name="read_only">Schreibgeschützt</string>
<string name="read_only_warning">KeePassDX benötigt Schreibrechte, um etwas an der Datenbank zu ändern.</string> <string name="read_only_warning">KeePassDX benötigt Schreibrechte, um etwas an der Datenbank zu ändern.</string>
<string name="content_description_remove_from_list">Löschen</string> <string name="content_description_remove_from_list">Löschen</string>
<string name="encryption_rijndael">Rijndael (AES)</string>
<string name="root">Start</string> <string name="root">Start</string>
<string name="rounds">Schlüsseltransformationen</string> <string name="rounds">Schlüsseltransformationen</string>
<string name="rounds_explanation">Zusätzliche Schlüsseltransformationen bieten einen besseren Schutz gegen Wörterbuch- oder Brute-Force-Angriffe. Allerdings dauert dann auch das Laden und Speichern der Datenbank entsprechend länger.</string> <string name="rounds_explanation">Zusätzliche Schlüsseltransformationen bieten einen besseren Schutz gegen Wörterbuch- oder Brute-Force-Angriffe. Allerdings dauert dann auch das Laden und Speichern der Datenbank entsprechend länger.</string>
@@ -139,7 +138,6 @@
<string name="special">Spezialsymbole</string> <string name="special">Spezialsymbole</string>
<string name="search">Suchen</string> <string name="search">Suchen</string>
<string name="search_results">Suchergebnisse</string> <string name="search_results">Suchergebnisse</string>
<string name="encryption_twofish">Twofish</string>
<string name="underline">Unterstriche</string> <string name="underline">Unterstriche</string>
<string name="unsupported_db_version">Datenbankversion wird nicht unterstützt.</string> <string name="unsupported_db_version">Datenbankversion wird nicht unterstützt.</string>
<string name="uppercase">Großbuchstaben</string> <string name="uppercase">Großbuchstaben</string>
@@ -279,8 +277,6 @@
<string name="html_text_dev_feature_upgrade">Denken Sie daran, Ihre App auf dem neuesten Stand zu halten, indem Sie neue Versionen installieren.</string> <string name="html_text_dev_feature_upgrade">Denken Sie daran, Ihre App auf dem neuesten Stand zu halten, indem Sie neue Versionen installieren.</string>
<string name="download">Download</string> <string name="download">Download</string>
<string name="contribute">Unterstützen</string> <string name="contribute">Unterstützen</string>
<string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string>
<string name="icon_pack_choose_title">Symbolpaket</string> <string name="icon_pack_choose_title">Symbolpaket</string>
<string name="icon_pack_choose_summary">In der App verwendetes Symbolpaket</string> <string name="icon_pack_choose_summary">In der App verwendetes Symbolpaket</string>
<string name="error_move_folder_in_itself">Eine Gruppe kann nicht in sich selbst verschoben werden.</string> <string name="error_move_folder_in_itself">Eine Gruppe kann nicht in sich selbst verschoben werden.</string>
@@ -552,8 +548,6 @@
<string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string> <string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string>
<string name="advanced_unlock_invalid_key">Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.</string> <string name="advanced_unlock_invalid_key">Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren</string> <string name="advanced_unlock_prompt_extract_credential_message">Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="error_rebuild_list">Die Liste kann nicht ordnungsgemäß neu erstellt werden.</string> <string name="error_rebuild_list">Die Liste kann nicht ordnungsgemäß neu erstellt werden.</string>
<string name="error_database_uri_null">Datenbank-URI kann nicht abgerufen werden.</string> <string name="error_database_uri_null">Datenbank-URI kann nicht abgerufen werden.</string>
<string name="menu_reload_database">Datenbank neu laden</string> <string name="menu_reload_database">Datenbank neu laden</string>

View File

@@ -118,7 +118,6 @@
<string name="protection">Προστασία</string> <string name="protection">Προστασία</string>
<string name="read_only_warning">Το KeePassDX χρειάζεται άδεια εγγραφής για να αλλάξει οτιδήποτε στη βάση δεδομένων σας.</string> <string name="read_only_warning">Το KeePassDX χρειάζεται άδεια εγγραφής για να αλλάξει οτιδήποτε στη βάση δεδομένων σας.</string>
<string name="content_description_remove_from_list">Αφαίρεση</string> <string name="content_description_remove_from_list">Αφαίρεση</string>
<string name="encryption_rijndael">Rijndael (AES)</string>
<string name="root">Ριζικός Κατάλογος</string> <string name="root">Ριζικός Κατάλογος</string>
<string name="rounds">Κύκλοι μετασχηματισμού Κρυπτογράφησης</string> <string name="rounds">Κύκλοι μετασχηματισμού Κρυπτογράφησης</string>
<string name="rounds_explanation">Επιπλέον κύκλοι κρυπτογράφησης παρέχουν πρόσθετη προστασία ενάντια σε επιθέσεις brute force, αλλά μπορεί να επιβραδύνει πολύ την φόρτωση και την αποθήκευση.</string> <string name="rounds_explanation">Επιπλέον κύκλοι κρυπτογράφησης παρέχουν πρόσθετη προστασία ενάντια σε επιθέσεις brute force, αλλά μπορεί να επιβραδύνει πολύ την φόρτωση και την αποθήκευση.</string>
@@ -129,7 +128,6 @@
<string name="special">Ειδικοί</string> <string name="special">Ειδικοί</string>
<string name="search">Αναζήτηση</string> <string name="search">Αναζήτηση</string>
<string name="search_results">Αποτελέσματα αναζήτησης</string> <string name="search_results">Αποτελέσματα αναζήτησης</string>
<string name="encryption_twofish">Twofish</string>
<string name="underline">Υπογράμμιση</string> <string name="underline">Υπογράμμιση</string>
<string name="unsupported_db_version">Μη υποστηριζόμενη έκδοση βάσης δεδομένων.</string> <string name="unsupported_db_version">Μη υποστηριζόμενη έκδοση βάσης δεδομένων.</string>
<string name="uppercase">Κεφαλαία</string> <string name="uppercase">Κεφαλαία</string>
@@ -260,8 +258,6 @@
<string name="html_text_dev_feature_upgrade">Θυμηθείτε να ενημερώνετε την εφαρμογή σας, εγκαθιστώντας νέες εκδόσεις.</string> <string name="html_text_dev_feature_upgrade">Θυμηθείτε να ενημερώνετε την εφαρμογή σας, εγκαθιστώντας νέες εκδόσεις.</string>
<string name="download">Λήψη</string> <string name="download">Λήψη</string>
<string name="contribute">Συνεισφορά</string> <string name="contribute">Συνεισφορά</string>
<string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string>
<string name="style_choose_title">Θέμα Εφαρμογής</string> <string name="style_choose_title">Θέμα Εφαρμογής</string>
<string name="style_choose_summary">Θέμα που χρησιμοποιείται στην εφαρμογή</string> <string name="style_choose_summary">Θέμα που χρησιμοποιείται στην εφαρμογή</string>
<string name="icon_pack_choose_title">Πακέτο Εικονιδίων</string> <string name="icon_pack_choose_title">Πακέτο Εικονιδίων</string>
@@ -541,8 +537,6 @@
<string name="temp_advanced_unlock_timeout_title">Λήξη προηγμένου ξεκλειδώματος</string> <string name="temp_advanced_unlock_timeout_title">Λήξη προηγμένου ξεκλειδώματος</string>
<string name="advanced_unlock_tap_delete">Πατήστε για διαγραφή προηγμένων κλειδιών ξεκλειδώματος</string> <string name="advanced_unlock_tap_delete">Πατήστε για διαγραφή προηγμένων κλειδιών ξεκλειδώματος</string>
<string name="content">Περιεχόμενα</string> <string name="content">Περιεχόμενα</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="error_rebuild_list">Δεν είναι δυνατή η σωστή αναδημιουργία της λίστας.</string> <string name="error_rebuild_list">Δεν είναι δυνατή η σωστή αναδημιουργία της λίστας.</string>
<string name="error_database_uri_null">Δεν είναι δυνατή η ανάκτηση του URI βάσης δεδομένων.</string> <string name="error_database_uri_null">Δεν είναι δυνατή η ανάκτηση του URI βάσης δεδομένων.</string>
<string name="autofill_inline_suggestions_keyboard">Προστέθηκαν προτάσεις αυτόματης συμπλήρωσης.</string> <string name="autofill_inline_suggestions_keyboard">Προστέθηκαν προτάσεις αυτόματης συμπλήρωσης.</string>

View File

@@ -109,7 +109,6 @@
<string name="progress_create">Creando nueva base de datos…</string> <string name="progress_create">Creando nueva base de datos…</string>
<string name="progress_title">Trabajando…</string> <string name="progress_title">Trabajando…</string>
<string name="content_description_remove_from_list">Quitar</string> <string name="content_description_remove_from_list">Quitar</string>
<string name="encryption_rijndael">Rijndael (AES)</string>
<string name="root">Raíz</string> <string name="root">Raíz</string>
<string name="rounds">Pasadas de transformación</string> <string name="rounds">Pasadas de transformación</string>
<string name="rounds_explanation">Un alto número de pasadas de cifrado proporciona protección adicional contra ataques de fuerza bruta, pero puede ralentizar mucho el cargado y el guardado.</string> <string name="rounds_explanation">Un alto número de pasadas de cifrado proporciona protección adicional contra ataques de fuerza bruta, pero puede ralentizar mucho el cargado y el guardado.</string>
@@ -119,7 +118,6 @@
<string name="sort_db">Orden natural</string> <string name="sort_db">Orden natural</string>
<string name="special">Especial</string> <string name="special">Especial</string>
<string name="search">Búsqueda</string> <string name="search">Búsqueda</string>
<string name="encryption_twofish">Twofish</string>
<string name="underline">Subrayado</string> <string name="underline">Subrayado</string>
<string name="unsupported_db_version">No se admite esta versión de la base de datos.</string> <string name="unsupported_db_version">No se admite esta versión de la base de datos.</string>
<string name="uppercase">Mayúsculas</string> <string name="uppercase">Mayúsculas</string>
@@ -273,8 +271,6 @@
<string name="html_text_dev_feature_upgrade">Recuerde mantener su aplicación actualizada instalando nuevas versiones.</string> <string name="html_text_dev_feature_upgrade">Recuerde mantener su aplicación actualizada instalando nuevas versiones.</string>
<string name="download">Descargar</string> <string name="download">Descargar</string>
<string name="contribute">Contribuir</string> <string name="contribute">Contribuir</string>
<string name="encryption_chacha20">ChaCha20</string>
<string name="kdf_AES">AES</string>
<string name="style_choose_title">Tema de la aplicación</string> <string name="style_choose_title">Tema de la aplicación</string>
<string name="style_choose_summary">Tema utilizado en la aplicación</string> <string name="style_choose_summary">Tema utilizado en la aplicación</string>
<string name="icon_pack_choose_title">Seleccione un paquete de iconos</string> <string name="icon_pack_choose_title">Seleccione un paquete de iconos</string>
@@ -507,8 +503,6 @@
<string name="notification">Notificación</string> <string name="notification">Notificación</string>
<string name="hide_expired_entries_title">Ocultar las entradas expiradas</string> <string name="hide_expired_entries_title">Ocultar las entradas expiradas</string>
<string name="keyboard_search_share_title">Buscar información compartida</string> <string name="keyboard_search_share_title">Buscar información compartida</string>
<string name="kdf_Argon2id">Argon2id</string>
<string name="kdf_Argon2d">Argon2d</string>
<string name="upload_attachment">Subir %1$s</string> <string name="upload_attachment">Subir %1$s</string>
<string name="education_setup_OTP_summary">Configurar la gestión de contraseñas de una sola vez (HOTP / TOTP) para generar un token solicitado para la autenticación de dos factores (2FA).</string> <string name="education_setup_OTP_summary">Configurar la gestión de contraseñas de una sola vez (HOTP / TOTP) para generar un token solicitado para la autenticación de dos factores (2FA).</string>
<string name="education_setup_OTP_title">Establecer la contaseña de un solo uso</string> <string name="education_setup_OTP_title">Establecer la contaseña de un solo uso</string>

Some files were not shown because too many files have changed in this diff Show More