mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Kotlinized VariantDictionnary and fix database creation
This commit is contained in:
@@ -22,7 +22,7 @@ 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? {
|
||||
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
|
||||
// Prefer the native final key implementation
|
||||
val keyTransformer = if (!deviceBlacklisted()
|
||||
&& NativeAESKeyTransformer.available()) {
|
||||
|
||||
@@ -1,78 +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 java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class AndroidAESKeyTransformer extends KeyTransformer {
|
||||
|
||||
@Override
|
||||
public byte[] transformMasterKey(byte[] pKeySeed, byte[] pKey, long rounds) throws IOException {
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IOException("NoSuchAlgorithm: " + e.getMessage());
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new IOException("NoSuchPadding: " + e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(pKeySeed, "AES"));
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new IOException("InvalidPasswordException: " + e.getMessage());
|
||||
}
|
||||
|
||||
// Encrypt key rounds times
|
||||
byte[] newKey = new byte[pKey.length];
|
||||
System.arraycopy(pKey, 0, newKey, 0, pKey.length);
|
||||
byte[] destKey = new byte[pKey.length];
|
||||
for (int i = 0; i < rounds; i++) {
|
||||
try {
|
||||
cipher.update(newKey, 0, newKey.length, destKey, 0);
|
||||
System.arraycopy(destKey, 0, newKey, 0, newKey.length);
|
||||
|
||||
} catch (ShortBufferException e) {
|
||||
throw new IOException("Short buffer: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Hash the key
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
assert true;
|
||||
throw new IOException("SHA-256 not implemented here: " + e.getMessage());
|
||||
}
|
||||
|
||||
md.update(newKey);
|
||||
return md.digest();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2020 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 java.io.IOException
|
||||
import java.lang.Exception
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.NoSuchPaddingException
|
||||
import javax.crypto.ShortBufferException
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class AndroidAESKeyTransformer : KeyTransformer() {
|
||||
@Throws(IOException::class)
|
||||
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
|
||||
val cipher: Cipher = try {
|
||||
Cipher.getInstance("AES/ECB/NoPadding")
|
||||
} catch (e: Exception) {
|
||||
throw IOException("Unable to get the cipher", e)
|
||||
}
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(seed, "AES"))
|
||||
} catch (e: InvalidKeyException) {
|
||||
throw IOException("Unable to init the cipher", e)
|
||||
}
|
||||
if (key == null) {
|
||||
throw IOException("Invalid key")
|
||||
}
|
||||
if (rounds == null) {
|
||||
throw IOException("Invalid rounds")
|
||||
}
|
||||
|
||||
// Encrypt key rounds times
|
||||
val keyLength = key.size
|
||||
val newKey = ByteArray(keyLength)
|
||||
System.arraycopy(key, 0, newKey, 0, keyLength)
|
||||
val destKey = ByteArray(keyLength)
|
||||
for (i in 0 until rounds) {
|
||||
try {
|
||||
cipher.update(newKey, 0, newKey.size, destKey, 0)
|
||||
System.arraycopy(destKey, 0, newKey, 0, newKey.size)
|
||||
} catch (e: ShortBufferException) {
|
||||
throw IOException("Short buffer", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Hash the key
|
||||
val messageDigest: MessageDigest = try {
|
||||
MessageDigest.getInstance("SHA-256")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw IOException("SHA-256 not implemented here: " + e.message)
|
||||
}
|
||||
messageDigest.update(newKey)
|
||||
return messageDigest.digest()
|
||||
}
|
||||
}
|
||||
@@ -23,5 +23,5 @@ import java.io.IOException
|
||||
|
||||
abstract class KeyTransformer {
|
||||
@Throws(IOException::class)
|
||||
abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long): ByteArray?
|
||||
abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray?
|
||||
}
|
||||
@@ -21,6 +21,8 @@ package com.kunzisoft.keepass.crypto.finalkey;
|
||||
|
||||
import com.kunzisoft.keepass.crypto.NativeLib;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -30,8 +32,9 @@ public class NativeAESKeyTransformer extends KeyTransformer {
|
||||
return NativeLib.INSTANCE.init();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException {
|
||||
public byte[] transformMasterKey(@Nullable byte[] seed, @Nullable byte[] key, @Nullable Long rounds) throws IOException {
|
||||
NativeLib.INSTANCE.init();
|
||||
|
||||
return nTransformMasterKey(seed, key, rounds);
|
||||
|
||||
@@ -24,63 +24,63 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.crypto.CryptoUtil
|
||||
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
|
||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
class AesKdf internal constructor() : KdfEngine() {
|
||||
class AesKdf : KdfEngine() {
|
||||
|
||||
init {
|
||||
uuid = CIPHER_UUID
|
||||
}
|
||||
|
||||
override val defaultParameters: KdfParameters
|
||||
get() {
|
||||
return KdfParameters(uuid!!).apply {
|
||||
setParamUUID()
|
||||
setUInt32(PARAM_ROUNDS, UnsignedInt.fromLong(defaultKeyRounds))
|
||||
setUInt64(PARAM_ROUNDS, defaultKeyRounds)
|
||||
}
|
||||
}
|
||||
|
||||
override val defaultKeyRounds: Long = 6000L
|
||||
|
||||
init {
|
||||
uuid = CIPHER_UUID
|
||||
}
|
||||
|
||||
override fun getName(resources: Resources): String {
|
||||
return resources.getString(R.string.kdf_AES)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
||||
var currentMasterKey = masterKey
|
||||
val rounds = p.getUInt64(PARAM_ROUNDS)
|
||||
var seed = p.getByteArray(PARAM_SEED)
|
||||
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
|
||||
|
||||
var seed = kdfParameters.getByteArray(PARAM_SEED)
|
||||
if (seed != null && seed.size != 32) {
|
||||
seed = CryptoUtil.hashSha256(seed)
|
||||
}
|
||||
|
||||
var currentMasterKey = masterKey
|
||||
if (currentMasterKey.size != 32) {
|
||||
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
|
||||
}
|
||||
|
||||
if (seed.size != 32) {
|
||||
seed = CryptoUtil.hashSha256(seed)
|
||||
}
|
||||
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)
|
||||
|
||||
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
|
||||
}
|
||||
|
||||
override fun randomize(p: KdfParameters) {
|
||||
override fun randomize(kdfParameters: KdfParameters) {
|
||||
val random = SecureRandom()
|
||||
|
||||
val seed = ByteArray(32)
|
||||
random.nextBytes(seed)
|
||||
|
||||
p.setByteArray(PARAM_SEED, seed)
|
||||
kdfParameters.setByteArray(PARAM_SEED, seed)
|
||||
}
|
||||
|
||||
override fun getKeyRounds(p: KdfParameters): Long {
|
||||
return p.getUInt64(PARAM_ROUNDS)
|
||||
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
|
||||
return kdfParameters.getUInt64(PARAM_ROUNDS) ?: defaultKeyRounds
|
||||
}
|
||||
|
||||
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
||||
p.setUInt64(PARAM_ROUNDS, keyRounds)
|
||||
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
|
||||
kdfParameters.setUInt64(PARAM_ROUNDS, keyRounds)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -103,7 +103,7 @@ class AesKdf internal constructor() : KdfEngine() {
|
||||
0x4F.toByte(),
|
||||
0xEA.toByte()))
|
||||
|
||||
const val PARAM_ROUNDS = "R"
|
||||
const val PARAM_SEED = "S"
|
||||
const val PARAM_ROUNDS = "R" // UInt64
|
||||
const val PARAM_SEED = "S" // Byte array
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
@@ -54,15 +54,23 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
||||
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
|
||||
|
||||
val salt = p.getByteArray(PARAM_SALT)
|
||||
val parallelism = UnsignedInt(p.getUInt32(PARAM_PARALLELISM))
|
||||
val memory = UnsignedInt.fromLong(p.getUInt64(PARAM_MEMORY) / MEMORY_BLOCK_SIZE)
|
||||
val iterations = UnsignedInt.fromLong(p.getUInt64(PARAM_ITERATIONS))
|
||||
val version = UnsignedInt(p.getUInt32(PARAM_VERSION))
|
||||
val secretKey = p.getByteArray(PARAM_SECRET_KEY)
|
||||
val assocData = p.getByteArray(PARAM_ASSOC_DATA)
|
||||
val salt = kdfParameters.getByteArray(PARAM_SALT)
|
||||
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
|
||||
UnsignedInt(it)
|
||||
}
|
||||
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.div(MEMORY_BLOCK_SIZE)?.let {
|
||||
UnsignedInt.fromLong(it)
|
||||
}
|
||||
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.let {
|
||||
UnsignedInt.fromLong(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(masterKey,
|
||||
salt,
|
||||
@@ -74,21 +82,21 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
version)
|
||||
}
|
||||
|
||||
override fun randomize(p: KdfParameters) {
|
||||
override fun randomize(kdfParameters: KdfParameters) {
|
||||
val random = SecureRandom()
|
||||
|
||||
val salt = ByteArray(32)
|
||||
random.nextBytes(salt)
|
||||
|
||||
p.setByteArray(PARAM_SALT, salt)
|
||||
kdfParameters.setByteArray(PARAM_SALT, salt)
|
||||
}
|
||||
|
||||
override fun getKeyRounds(p: KdfParameters): Long {
|
||||
return p.getUInt64(PARAM_ITERATIONS)
|
||||
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
|
||||
return kdfParameters.getUInt64(PARAM_ITERATIONS) ?: defaultKeyRounds
|
||||
}
|
||||
|
||||
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
||||
p.setUInt64(PARAM_ITERATIONS, keyRounds)
|
||||
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
|
||||
kdfParameters.setUInt64(PARAM_ITERATIONS, keyRounds)
|
||||
}
|
||||
|
||||
override val minKeyRounds: Long
|
||||
@@ -97,12 +105,12 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
override val maxKeyRounds: Long
|
||||
get() = MAX_ITERATIONS
|
||||
|
||||
override fun getMemoryUsage(p: KdfParameters): Long {
|
||||
return p.getUInt64(PARAM_MEMORY)
|
||||
override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
|
||||
return kdfParameters.getUInt64(PARAM_MEMORY) ?: defaultMemoryUsage
|
||||
}
|
||||
|
||||
override fun setMemoryUsage(p: KdfParameters, memory: Long) {
|
||||
p.setUInt64(PARAM_MEMORY, memory)
|
||||
override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
|
||||
kdfParameters.setUInt64(PARAM_MEMORY, memory)
|
||||
}
|
||||
|
||||
override val defaultMemoryUsage: Long
|
||||
@@ -114,12 +122,14 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
override val maxMemoryUsage: Long
|
||||
get() = MAX_MEMORY
|
||||
|
||||
override fun getParallelism(p: KdfParameters): Long {
|
||||
return UnsignedInt(p.getUInt32(PARAM_PARALLELISM)).toLong()
|
||||
override fun getParallelism(kdfParameters: KdfParameters): Long {
|
||||
return kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
|
||||
UnsignedInt(it).toLong()
|
||||
} ?: defaultParallelism
|
||||
}
|
||||
|
||||
override fun setParallelism(p: KdfParameters, parallelism: Long) {
|
||||
p.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
|
||||
override fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
|
||||
kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
|
||||
}
|
||||
|
||||
override val defaultParallelism: Long
|
||||
|
||||
@@ -34,17 +34,17 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
|
||||
abstract val defaultParameters: KdfParameters
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray
|
||||
abstract fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray
|
||||
|
||||
abstract fun randomize(p: KdfParameters)
|
||||
abstract fun randomize(kdfParameters: KdfParameters)
|
||||
|
||||
/*
|
||||
* ITERATIONS
|
||||
*/
|
||||
|
||||
abstract fun getKeyRounds(p: KdfParameters): Long
|
||||
abstract fun getKeyRounds(kdfParameters: KdfParameters): Long
|
||||
|
||||
abstract fun setKeyRounds(p: KdfParameters, keyRounds: Long)
|
||||
abstract fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long)
|
||||
|
||||
abstract val defaultKeyRounds: Long
|
||||
|
||||
@@ -58,11 +58,11 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
|
||||
* MEMORY
|
||||
*/
|
||||
|
||||
open fun getMemoryUsage(p: KdfParameters): Long {
|
||||
open fun getMemoryUsage(kdfParameters: KdfParameters): Long {
|
||||
return UNKNOWN_VALUE
|
||||
}
|
||||
|
||||
open fun setMemoryUsage(p: KdfParameters, memory: Long) {
|
||||
open fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
@@ -79,11 +79,11 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
|
||||
* PARALLELISM
|
||||
*/
|
||||
|
||||
open fun getParallelism(p: KdfParameters): Long {
|
||||
open fun getParallelism(kdfParameters: KdfParameters): Long {
|
||||
return UNKNOWN_VALUE
|
||||
}
|
||||
|
||||
open fun setParallelism(p: KdfParameters, parallelism: Long) {
|
||||
open fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
|
||||
class KdfParameters(val uuid: UUID) : VariantDictionary() {
|
||||
|
||||
fun setParamUUID() {
|
||||
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
|
||||
@@ -41,26 +41,25 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun deserialize(data: ByteArray): KdfParameters? {
|
||||
val bis = ByteArrayInputStream(data)
|
||||
val lis = LittleEndianDataInputStream(bis)
|
||||
val inputStream = LittleEndianDataInputStream(ByteArrayInputStream(data))
|
||||
val dictionary = deserialize(inputStream)
|
||||
|
||||
val d = deserialize(lis) ?: return null
|
||||
val uuidBytes = dictionary.getByteArray(PARAM_UUID) ?: return null
|
||||
val uuid = bytes16ToUuid(uuidBytes)
|
||||
|
||||
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
|
||||
|
||||
val kdfP = KdfParameters(uuid)
|
||||
kdfP.copyTo(d)
|
||||
return kdfP
|
||||
val kdfParameters = KdfParameters(uuid)
|
||||
kdfParameters.copyTo(dictionary)
|
||||
return kdfParameters
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun serialize(kdf: KdfParameters): ByteArray {
|
||||
val bos = ByteArrayOutputStream()
|
||||
val los = LittleEndianDataOutputStream(bos)
|
||||
fun serialize(kdfParameters: KdfParameters): ByteArray {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
val outputStream = LittleEndianDataOutputStream(byteArrayOutputStream)
|
||||
|
||||
serialize(kdf, los)
|
||||
serialize(kdfParameters, outputStream)
|
||||
|
||||
return bos.toByteArray()
|
||||
return byteArrayOutputStream.toByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,9 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
||||
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED)
|
||||
private set(seed) {
|
||||
assignAesKdfEngineIfNotExists()
|
||||
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, seed)
|
||||
seed?.let {
|
||||
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, it)
|
||||
}
|
||||
}
|
||||
|
||||
object PwDbHeaderV4Fields {
|
||||
|
||||
@@ -1,237 +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.utils;
|
||||
|
||||
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream;
|
||||
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.kunzisoft.keepass.stream.StreamBytesUtilsKt.bytes4ToUInt;
|
||||
import static com.kunzisoft.keepass.stream.StreamBytesUtilsKt.bytes64ToLong;
|
||||
|
||||
public class VariantDictionary {
|
||||
private static final int VdVersion = 0x0100;
|
||||
private static final int VdmCritical = 0xFF00;
|
||||
private static final int VdmInfo = 0x00FF;
|
||||
private static Charset UTF8Charset = Charset.forName("UTF-8");
|
||||
|
||||
private Map<String, VdType> dict = new HashMap<>();
|
||||
|
||||
private class VdType {
|
||||
public static final byte None = 0x00;
|
||||
public static final byte UInt32 = 0x04;
|
||||
public static final byte UInt64 =0x05;
|
||||
public static final byte Bool =0x08;
|
||||
public static final byte Int32 =0x0C;
|
||||
public static final byte Int64 =0x0D;
|
||||
public static final byte String =0x18;
|
||||
public static final byte ByteArray =0x42;
|
||||
|
||||
public final byte type;
|
||||
public final Object value;
|
||||
|
||||
VdType(byte type, Object value) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getValue(String name) {
|
||||
VdType val = dict.get(name);
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return val.value;
|
||||
}
|
||||
private void putType(byte type, String name, Object value) {
|
||||
dict.put(name, new VdType(type, value));
|
||||
}
|
||||
|
||||
public void setUInt32(String name, UnsignedInt value) { putType(VdType.UInt32, name, value); }
|
||||
public UnsignedInt getUInt32(String name) { return (UnsignedInt)dict.get(name).value; }
|
||||
|
||||
public void setUInt64(String name, long value) { putType(VdType.UInt64, name, value); }
|
||||
public long getUInt64(String name) { return (long)dict.get(name).value; }
|
||||
|
||||
public void setBool(String name, boolean value) { putType(VdType.Bool, name, value); }
|
||||
public boolean getBool(String name) { return (boolean)dict.get(name).value; }
|
||||
|
||||
public void setInt32(String name, int value) { putType(VdType.Int32 ,name, value); }
|
||||
public int getInt32(String name) { return (int)dict.get(name).value; }
|
||||
|
||||
public void setInt64(String name, long value) { putType(VdType.Int64 ,name, value); }
|
||||
public long getInt64(String name) { return (long)dict.get(name).value; }
|
||||
|
||||
public void setString(String name, String value) { putType(VdType.String ,name, value); }
|
||||
public String getString(String name) { return (String)getValue(name); }
|
||||
|
||||
public void setByteArray(String name, byte[] value) { putType(VdType.ByteArray, name, value); }
|
||||
public byte[] getByteArray(String name) { return (byte[])getValue(name); }
|
||||
|
||||
public static VariantDictionary deserialize(LittleEndianDataInputStream lis) throws IOException {
|
||||
VariantDictionary d = new VariantDictionary();
|
||||
|
||||
int version = lis.readUShort();
|
||||
if ((version & VdmCritical) > (VdVersion & VdmCritical)) {
|
||||
throw new IOException("Invalid format");
|
||||
}
|
||||
|
||||
while(true) {
|
||||
int type = lis.read();
|
||||
if (type < 0) {
|
||||
throw new IOException(("Invalid format"));
|
||||
}
|
||||
|
||||
byte bType = (byte)type;
|
||||
if (bType == VdType.None) {
|
||||
break;
|
||||
}
|
||||
|
||||
int nameLen = lis.readUInt().toInt();
|
||||
byte[] nameBuf = lis.readBytes(nameLen);
|
||||
if (nameLen != nameBuf.length) {
|
||||
throw new IOException("Invalid format");
|
||||
}
|
||||
String name = new String(nameBuf, UTF8Charset);
|
||||
|
||||
int valueLen = lis.readUInt().toInt();
|
||||
byte[] valueBuf = lis.readBytes(valueLen);
|
||||
if (valueLen != valueBuf.length) {
|
||||
throw new IOException("Invalid format");
|
||||
}
|
||||
|
||||
switch (bType) {
|
||||
case VdType.UInt32:
|
||||
if (valueLen == 4) {
|
||||
d.setUInt32(name, bytes4ToUInt(valueBuf));
|
||||
}
|
||||
break;
|
||||
case VdType.UInt64:
|
||||
if (valueLen == 8) {
|
||||
d.setUInt64(name, bytes64ToLong(valueBuf));
|
||||
}
|
||||
break;
|
||||
case VdType.Bool:
|
||||
if (valueLen == 1) {
|
||||
d.setBool(name, valueBuf[0] != 0);
|
||||
}
|
||||
break;
|
||||
case VdType.Int32:
|
||||
if (valueLen == 4) {
|
||||
d.setInt32(name, bytes4ToUInt(valueBuf).toInt());
|
||||
}
|
||||
break;
|
||||
case VdType.Int64:
|
||||
if (valueLen == 8) {
|
||||
d.setInt64(name, bytes64ToLong(valueBuf));
|
||||
}
|
||||
break;
|
||||
case VdType.String:
|
||||
d.setString(name, new String(valueBuf, UTF8Charset));
|
||||
break;
|
||||
case VdType.ByteArray:
|
||||
d.setByteArray(name, valueBuf);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public static void serialize(VariantDictionary d,
|
||||
LittleEndianDataOutputStream los) throws IOException{
|
||||
if (los == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
los.writeUShort(VdVersion);
|
||||
|
||||
for (Map.Entry<String, VdType> entry: d.dict.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
byte[] nameBuf = nameBuf = name.getBytes(UTF8Charset);
|
||||
|
||||
VdType vd = entry.getValue();
|
||||
|
||||
los.write(vd.type);
|
||||
los.writeInt(nameBuf.length);
|
||||
los.write(nameBuf);
|
||||
|
||||
byte[] buf;
|
||||
switch (vd.type) {
|
||||
case VdType.UInt32:
|
||||
los.writeInt(4);
|
||||
los.writeUInt((UnsignedInt) vd.value);
|
||||
break;
|
||||
case VdType.UInt64:
|
||||
los.writeInt(8);
|
||||
los.writeLong((long)vd.value);
|
||||
break;
|
||||
case VdType.Bool:
|
||||
los.writeInt(1);
|
||||
byte bool = (boolean)vd.value ? (byte)1 : (byte)0;
|
||||
los.write(bool);
|
||||
break;
|
||||
case VdType.Int32:
|
||||
los.writeInt(4);
|
||||
los.writeInt((int)vd.value);
|
||||
break;
|
||||
case VdType.Int64:
|
||||
los.writeInt(8);
|
||||
los.writeLong((long)vd.value);
|
||||
break;
|
||||
case VdType.String:
|
||||
String value = (String)vd.value;
|
||||
buf = value.getBytes(UTF8Charset);
|
||||
los.writeInt(buf.length);
|
||||
los.write(buf);
|
||||
break;
|
||||
case VdType.ByteArray:
|
||||
buf = (byte[])vd.value;
|
||||
los.writeInt(buf.length);
|
||||
los.write(buf);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
los.write(VdType.None);
|
||||
}
|
||||
|
||||
public void copyTo(VariantDictionary d) {
|
||||
for (Map.Entry<String, VdType> entry : d.dict.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
VdType value = entry.getValue();
|
||||
dict.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return dict.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright 2020 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.LittleEndianDataInputStream
|
||||
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
|
||||
import com.kunzisoft.keepass.stream.bytes4ToUInt
|
||||
import com.kunzisoft.keepass.stream.bytes64ToLong
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
open class VariantDictionary {
|
||||
|
||||
private val dict: MutableMap<String, VdType> = HashMap()
|
||||
|
||||
private fun getValue(name: String): Any? {
|
||||
return dict[name]?.value ?: return null
|
||||
}
|
||||
|
||||
private fun putType(type: Byte, name: String, value: Any) {
|
||||
dict[name] = VdType(type, value)
|
||||
}
|
||||
|
||||
fun setUInt32(name: String, value: UnsignedInt) {
|
||||
putType(VdType.UInt32, name, value)
|
||||
}
|
||||
|
||||
fun getUInt32(name: String): UnsignedInt? {
|
||||
return dict[name]?.value as UnsignedInt?
|
||||
}
|
||||
|
||||
fun setUInt64(name: String, value: Long) {
|
||||
putType(VdType.UInt64, name, value)
|
||||
}
|
||||
|
||||
fun getUInt64(name: String): Long? {
|
||||
return dict[name]?.value as Long?
|
||||
}
|
||||
|
||||
fun setBool(name: String, value: Boolean) {
|
||||
putType(VdType.Bool, name, value)
|
||||
}
|
||||
|
||||
fun getBool(name: String): Boolean? {
|
||||
return dict[name]?.value as Boolean?
|
||||
}
|
||||
|
||||
fun setInt32(name: String, value: Int) {
|
||||
putType(VdType.Int32, name, value)
|
||||
}
|
||||
|
||||
fun getInt32(name: String): Int? {
|
||||
return dict[name]?.value as Int?
|
||||
}
|
||||
|
||||
fun setInt64(name: String, value: Long) {
|
||||
putType(VdType.Int64, name, value)
|
||||
}
|
||||
|
||||
fun getInt64(name: String): Long? {
|
||||
return dict[name]?.value as Long?
|
||||
}
|
||||
|
||||
fun setString(name: String, value: String) {
|
||||
putType(VdType.String, name, value)
|
||||
}
|
||||
|
||||
fun getString(name: String): String? {
|
||||
return getValue(name) as String?
|
||||
}
|
||||
|
||||
fun setByteArray(name: String, value: ByteArray) {
|
||||
putType(VdType.ByteArray, name, value)
|
||||
}
|
||||
|
||||
fun getByteArray(name: String): ByteArray? {
|
||||
return getValue(name) as ByteArray?
|
||||
}
|
||||
|
||||
fun copyTo(d: VariantDictionary) {
|
||||
for ((key, value) in d.dict) {
|
||||
dict[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
fun size(): Int {
|
||||
return dict.size
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val VdVersion = 0x0100
|
||||
private const val VdmCritical = 0xFF00
|
||||
private const val VdmInfo = 0x00FF
|
||||
private val UTF8Charset = Charset.forName("UTF-8")
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun deserialize(inputStream: LittleEndianDataInputStream): VariantDictionary {
|
||||
val dictionary = VariantDictionary()
|
||||
val version = inputStream.readUShort()
|
||||
if (version and VdmCritical > VdVersion and VdmCritical) {
|
||||
throw IOException("Invalid format")
|
||||
}
|
||||
while (true) {
|
||||
val type = inputStream.read()
|
||||
if (type < 0) {
|
||||
throw IOException("Invalid format")
|
||||
}
|
||||
val bType = type.toByte()
|
||||
if (bType == VdType.None) {
|
||||
break
|
||||
}
|
||||
val nameLen = inputStream.readUInt().toInt()
|
||||
val nameBuf = inputStream.readBytes(nameLen)
|
||||
if (nameLen != nameBuf.size) {
|
||||
throw IOException("Invalid format")
|
||||
}
|
||||
val name = String(nameBuf, UTF8Charset)
|
||||
val valueLen = inputStream.readUInt().toInt()
|
||||
val valueBuf = inputStream.readBytes(valueLen)
|
||||
if (valueLen != valueBuf.size) {
|
||||
throw IOException("Invalid format")
|
||||
}
|
||||
when (bType) {
|
||||
VdType.UInt32 -> if (valueLen == 4) {
|
||||
dictionary.setUInt32(name, bytes4ToUInt(valueBuf))
|
||||
}
|
||||
VdType.UInt64 -> if (valueLen == 8) {
|
||||
dictionary.setUInt64(name, bytes64ToLong(valueBuf))
|
||||
}
|
||||
VdType.Bool -> if (valueLen == 1) {
|
||||
dictionary.setBool(name, valueBuf[0] != 0.toByte())
|
||||
}
|
||||
VdType.Int32 -> if (valueLen == 4) {
|
||||
dictionary.setInt32(name, bytes4ToUInt(valueBuf).toInt())
|
||||
}
|
||||
VdType.Int64 -> if (valueLen == 8) {
|
||||
dictionary.setInt64(name, bytes64ToLong(valueBuf))
|
||||
}
|
||||
VdType.String -> dictionary.setString(name, String(valueBuf, UTF8Charset))
|
||||
VdType.ByteArray -> dictionary.setByteArray(name, valueBuf)
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return dictionary
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun serialize(variantDictionary: VariantDictionary,
|
||||
outputStream: LittleEndianDataOutputStream?) {
|
||||
if (outputStream == null) {
|
||||
return
|
||||
}
|
||||
outputStream.writeUShort(VdVersion)
|
||||
for ((name, vd) in variantDictionary.dict) {
|
||||
val nameBuf = name.toByteArray(UTF8Charset)
|
||||
outputStream.write(vd.type.toInt())
|
||||
outputStream.writeInt(nameBuf.size)
|
||||
outputStream.write(nameBuf)
|
||||
var buf: ByteArray
|
||||
when (vd.type) {
|
||||
VdType.UInt32 -> {
|
||||
outputStream.writeInt(4)
|
||||
outputStream.writeUInt((vd.value as UnsignedInt))
|
||||
}
|
||||
VdType.UInt64 -> {
|
||||
outputStream.writeInt(8)
|
||||
outputStream.writeLong(vd.value as Long)
|
||||
}
|
||||
VdType.Bool -> {
|
||||
outputStream.writeInt(1)
|
||||
val bool = if (vd.value as Boolean) 1.toByte() else 0.toByte()
|
||||
outputStream.write(bool.toInt())
|
||||
}
|
||||
VdType.Int32 -> {
|
||||
outputStream.writeInt(4)
|
||||
outputStream.writeInt(vd.value as Int)
|
||||
}
|
||||
VdType.Int64 -> {
|
||||
outputStream.writeInt(8)
|
||||
outputStream.writeLong(vd.value as Long)
|
||||
}
|
||||
VdType.String -> {
|
||||
val value = vd.value as String
|
||||
buf = value.toByteArray(UTF8Charset)
|
||||
outputStream.writeInt(buf.size)
|
||||
outputStream.write(buf)
|
||||
}
|
||||
VdType.ByteArray -> {
|
||||
buf = vd.value as ByteArray
|
||||
outputStream.writeInt(buf.size)
|
||||
outputStream.write(buf)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
outputStream.write(VdType.None.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
class VdType(val type: Byte, val value: Any) {
|
||||
|
||||
companion object {
|
||||
const val None: Byte = 0x00
|
||||
const val UInt32: Byte = 0x04
|
||||
const val UInt64: Byte = 0x05
|
||||
const val Bool: Byte = 0x08
|
||||
const val Int32: Byte = 0x0C
|
||||
const val Int64: Byte = 0x0D
|
||||
const val String: Byte = 0x18
|
||||
const val ByteArray: Byte = 0x42
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user