diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt index 17de99ad7..9b626fc9b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt @@ -22,10 +22,8 @@ package com.kunzisoft.keepass.database.action import android.content.Context import android.net.Uri import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidKeyFileException import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.utils.UriUtil -import java.io.IOException open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor( context: Context, @@ -63,10 +61,7 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor( // To save the database super.run() finishRun(true) - } catch (e: LoadDatabaseInvalidKeyFileException) { - erase(mBackupKey) - finishRun(false, e.message) - } catch (e: IOException) { + } catch (e: Exception) { erase(mBackupKey) finishRun(false, e.message) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index c308e99e6..111c5cb77 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -294,88 +294,73 @@ class Database { fixDuplicateUUID: Boolean, progressTaskUpdater: ProgressTaskUpdater?) { + mUri = uri + isReadOnly = false + if (uri.scheme == "file") { + val file = File(uri.path!!) + isReadOnly = !file.canWrite() + } + + // Pass Uris as InputStreams + val inputStream: InputStream? try { + inputStream = UriUtil.getUriInputStream(contentResolver, uri) + } catch (e: Exception) { + Log.e("KPD", "Database::loadData", e) + throw LoadDatabaseFileNotFoundException() + } - mUri = uri - isReadOnly = false - if (uri.scheme == "file") { - val file = File(uri.path!!) - isReadOnly = !file.canWrite() - } - - // Pass Uris as InputStreams - val inputStream: InputStream? + // Pass KeyFile Uri as InputStreams + var keyFileInputStream: InputStream? = null + keyfile?.let { try { - inputStream = UriUtil.getUriInputStream(contentResolver, uri) + keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile) } catch (e: Exception) { Log.e("KPD", "Database::loadData", e) throw LoadDatabaseFileNotFoundException() } - - // Pass KeyFile Uri as InputStreams - var keyFileInputStream: InputStream? = null - keyfile?.let { - try { - keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile) - } catch (e: Exception) { - Log.e("KPD", "Database::loadData", e) - throw LoadDatabaseFileNotFoundException() - } - } - - // Load Data - - val bufferedInputStream = BufferedInputStream(inputStream) - if (!bufferedInputStream.markSupported()) { - throw IOException("Input stream does not support mark.") - } - - // We'll end up reading 8 bytes to identify the header. Might as well use two extra. - bufferedInputStream.mark(10) - - // Get the file directory to save the attachments - val sig1 = LEDataInputStream.readInt(bufferedInputStream) - val sig2 = LEDataInputStream.readInt(bufferedInputStream) - - // Return to the start - bufferedInputStream.reset() - - when { - // Header of database V3 - PwDbHeaderV3.matchesHeader(sig1, sig2) -> setDatabaseV3(ImporterV3() - .openDatabase(bufferedInputStream, - password, - keyFileInputStream, - progressTaskUpdater)) - - // Header of database V4 - PwDbHeaderV4.matchesHeader(sig1, sig2) -> setDatabaseV4(ImporterV4( - cacheDirectory, - fixDuplicateUUID) - .openDatabase(bufferedInputStream, - password, - keyFileInputStream, - progressTaskUpdater)) - - // Header not recognized - else -> throw LoadDatabaseSignatureException() - } - - this.mSearchHelper = searchHelper - loaded = true - - } catch (e: LoadDatabaseException) { - throw e - } catch (e: IOException) { - if (e.message?.contains("Hash failed with code") == true) - throw LoadDatabaseKDFMemoryException(e) - else - throw LoadDatabaseIOException(e) - } catch (e: OutOfMemoryError) { - throw LoadDatabaseNoMemoryException(e) - } catch (e: Exception) { - throw LoadDatabaseException(e) } + + // Load Data + + val bufferedInputStream = BufferedInputStream(inputStream) + if (!bufferedInputStream.markSupported()) { + throw IOException("Input stream does not support mark.") + } + + // We'll end up reading 8 bytes to identify the header. Might as well use two extra. + bufferedInputStream.mark(10) + + // Get the file directory to save the attachments + val sig1 = LEDataInputStream.readInt(bufferedInputStream) + val sig2 = LEDataInputStream.readInt(bufferedInputStream) + + // Return to the start + bufferedInputStream.reset() + + when { + // Header of database V3 + PwDbHeaderV3.matchesHeader(sig1, sig2) -> setDatabaseV3(ImporterV3() + .openDatabase(bufferedInputStream, + password, + keyFileInputStream, + progressTaskUpdater)) + + // Header of database V4 + PwDbHeaderV4.matchesHeader(sig1, sig2) -> setDatabaseV4(ImporterV4( + cacheDirectory, + fixDuplicateUUID) + .openDatabase(bufferedInputStream, + password, + keyFileInputStream, + progressTaskUpdater)) + + // Header not recognized + else -> throw LoadDatabaseSignatureException() + } + + this.mSearchHelper = searchHelper + loaded = true } fun isGroupSearchable(group: GroupVersioned, isOmitBackup: Boolean): Boolean { @@ -519,7 +504,7 @@ class Database { ?: false } - @Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class) + @Throws(IOException::class) fun retrieveMasterKey(key: String?, keyInputStream: InputStream?) { pwDatabaseV3?.retrieveMasterKey(key, keyInputStream) pwDatabaseV4?.retrieveMasterKey(key, keyInputStream) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabase.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabase.kt index 9d5f77fad..8b4c97b7c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabase.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabase.kt @@ -21,19 +21,12 @@ package com.kunzisoft.keepass.database.element import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException -import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidKeyFileException import com.kunzisoft.keepass.database.exception.LoadDatabaseKeyFileEmptyException import com.kunzisoft.keepass.utils.MemoryUtil - -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.InputStream -import java.io.UnsupportedEncodingException +import java.io.* import java.security.MessageDigest import java.security.NoSuchAlgorithmException -import java.util.LinkedHashMap -import java.util.UUID +import java.util.* abstract class PwDatabase< GroupId, @@ -78,15 +71,15 @@ abstract class PwDatabase< var rootGroup: Group? = null - @Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class) + @Throws(IOException::class) protected abstract fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray - @Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class) + @Throws(IOException::class) fun retrieveMasterKey(key: String?, keyInputStream: InputStream?) { masterKey = getMasterKey(key, keyInputStream) } - @Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class) + @Throws(IOException::class) protected fun getCompositeKey(key: String, keyInputStream: InputStream): ByteArray { val fileKey = getFileKey(keyInputStream) val passwordKey = getPasswordKey(key) @@ -126,7 +119,7 @@ abstract class PwDatabase< return messageDigest.digest() } - @Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class) + @Throws(IOException::class) protected fun getFileKey(keyInputStream: InputStream): ByteArray { val keyByteArrayOutputStream = ByteArrayOutputStream() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV3.kt index 65502af0c..58e8b0027 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV3.kt @@ -22,7 +22,6 @@ package com.kunzisoft.keepass.database.element import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory -import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidKeyFileException import com.kunzisoft.keepass.stream.NullOutputStream import java.io.IOException import java.io.InputStream @@ -112,7 +111,7 @@ class PwDatabaseV3 : PwDatabase() { return newId } - @Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class) + @Throws(IOException::class) override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray { return if (key != null && keyInputStream != null) { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt index 2ab624d92..ce0d56591 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt @@ -27,7 +27,6 @@ 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.* -import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidKeyFileException import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.utils.VariantDictionary import org.w3c.dom.Node @@ -238,7 +237,7 @@ class PwDatabaseV4 : PwDatabase { return getCustomData().isNotEmpty() } - @Throws(LoadDatabaseInvalidKeyFileException::class, IOException::class) + @Throws(IOException::class) public override fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray { var masterKey = byteArrayOf() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/exception/LoadDatabaseException.kt b/app/src/main/java/com/kunzisoft/keepass/database/exception/LoadDatabaseException.kt index 4f9e2025a..402bd45aa 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/exception/LoadDatabaseException.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/exception/LoadDatabaseException.kt @@ -5,44 +5,60 @@ import androidx.annotation.StringRes import com.kunzisoft.keepass.R import com.kunzisoft.keepass.database.element.PwNodeId import com.kunzisoft.keepass.database.element.Type -import java.io.IOException -class LoadDatabaseArcFourException : - LoadDatabaseException(R.string.error_arc4) +class LoadDatabaseArcFourException : LoadDatabaseException { + constructor() : super(R.string.error_arc4) + constructor(exception: Throwable) : super(exception, R.string.error_arc4) +} -class LoadDatabaseFileNotFoundException : - LoadDatabaseException(R.string.file_not_found_content) +class LoadDatabaseFileNotFoundException : LoadDatabaseException { + constructor() : super(R.string.file_not_found_content) + constructor(exception: Throwable) : super(exception, R.string.file_not_found_content) +} -class LoadDatabaseInvalidAlgorithmException : - LoadDatabaseException(R.string.invalid_algorithm) +class LoadDatabaseInvalidAlgorithmException : LoadDatabaseException { + constructor() : super(R.string.invalid_algorithm) + constructor(exception: Throwable) : super(exception, R.string.invalid_algorithm) +} class LoadDatabaseDuplicateUuidException(type: Type, uuid: PwNodeId<*>): LoadDatabaseException(R.string.invalid_db_same_uuid, type.name, uuid.toString()) -class LoadDatabaseIOException(exception: IOException) : - LoadDatabaseException(exception, R.string.error_load_database) +class LoadDatabaseIOException : LoadDatabaseException { + constructor() : super(R.string.error_load_database) + constructor(exception: Throwable) : super(exception, R.string.error_load_database) +} -class LoadDatabaseKDFMemoryException(exception: IOException) : - LoadDatabaseException(exception, R.string.error_load_database_KDF_memory) +class LoadDatabaseKDFMemoryException : LoadDatabaseException { + constructor() : super(R.string.error_load_database_KDF_memory) + constructor(exception: Throwable) : super(exception, R.string.error_load_database_KDF_memory) +} -class LoadDatabaseSignatureException : - LoadDatabaseException(R.string.invalid_db_sig) +class LoadDatabaseSignatureException : LoadDatabaseException { + constructor() : super(R.string.invalid_db_sig) + constructor(exception: Throwable) : super(exception, R.string.invalid_db_sig) +} -class LoadDatabaseVersionException : - LoadDatabaseException(R.string.unsupported_db_version) +class LoadDatabaseVersionException : LoadDatabaseException { + constructor() : super(R.string.unsupported_db_version) + constructor(exception: Throwable) : super(exception, R.string.unsupported_db_version) +} -open class LoadDatabaseInvalidKeyFileException : - LoadDatabaseException(R.string.keyfile_does_not_exist) +class LoadDatabaseInvalidCredentialsException : LoadDatabaseException { + constructor() : super(R.string.invalid_credentials) + constructor(exception: Throwable) : super(exception, R.string.invalid_credentials) +} -class LoadDatabaseInvalidPasswordException : - LoadDatabaseException(R.string.invalid_password) +class LoadDatabaseKeyFileEmptyException : LoadDatabaseException { + constructor() : super(R.string.keyfile_is_empty) + constructor(exception: Throwable) : super(exception, R.string.keyfile_is_empty) +} -class LoadDatabaseKeyFileEmptyException : - LoadDatabaseException(R.string.keyfile_is_empty) - -class LoadDatabaseNoMemoryException(exception: OutOfMemoryError) : - LoadDatabaseException(exception, R.string.error_out_of_memory) +class LoadDatabaseNoMemoryException: LoadDatabaseException { + constructor() : super(R.string.error_out_of_memory) + constructor(exception: Throwable) : super(exception, R.string.error_out_of_memory) +} open class LoadDatabaseException : Exception { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt index 83bd0bb55..d10d234cb 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt @@ -22,8 +22,6 @@ package com.kunzisoft.keepass.database.file.load import com.kunzisoft.keepass.database.element.PwDatabase import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.tasks.ProgressTaskUpdater - -import java.io.IOException import java.io.InputStream abstract class Importer> { @@ -35,10 +33,9 @@ abstract class Importer> { * @param password Pass phrase for infile. * @return new PwDatabase container. * - * @throws IOException on any file error. - * @throws LoadDatabaseException on database error. + * @throws LoadDatabaseException on database error (contains IO exceptions) */ - @Throws(IOException::class, LoadDatabaseException::class) + @Throws(LoadDatabaseException::class) abstract fun openDatabase(databaseInputStream: InputStream, password: String?, keyInputStream: InputStream?, diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt index c16c24281..be42a451a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt @@ -73,155 +73,163 @@ class ImporterV3 : Importer() { private lateinit var mDatabaseToOpen: PwDatabaseV3 - @Throws(IOException::class, LoadDatabaseException::class) + @Throws(LoadDatabaseException::class) override fun openDatabase(databaseInputStream: InputStream, password: String?, keyInputStream: InputStream?, progressTaskUpdater: ProgressTaskUpdater?): PwDatabaseV3 { - // Load entire file, most of it's encrypted. - val fileSize = databaseInputStream.available() - val filebuf = ByteArray(fileSize + 16) // Pad with a blocksize (Twofish uses 128 bits), since Android 4.3 tries to write more to the buffer - databaseInputStream.read(filebuf, 0, fileSize) // TODO remove - databaseInputStream.close() - - // Parse header (unencrypted) - if (fileSize < PwDbHeaderV3.BUF_SIZE) - throw IOException("File too short for header") - val hdr = PwDbHeaderV3() - hdr.loadFromFile(filebuf, 0) - - if (hdr.signature1 != PwDbHeader.PWM_DBSIG_1 || hdr.signature2 != PwDbHeaderV3.DBSIG_2) { - throw LoadDatabaseSignatureException() - } - - if (!hdr.matchesVersion()) { - throw LoadDatabaseVersionException() - } - - progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) - mDatabaseToOpen = PwDatabaseV3() - mDatabaseToOpen.retrieveMasterKey(password, keyInputStream) - - // Select algorithm - if (hdr.flags and PwDbHeaderV3.FLAG_RIJNDAEL != 0) { - mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.AESRijndael - } else if (hdr.flags and PwDbHeaderV3.FLAG_TWOFISH != 0) { - mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.Twofish - } else { - throw LoadDatabaseInvalidAlgorithmException() - } - - mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong() - - // Generate transformedMasterKey from masterKey - mDatabaseToOpen.makeFinalKey(hdr.masterSeed, hdr.transformSeed, mDatabaseToOpen.numberKeyEncryptionRounds) - - progressTaskUpdater?.updateMessage(R.string.decrypting_db) - // Initialize Rijndael algorithm - val cipher: Cipher try { - if (mDatabaseToOpen.encryptionAlgorithm === PwEncryptionAlgorithm.AESRijndael) { - cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding") - } else if (mDatabaseToOpen.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish) { - cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING") - } else { - throw IOException("Encryption algorithm is not supported") + // Load entire file, most of it's encrypted. + val fileSize = databaseInputStream.available() + val filebuf = ByteArray(fileSize + 16) // Pad with a blocksize (Twofish uses 128 bits), since Android 4.3 tries to write more to the buffer + databaseInputStream.read(filebuf, 0, fileSize) // TODO remove + databaseInputStream.close() + + // Parse header (unencrypted) + if (fileSize < PwDbHeaderV3.BUF_SIZE) + throw IOException("File too short for header") + val hdr = PwDbHeaderV3() + hdr.loadFromFile(filebuf, 0) + + if (hdr.signature1 != PwDbHeader.PWM_DBSIG_1 || hdr.signature2 != PwDbHeaderV3.DBSIG_2) { + throw LoadDatabaseSignatureException() } - } catch (e1: NoSuchAlgorithmException) { - throw IOException("No such algorithm") - } catch (e1: NoSuchPaddingException) { - throw IOException("No such pdading") - } + if (!hdr.matchesVersion()) { + throw LoadDatabaseVersionException() + } - try { - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(mDatabaseToOpen.finalKey, "AES"), IvParameterSpec(hdr.encryptionIV)) - } catch (e1: InvalidKeyException) { - throw IOException("Invalid key") - } catch (e1: InvalidAlgorithmParameterException) { - throw IOException("Invalid algorithm parameter.") - } + progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) + mDatabaseToOpen = PwDatabaseV3() + mDatabaseToOpen.retrieveMasterKey(password, keyInputStream) - // Decrypt! The first bytes aren't encrypted (that's the header) - val encryptedPartSize: Int - try { - encryptedPartSize = cipher.doFinal(filebuf, PwDbHeaderV3.BUF_SIZE, fileSize - PwDbHeaderV3.BUF_SIZE, filebuf, PwDbHeaderV3.BUF_SIZE) - } catch (e1: ShortBufferException) { - throw IOException("Buffer too short") - } catch (e1: IllegalBlockSizeException) { - throw IOException("Invalid block size") - } catch (e1: BadPaddingException) { - throw LoadDatabaseInvalidPasswordException() - } + // Select algorithm + when { + hdr.flags and PwDbHeaderV3.FLAG_RIJNDAEL != 0 -> mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.AESRijndael + hdr.flags and PwDbHeaderV3.FLAG_TWOFISH != 0 -> mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.Twofish + else -> throw LoadDatabaseInvalidAlgorithmException() + } - val md: MessageDigest - try { - md = MessageDigest.getInstance("SHA-256") - } catch (e: NoSuchAlgorithmException) { - throw IOException("No SHA-256 algorithm") - } + mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong() - val nos = NullOutputStream() - val dos = DigestOutputStream(nos, md) - dos.write(filebuf, PwDbHeaderV3.BUF_SIZE, encryptedPartSize) - dos.close() - val hash = md.digest() + // Generate transformedMasterKey from masterKey + mDatabaseToOpen.makeFinalKey(hdr.masterSeed, hdr.transformSeed, mDatabaseToOpen.numberKeyEncryptionRounds) - if (!Arrays.equals(hash, hdr.contentsHash)) { + progressTaskUpdater?.updateMessage(R.string.decrypting_db) + // Initialize Rijndael algorithm + val cipher: Cipher + try { + if (mDatabaseToOpen.encryptionAlgorithm === PwEncryptionAlgorithm.AESRijndael) { + cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding") + } else if (mDatabaseToOpen.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish) { + cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING") + } else { + throw IOException("Encryption algorithm is not supported") + } - Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)") - throw LoadDatabaseInvalidPasswordException() - } + } catch (e1: NoSuchAlgorithmException) { + throw IOException("No such algorithm") + } catch (e1: NoSuchPaddingException) { + throw IOException("No such pdading") + } - // New manual root because V3 contains multiple root groups (here available with getRootGroups()) - val newRoot = mDatabaseToOpen.createGroup() - newRoot.level = -1 - mDatabaseToOpen.rootGroup = newRoot + try { + cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(mDatabaseToOpen.finalKey, "AES"), IvParameterSpec(hdr.encryptionIV)) + } catch (e1: InvalidKeyException) { + throw IOException("Invalid key") + } catch (e1: InvalidAlgorithmParameterException) { + throw IOException("Invalid algorithm parameter.") + } - // Import all groups - var pos = PwDbHeaderV3.BUF_SIZE - var newGrp = mDatabaseToOpen.createGroup() - run { + // Decrypt! The first bytes aren't encrypted (that's the header) + val encryptedPartSize: Int + try { + encryptedPartSize = cipher.doFinal(filebuf, PwDbHeaderV3.BUF_SIZE, fileSize - PwDbHeaderV3.BUF_SIZE, filebuf, PwDbHeaderV3.BUF_SIZE) + } catch (e1: ShortBufferException) { + throw IOException("Buffer too short") + } catch (e1: IllegalBlockSizeException) { + throw IOException("Invalid block size") + } catch (e1: BadPaddingException) { + throw LoadDatabaseInvalidCredentialsException() + } + + val md: MessageDigest + try { + md = MessageDigest.getInstance("SHA-256") + } catch (e: NoSuchAlgorithmException) { + throw IOException("No SHA-256 algorithm") + } + + val nos = NullOutputStream() + val dos = DigestOutputStream(nos, md) + dos.write(filebuf, PwDbHeaderV3.BUF_SIZE, encryptedPartSize) + dos.close() + val hash = md.digest() + + if (!Arrays.equals(hash, hdr.contentsHash)) { + + Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)") + throw LoadDatabaseInvalidCredentialsException() + } + + // New manual root because V3 contains multiple root groups (here available with getRootGroups()) + val newRoot = mDatabaseToOpen.createGroup() + newRoot.level = -1 + mDatabaseToOpen.rootGroup = newRoot + + // Import all groups + var pos = PwDbHeaderV3.BUF_SIZE + var newGrp = mDatabaseToOpen.createGroup() + run { + var i = 0 + while (i < hdr.numGroups) { + val fieldType = LEDataInputStream.readUShort(filebuf, pos) + pos += 2 + val fieldSize = LEDataInputStream.readInt(filebuf, pos) + pos += 4 + + if (fieldType == 0xFFFF) { + // End-Group record. Save group and count it. + mDatabaseToOpen.addGroupIndex(newGrp) + newGrp = mDatabaseToOpen.createGroup() + i++ + } else { + readGroupField(mDatabaseToOpen, newGrp, fieldType, filebuf, pos) + } + pos += fieldSize + } + } + + // Import all entries + var newEnt = mDatabaseToOpen.createEntry() var i = 0 - while (i < hdr.numGroups) { + while (i < hdr.numEntries) { val fieldType = LEDataInputStream.readUShort(filebuf, pos) - pos += 2 - val fieldSize = LEDataInputStream.readInt(filebuf, pos) - pos += 4 + val fieldSize = LEDataInputStream.readInt(filebuf, pos + 2) if (fieldType == 0xFFFF) { // End-Group record. Save group and count it. - mDatabaseToOpen.addGroupIndex(newGrp) - newGrp = mDatabaseToOpen.createGroup() + mDatabaseToOpen.addEntryIndex(newEnt) + newEnt = mDatabaseToOpen.createEntry() i++ } else { - readGroupField(mDatabaseToOpen, newGrp, fieldType, filebuf, pos) + readEntryField(mDatabaseToOpen, newEnt, filebuf, pos) } - pos += fieldSize + pos += 2 + 4 + fieldSize } + + constructTreeFromIndex() + } catch (e: LoadDatabaseException) { + throw e + } catch (e: IOException) { + throw LoadDatabaseIOException(e) + } catch (e: OutOfMemoryError) { + throw LoadDatabaseNoMemoryException(e) + } catch (e: Exception) { + throw LoadDatabaseException(e) } - // Import all entries - var newEnt = mDatabaseToOpen.createEntry() - var i = 0 - while (i < hdr.numEntries) { - val fieldType = LEDataInputStream.readUShort(filebuf, pos) - val fieldSize = LEDataInputStream.readInt(filebuf, pos + 2) - - if (fieldType == 0xFFFF) { - // End-Group record. Save group and count it. - mDatabaseToOpen.addEntryIndex(newEnt) - newEnt = mDatabaseToOpen.createEntry() - i++ - } else { - readEntryField(mDatabaseToOpen, newEnt, filebuf, pos) - } - pos += 2 + 4 + fieldSize - } - - constructTreeFromIndex() - return mDatabaseToOpen } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt index 25329885e..a148318ca 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt @@ -24,20 +24,17 @@ import com.kunzisoft.keepass.R import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.StreamCipherFactory import com.kunzisoft.keepass.crypto.engine.CipherEngine -import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm import com.kunzisoft.keepass.database.element.* -import com.kunzisoft.keepass.database.exception.LoadDatabaseArcFourException -import com.kunzisoft.keepass.database.exception.LoadDatabaseException -import com.kunzisoft.keepass.database.exception.LoadDatabaseInvalidPasswordException -import com.kunzisoft.keepass.database.file.PwDbHeaderV4 import com.kunzisoft.keepass.database.element.security.ProtectedBinary import com.kunzisoft.keepass.database.element.security.ProtectedString +import com.kunzisoft.keepass.database.exception.* +import com.kunzisoft.keepass.database.file.KDBX4DateUtil +import com.kunzisoft.keepass.database.file.PwDbHeaderV4 import com.kunzisoft.keepass.stream.BetterCipherInputStream import com.kunzisoft.keepass.stream.HashedBlockInputStream import com.kunzisoft.keepass.stream.HmacBlockInputStream import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.tasks.ProgressTaskUpdater -import com.kunzisoft.keepass.database.file.KDBX4DateUtil import com.kunzisoft.keepass.utils.MemoryUtil import com.kunzisoft.keepass.utils.Types import org.spongycastle.crypto.StreamCipher @@ -46,14 +43,10 @@ import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserFactory import java.io.* import java.nio.charset.Charset -import java.security.InvalidAlgorithmParameterException -import java.security.InvalidKeyException -import java.security.NoSuchAlgorithmException import java.text.ParseException import java.util.* import java.util.zip.GZIPInputStream import javax.crypto.Cipher -import javax.crypto.NoSuchPaddingException import kotlin.math.min class ImporterV4(private val streamDir: File, @@ -90,110 +83,120 @@ class ImporterV4(private val streamDir: File, private var entryCustomDataKey: String? = null private var entryCustomDataValue: String? = null - @Throws(IOException::class, LoadDatabaseException::class) + @Throws(LoadDatabaseException::class) override fun openDatabase(databaseInputStream: InputStream, password: String?, keyInputStream: InputStream?, progressTaskUpdater: ProgressTaskUpdater?): PwDatabaseV4 { - // TODO performance - progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) - - mDatabase = PwDatabaseV4() - - mDatabase.changeDuplicateId = fixDuplicateUUID - - val header = PwDbHeaderV4(mDatabase) - - val headerAndHash = header.loadFromFile(databaseInputStream) - version = header.version - - hashOfHeader = headerAndHash.hash - val pbHeader = headerAndHash.header - - mDatabase.retrieveMasterKey(password, keyInputStream) - mDatabase.makeFinalKey(header.masterSeed) - // TODO performance - - progressTaskUpdater?.updateMessage(R.string.decrypting_db) - val engine: CipherEngine - val cipher: Cipher try { - engine = CipherFactory.getInstance(mDatabase.dataCipher) - mDatabase.setDataEngine(engine) - mDatabase.encryptionAlgorithm = engine.getPwEncryptionAlgorithm() - cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV) - } catch (e: NoSuchAlgorithmException) { - throw IOException("Invalid algorithm.", e) - } catch (e: NoSuchPaddingException) { - throw IOException("Invalid algorithm.", e) - } catch (e: InvalidKeyException) { - throw IOException("Invalid algorithm.", e) - } catch (e: InvalidAlgorithmParameterException) { - throw IOException("Invalid algorithm.", e) - } + // TODO performance + progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) - val isPlain: InputStream - if (version < PwDbHeaderV4.FILE_VERSION_32_4) { + mDatabase = PwDatabaseV4() - val decrypted = attachCipherStream(databaseInputStream, cipher) - val dataDecrypted = LEDataInputStream(decrypted) - val storedStartBytes: ByteArray? + mDatabase.changeDuplicateId = fixDuplicateUUID + + val header = PwDbHeaderV4(mDatabase) + + val headerAndHash = header.loadFromFile(databaseInputStream) + version = header.version + + hashOfHeader = headerAndHash.hash + val pbHeader = headerAndHash.header + + mDatabase.retrieveMasterKey(password, keyInputStream) + mDatabase.makeFinalKey(header.masterSeed) + // TODO performance + + progressTaskUpdater?.updateMessage(R.string.decrypting_db) + val engine: CipherEngine + val cipher: Cipher try { - storedStartBytes = dataDecrypted.readBytes(32) - if (storedStartBytes == null || storedStartBytes.size != 32) { - throw LoadDatabaseInvalidPasswordException() + engine = CipherFactory.getInstance(mDatabase.dataCipher) + mDatabase.setDataEngine(engine) + mDatabase.encryptionAlgorithm = engine.getPwEncryptionAlgorithm() + cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV) + } catch (e: Exception) { + throw LoadDatabaseInvalidAlgorithmException(e) + } + + val isPlain: InputStream + if (version < PwDbHeaderV4.FILE_VERSION_32_4) { + + val decrypted = attachCipherStream(databaseInputStream, cipher) + val dataDecrypted = LEDataInputStream(decrypted) + val storedStartBytes: ByteArray? + try { + storedStartBytes = dataDecrypted.readBytes(32) + if (storedStartBytes == null || storedStartBytes.size != 32) { + throw LoadDatabaseInvalidCredentialsException() + } + } catch (e: IOException) { + throw LoadDatabaseInvalidCredentialsException() } - } catch (e: IOException) { - throw LoadDatabaseInvalidPasswordException() + + if (!Arrays.equals(storedStartBytes, header.streamStartBytes)) { + throw LoadDatabaseInvalidCredentialsException() + } + + isPlain = HashedBlockInputStream(dataDecrypted) + } else { // KDBX 4 + val isData = LEDataInputStream(databaseInputStream) + val storedHash = isData.readBytes(32) + if (!Arrays.equals(storedHash, hashOfHeader)) { + throw LoadDatabaseInvalidCredentialsException() + } + + val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException() + val headerHmac = PwDbHeaderV4.computeHeaderHmac(pbHeader, hmacKey) + val storedHmac = isData.readBytes(32) + if (storedHmac == null || storedHmac.size != 32) { + throw LoadDatabaseInvalidCredentialsException() + } + // Mac doesn't match + if (!Arrays.equals(headerHmac, storedHmac)) { + throw LoadDatabaseInvalidCredentialsException() + } + + val hmIs = HmacBlockInputStream(isData, true, hmacKey) + + isPlain = attachCipherStream(hmIs, cipher) } - if (!Arrays.equals(storedStartBytes, header.streamStartBytes)) { - throw LoadDatabaseInvalidPasswordException() + val inputStreamXml: InputStream + inputStreamXml = when (mDatabase.compressionAlgorithm) { + PwCompressionAlgorithm.GZip -> GZIPInputStream(isPlain) + else -> isPlain } - isPlain = HashedBlockInputStream(dataDecrypted) - } else { // KDBX 4 - val isData = LEDataInputStream(databaseInputStream) - val storedHash = isData.readBytes(32) - if (!Arrays.equals(storedHash, hashOfHeader)) { - throw LoadDatabaseException() + if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { + loadInnerHeader(inputStreamXml, header) } - val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException() - val headerHmac = PwDbHeaderV4.computeHeaderHmac(pbHeader, hmacKey) - val storedHmac = isData.readBytes(32) - if (storedHmac == null || storedHmac.size != 32) { - throw LoadDatabaseException() - } - // Mac doesn't match - if (!Arrays.equals(headerHmac, storedHmac)) { - throw LoadDatabaseException() + randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) + + if (randomStream == null) { + throw LoadDatabaseArcFourException() } - val hmIs = HmacBlockInputStream(isData, true, hmacKey) + readDocumentStreamed(createPullParser(inputStreamXml)) - isPlain = attachCipherStream(hmIs, cipher) + } catch (e: LoadDatabaseException) { + throw e + } catch (e: XmlPullParserException) { + throw LoadDatabaseIOException(e) + } catch (e: IOException) { + if (e.message?.contains("Hash failed with code") == true) + throw LoadDatabaseKDFMemoryException(e) + else + throw LoadDatabaseIOException(e) + } catch (e: OutOfMemoryError) { + throw LoadDatabaseNoMemoryException(e) + } catch (e: Exception) { + throw LoadDatabaseException(e) } - val isXml: InputStream - isXml = when(mDatabase.compressionAlgorithm) { - PwCompressionAlgorithm.GZip -> GZIPInputStream(isPlain) - else -> isPlain - } - - if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { - loadInnerHeader(isXml, header) - } - - randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) - - if (randomStream == null) { - throw LoadDatabaseArcFourException() - } - - readXmlStreamed(isXml) - return mDatabase } @@ -274,17 +277,6 @@ class ImporterV4(private val streamDir: File, Binaries } - @Throws(IOException::class, LoadDatabaseException::class) - private fun readXmlStreamed(readerStream: InputStream) { - try { - readDocumentStreamed(createPullParser(readerStream)) - } catch (e: XmlPullParserException) { - e.printStackTrace() - throw IOException(e.localizedMessage) - } - - } - @Throws(XmlPullParserException::class, IOException::class, LoadDatabaseException::class) private fun readDocumentStreamed(xpp: XmlPullParser) { @@ -1003,7 +995,7 @@ class ImporterV4(private val streamDir: File, try { return String(buf, Charset.forName("UTF-8")) } catch (e: UnsupportedEncodingException) { - throw IOException(e.localizedMessage) + throw IOException(e) } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index bf891c822..2d8643462 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -148,7 +148,7 @@ غير قادر على نقل مجموعة إلى نفسها. تعذر إيجاد الملف. جرِّب فتحه من متصفح ملفات. متصفح الملفات - تعذرت قراءة كلمة السر أو ملف المفتاح. + تعذرت قراءة كلمة السر أو ملف المفتاح. تعذر تمييز نسق قاعدة البيانات. لا يوجد ملف مفتاح. ملف المفتاح فارغ. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 98deec432..a5a113977 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -82,7 +82,7 @@ Contrasenya Instal·la de la Play Store Instal·la de la F-Droid - Contrasenya o arxiu clau invàlids. + Contrasenya o arxiu clau invàlids. Format de base de dades desconegut. Longitud Mida de la llista de grups diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index f11a40238..02dff13f0 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -89,7 +89,7 @@ Heslo Instalovat z katalogu Play Store Instalovat z katalogu F-Droid - Nesprávné heslo nebo soubor s klíčem. + Nesprávné heslo nebo soubor s klíčem. Nesprávný algoritmus. Nedaří se rozpoznat formát databáze. Soubor s klíčem neexistuje. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index ac9d55474..d9a4d8e85 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -88,7 +88,7 @@ Adgangskode Installer fra Google Play Installer fra F-Droid - Ugyldig adgangskode eller nøglefil. + Ugyldig adgangskode eller nøglefil. Forkert algoritme. Kunne ikke genkende databaseformat. Nøglefil eksisterer ikke. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e459e4356..0298d5221 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -92,7 +92,7 @@ Passwort Google Play F-Droid - Passwort oder Schlüsseldatei ist falsch. + Passwort oder Schlüsseldatei ist falsch. Falscher Algorithmus. Datenbankformat nicht erkannt. Es existiert keine Schlüsseldatei. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index eaed388d2..d341c84de 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -87,7 +87,7 @@ Κωδικός Πρόσβασης Εγκατάσταση από το Play Store Εγκατάσταση από το F-Droid - Εσφαλμένος κωδικός ή αρχείο κλειδιού. + Εσφαλμένος κωδικός ή αρχείο κλειδιού. Εσφαλμένος αλγόριθμος. Η μορφή της Βάσης Δεδομένων δεν αναγνωρίζεται. Δεν υπάρχει αρχείο κλειδιού. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d3a94100c..59ed0e747 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -81,7 +81,7 @@ contraseña Instalar desde Play Store Instalar desde F-Droid - Contraseña o archivo de clave no válido. + Contraseña o archivo de clave no válido. Formato de base de datos no reconocido. Longitud Tamaño de la lista de Grupo diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 7a968d736..4dfb63050 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -88,7 +88,7 @@ Pasahitza Play store-etik Instalatu F-Droid-etik Instalatu - Pasahitz edo gako fitxategi baliogabea. + Pasahitz edo gako fitxategi baliogabea. Algoritmo baliogabea. Datubase formato ez ezaguna. Gako fitxategia ez da esistitzen. diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index ec3196f1b..17d93f582 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -87,7 +87,7 @@ Salasana Asenna Play Storesta Asenna F-Droid - Väärä salasana tai avaintiedosto. + Väärä salasana tai avaintiedosto. Epäkelpo algoritmi. Salasanatietokannan tyyppiä ei tunnistettu. Avaintiedostoa ei ole olemassa. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 934953fe7..3109f9f0b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -94,7 +94,7 @@ Mot de passe Installer depuis Google Play Installer depuis F-Droid - Impossible de lire le mot de passe ou le fichier clé. + Impossible de lire le mot de passe ou le fichier clé. Mauvais algorithme. Impossible de reconnaître le format de la base de données. Aucun fichier de clé n’existe. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 3ebaf1683..1827598c0 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -87,7 +87,7 @@ Jelszó Telepítés a Play Áruházból Telepítés az F-Droidból - A jelszó vagy kulcsfájl nem olvasható. + A jelszó vagy kulcsfájl nem olvasható. Hibás algoritmus. Az adatbázis formátuma nem ismerhető fel. Nem létezik kulcsfájl. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3edc21733..44aaf6a7a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -88,7 +88,7 @@ password Installa dal Play Store Installa dal F-Droid - Password o file chiave non validi. + Password o file chiave non validi. Algoritmo errato. Formato database non riconosciuto. Non esiste alcun file chiave. diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index cf95d67a9..c5cffcf61 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -84,7 +84,7 @@ סיסמה התקן מחנות Play התקן מהאינטרנט - סיסמה או קובץ מפתח לא מתאימים. + סיסמה או קובץ מפתח לא מתאימים. אלגוריתם לא חוקי. תבנית מסד הנתונים אינה מזוהה. קובץ המפתח לא קיים. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c4298a2c2..79db6b054 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -78,7 +78,7 @@ パスワード Play Storeで入手 F-Droidで入手 - パスワード/キーファイルが違います。 + パスワード/キーファイルが違います。 データベースフォーマットが認識できません。 生成するパスワードの長さ グループ一覧サイズ diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 5d30cb9e8..50f09a5c4 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -96,7 +96,7 @@ 비밀번호 F-Droid에서 설치 플레이 스토어에서 설치 - 비밀번호나 키 파일을 읽을 수 없습니다. + 비밀번호나 키 파일을 읽을 수 없습니다. 잘못된 알고리즘입니다. 데이터베이스 형식을 인식할 수 없습니다. 키 파일이 없습니다. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index ff9a7e1f2..0b7b627f7 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -2,7 +2,7 @@ KeePass DX yra KeePass slaptažodžių tvarkyklės realizacija Android platformai Iškarpinė išvalyta. - Neteisingas slaptažodis arba rakto failas. + Neteisingas slaptažodis arba rakto failas. Atsiliepimai: Pagrindinis puslapis: Priimti diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index b3b6be234..2a58ed703 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -67,7 +67,7 @@ Parole Instalēt no Play veikala Instalēt no F-Droid - Nederīga parole vai atslēgas fails. + Nederīga parole vai atslēgas fails. Nederīgs algoritms. Datu bāzes formāts nav atpazīts. Atslēgas fails nepastāv. diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index f913060f1..c3dcc97bc 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -96,7 +96,7 @@ Passord Installer fra Play-butikken Installer fra F-Droid - Ugyldig passord eller nøkkelfil. + Ugyldig passord eller nøkkelfil. Ugyldig algoritme. Fremmed databaseformat. Ingen nøkkelfil finnes. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index f1607444c..b8f6eefe4 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -81,7 +81,7 @@ wachtwoord Installeren via Play Store Installeren via F-Droid - Ongeldig wachtwoord of sleutelbestand. + Ongeldig wachtwoord of sleutelbestand. Databankformaat kan niet worden herkend. Lengte Grootte van itemlijst diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index 3ea0729eb..0d3ddb9ae 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -79,7 +79,7 @@ passord Installer frå Play Store Installer frå F-Droid - Ugyldig passord eller nøkkelfil. + Ugyldig passord eller nøkkelfil. Ukjent databaseformat. Lengd Gruppelistestorleik diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 78c8b9304..a4445c648 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -78,7 +78,7 @@ hasło Zainstaluj z Play-Store Zainstaluj z F-Droid - Nieprawidłowe hasło lub plik klucza. + Nieprawidłowe hasło lub plik klucza. Nie można rozpoznać formatu bazy danych. Długość Wielkość listy grup diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 1caf84b35..bac9d7bfb 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -81,7 +81,7 @@ senha Instalar a partir do Google Play Instalar a partir do F-Droid - Senha ou arquivo de chaves inválidos. + Senha ou arquivo de chaves inválidos. Não pôde reconhecer formato do banco de dados. Tamanho Tamanho da lista de grupos diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index bbfba6a7c..176711ad6 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -88,7 +88,7 @@ Palavra-chave Instalar pela Play Store Instalar pela F-Droid - Palavra-chave ou ficheiro chave inválidos. + Palavra-chave ou ficheiro chave inválidos. Algoritmo errado. Não pôde reconhecer formato do banco de dados. Não existem ficheiros-chave. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b7ce3614b..15a420ee7 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -88,7 +88,7 @@ Пароль Google Play F-Droid - Неверный пароль или файл ключа. + Неверный пароль или файл ключа. Неверный алгоритм. Не удалось определить формат базы. Файл ключа не найден. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 1d3ec2b72..aaa0fd1fe 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -78,7 +78,7 @@ heslo Inštalovať z Play Store Inštalovať z F-Droid - Chybné heslo, alebo súbor keyfile. + Chybné heslo, alebo súbor keyfile. Formát Databázy nerozpoznaný. Dĺžka Dĺžka zoznamu skupiny diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 269f46802..12edf7f84 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -87,7 +87,7 @@ Lösenord Installera från Play Store Installera från F-Droid - Ogiltigt lösenord eller nyckelfil. + Ogiltigt lösenord eller nyckelfil. Ogiltig algoritm. Databasformatet är okänt. Nyckelfilen existerar inte. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index e3284f354..17010db79 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -96,7 +96,7 @@ Parola F-Droid\'den yükleyin "Play Store\'dan yükleyin " - Parola veya anahtar dosya okunamadı. + Parola veya anahtar dosya okunamadı. Yanlış algoritma. Veritabanı biçimi tanımlanamadı. Hiç anahtar dosya yok. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index e6a70ad6b..52a7816c0 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -79,7 +79,7 @@ пароль Інсталювати із Google Play Інсталювати із F-Droid - Невірний пароль або файл ключа. + Невірний пароль або файл ключа. Формат бази даних не розпізнано. Довжина Розмір списку груп diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fcd56c444..987c11240 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -79,7 +79,7 @@ 密码 从 Play 商店安装 从 F-Droid 安装 - 无法读取密码或密钥文件。 + 无法读取密码或密钥文件。 无法识别数据库格式。 长度 列表项目尺寸 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 5cfc8d24d..a0a211159 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -78,7 +78,7 @@ 密碼 從 Play 商店安裝 從 F-Droid 安裝 - 無效的密碼或密鑰檔。 + 無效的密碼或密鑰檔。 資料庫格式無法識別。 長度 群列表尺寸 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bdcead124..493f2b350 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -125,7 +125,7 @@ Password Install from F-Droid Install from Play Store - Could not read password or keyfile. + Could not read credentials. Wrong algorithm. %1$s with the same UUID %2$s already exists. Could not recognize the database format.