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
|
import com.kunzisoft.keepass.crypto.CipherFactory.deviceBlacklisted
|
||||||
|
|
||||||
object AESKeyTransformerFactory : KeyTransformer() {
|
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
|
// Prefer the native final key implementation
|
||||||
val keyTransformer = if (!deviceBlacklisted()
|
val keyTransformer = if (!deviceBlacklisted()
|
||||||
&& NativeAESKeyTransformer.available()) {
|
&& 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 {
|
abstract class KeyTransformer {
|
||||||
@Throws(IOException::class)
|
@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 com.kunzisoft.keepass.crypto.NativeLib;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
@@ -30,8 +32,9 @@ public class NativeAESKeyTransformer extends KeyTransformer {
|
|||||||
return NativeLib.INSTANCE.init();
|
return NativeLib.INSTANCE.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@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();
|
NativeLib.INSTANCE.init();
|
||||||
|
|
||||||
return nTransformMasterKey(seed, key, rounds);
|
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.CryptoUtil
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
|
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
|
||||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class AesKdf internal constructor() : KdfEngine() {
|
class AesKdf : KdfEngine() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
uuid = CIPHER_UUID
|
||||||
|
}
|
||||||
|
|
||||||
override val defaultParameters: KdfParameters
|
override val defaultParameters: KdfParameters
|
||||||
get() {
|
get() {
|
||||||
return KdfParameters(uuid!!).apply {
|
return KdfParameters(uuid!!).apply {
|
||||||
setParamUUID()
|
setParamUUID()
|
||||||
setUInt32(PARAM_ROUNDS, UnsignedInt.fromLong(defaultKeyRounds))
|
setUInt64(PARAM_ROUNDS, defaultKeyRounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultKeyRounds: Long = 6000L
|
override val defaultKeyRounds: Long = 6000L
|
||||||
|
|
||||||
init {
|
|
||||||
uuid = CIPHER_UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getName(resources: Resources): String {
|
override fun getName(resources: Resources): String {
|
||||||
return resources.getString(R.string.kdf_AES)
|
return resources.getString(R.string.kdf_AES)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
|
||||||
var currentMasterKey = masterKey
|
|
||||||
val rounds = p.getUInt64(PARAM_ROUNDS)
|
|
||||||
var seed = p.getByteArray(PARAM_SEED)
|
|
||||||
|
|
||||||
|
var seed = kdfParameters.getByteArray(PARAM_SEED)
|
||||||
|
if (seed != null && seed.size != 32) {
|
||||||
|
seed = CryptoUtil.hashSha256(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentMasterKey = masterKey
|
||||||
if (currentMasterKey.size != 32) {
|
if (currentMasterKey.size != 32) {
|
||||||
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
|
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seed.size != 32) {
|
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)
|
||||||
seed = CryptoUtil.hashSha256(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
|
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun randomize(p: KdfParameters) {
|
override fun randomize(kdfParameters: KdfParameters) {
|
||||||
val random = SecureRandom()
|
val random = SecureRandom()
|
||||||
|
|
||||||
val seed = ByteArray(32)
|
val seed = ByteArray(32)
|
||||||
random.nextBytes(seed)
|
random.nextBytes(seed)
|
||||||
|
|
||||||
p.setByteArray(PARAM_SEED, seed)
|
kdfParameters.setByteArray(PARAM_SEED, seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeyRounds(p: KdfParameters): Long {
|
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
|
||||||
return p.getUInt64(PARAM_ROUNDS)
|
return kdfParameters.getUInt64(PARAM_ROUNDS) ?: defaultKeyRounds
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
|
||||||
p.setUInt64(PARAM_ROUNDS, keyRounds)
|
kdfParameters.setUInt64(PARAM_ROUNDS, keyRounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -103,7 +103,7 @@ class AesKdf internal constructor() : KdfEngine() {
|
|||||||
0x4F.toByte(),
|
0x4F.toByte(),
|
||||||
0xEA.toByte()))
|
0xEA.toByte()))
|
||||||
|
|
||||||
const val PARAM_ROUNDS = "R"
|
const val PARAM_ROUNDS = "R" // UInt64
|
||||||
const val PARAM_SEED = "S"
|
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.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -54,15 +54,23 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@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 salt = kdfParameters.getByteArray(PARAM_SALT)
|
||||||
val parallelism = UnsignedInt(p.getUInt32(PARAM_PARALLELISM))
|
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
|
||||||
val memory = UnsignedInt.fromLong(p.getUInt64(PARAM_MEMORY) / MEMORY_BLOCK_SIZE)
|
UnsignedInt(it)
|
||||||
val iterations = UnsignedInt.fromLong(p.getUInt64(PARAM_ITERATIONS))
|
}
|
||||||
val version = UnsignedInt(p.getUInt32(PARAM_VERSION))
|
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.div(MEMORY_BLOCK_SIZE)?.let {
|
||||||
val secretKey = p.getByteArray(PARAM_SECRET_KEY)
|
UnsignedInt.fromLong(it)
|
||||||
val assocData = p.getByteArray(PARAM_ASSOC_DATA)
|
}
|
||||||
|
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,
|
return Argon2Native.transformKey(masterKey,
|
||||||
salt,
|
salt,
|
||||||
@@ -74,21 +82,21 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
version)
|
version)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun randomize(p: KdfParameters) {
|
override fun randomize(kdfParameters: KdfParameters) {
|
||||||
val random = SecureRandom()
|
val random = SecureRandom()
|
||||||
|
|
||||||
val salt = ByteArray(32)
|
val salt = ByteArray(32)
|
||||||
random.nextBytes(salt)
|
random.nextBytes(salt)
|
||||||
|
|
||||||
p.setByteArray(PARAM_SALT, salt)
|
kdfParameters.setByteArray(PARAM_SALT, salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeyRounds(p: KdfParameters): Long {
|
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
|
||||||
return p.getUInt64(PARAM_ITERATIONS)
|
return kdfParameters.getUInt64(PARAM_ITERATIONS) ?: defaultKeyRounds
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
|
||||||
p.setUInt64(PARAM_ITERATIONS, keyRounds)
|
kdfParameters.setUInt64(PARAM_ITERATIONS, keyRounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val minKeyRounds: Long
|
override val minKeyRounds: Long
|
||||||
@@ -97,12 +105,12 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
override val maxKeyRounds: Long
|
override val maxKeyRounds: Long
|
||||||
get() = MAX_ITERATIONS
|
get() = MAX_ITERATIONS
|
||||||
|
|
||||||
override fun getMemoryUsage(p: KdfParameters): Long {
|
override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
|
||||||
return p.getUInt64(PARAM_MEMORY)
|
return kdfParameters.getUInt64(PARAM_MEMORY) ?: defaultMemoryUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setMemoryUsage(p: KdfParameters, memory: Long) {
|
override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
|
||||||
p.setUInt64(PARAM_MEMORY, memory)
|
kdfParameters.setUInt64(PARAM_MEMORY, memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultMemoryUsage: Long
|
override val defaultMemoryUsage: Long
|
||||||
@@ -114,12 +122,14 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
override val maxMemoryUsage: Long
|
override val maxMemoryUsage: Long
|
||||||
get() = MAX_MEMORY
|
get() = MAX_MEMORY
|
||||||
|
|
||||||
override fun getParallelism(p: KdfParameters): Long {
|
override fun getParallelism(kdfParameters: KdfParameters): Long {
|
||||||
return UnsignedInt(p.getUInt32(PARAM_PARALLELISM)).toLong()
|
return kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
|
||||||
|
UnsignedInt(it).toLong()
|
||||||
|
} ?: defaultParallelism
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setParallelism(p: KdfParameters, parallelism: Long) {
|
override fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
|
||||||
p.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
|
kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultParallelism: Long
|
override val defaultParallelism: Long
|
||||||
|
|||||||
@@ -34,17 +34,17 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
|
|||||||
abstract val defaultParameters: KdfParameters
|
abstract val defaultParameters: KdfParameters
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@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
|
* 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
|
abstract val defaultKeyRounds: Long
|
||||||
|
|
||||||
@@ -58,11 +58,11 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
|
|||||||
* MEMORY
|
* MEMORY
|
||||||
*/
|
*/
|
||||||
|
|
||||||
open fun getMemoryUsage(p: KdfParameters): Long {
|
open fun getMemoryUsage(kdfParameters: KdfParameters): Long {
|
||||||
return UNKNOWN_VALUE
|
return UNKNOWN_VALUE
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setMemoryUsage(p: KdfParameters, memory: Long) {
|
open fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
|
||||||
// Do nothing by default
|
// Do nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,11 +79,11 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
|
|||||||
* PARALLELISM
|
* PARALLELISM
|
||||||
*/
|
*/
|
||||||
|
|
||||||
open fun getParallelism(p: KdfParameters): Long {
|
open fun getParallelism(kdfParameters: KdfParameters): Long {
|
||||||
return UNKNOWN_VALUE
|
return UNKNOWN_VALUE
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setParallelism(p: KdfParameters, parallelism: Long) {
|
open fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
|
||||||
// Do nothing by default
|
// Do nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import java.io.ByteArrayOutputStream
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
|
class KdfParameters(val uuid: UUID) : VariantDictionary() {
|
||||||
|
|
||||||
fun setParamUUID() {
|
fun setParamUUID() {
|
||||||
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
|
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
|
||||||
@@ -41,26 +41,25 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
|
|||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun deserialize(data: ByteArray): KdfParameters? {
|
fun deserialize(data: ByteArray): KdfParameters? {
|
||||||
val bis = ByteArrayInputStream(data)
|
val inputStream = LittleEndianDataInputStream(ByteArrayInputStream(data))
|
||||||
val lis = LittleEndianDataInputStream(bis)
|
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 kdfParameters = KdfParameters(uuid)
|
||||||
|
kdfParameters.copyTo(dictionary)
|
||||||
val kdfP = KdfParameters(uuid)
|
return kdfParameters
|
||||||
kdfP.copyTo(d)
|
|
||||||
return kdfP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun serialize(kdf: KdfParameters): ByteArray {
|
fun serialize(kdfParameters: KdfParameters): ByteArray {
|
||||||
val bos = ByteArrayOutputStream()
|
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||||
val los = LittleEndianDataOutputStream(bos)
|
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)
|
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED)
|
||||||
private set(seed) {
|
private set(seed) {
|
||||||
assignAesKdfEngineIfNotExists()
|
assignAesKdfEngineIfNotExists()
|
||||||
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, seed)
|
seed?.let {
|
||||||
|
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object PwDbHeaderV4Fields {
|
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