mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Refactor key encryption methods and add unit tests
This commit is contained in:
@@ -29,8 +29,8 @@ import junit.framework.TestCase
|
|||||||
import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
|
import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
|
import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
|
||||||
|
|
||||||
class FinalKeyTest : TestCase() {
|
class AESKeyTest : TestCase() {
|
||||||
private var mRand: Random? = null
|
private lateinit var mRand: Random
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
@@ -40,29 +40,28 @@ class FinalKeyTest : TestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun testNativeAndroid() {
|
fun testAES() {
|
||||||
// Test both an old and an even number to test my flip variable
|
// Test both an old and an even number to test my flip variable
|
||||||
testNativeFinalKey(5)
|
testAESFinalKey(5)
|
||||||
testNativeFinalKey(6)
|
testAESFinalKey(6)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun testNativeFinalKey(rounds: Int) {
|
private fun testAESFinalKey(rounds: Long) {
|
||||||
val seed = ByteArray(32)
|
val seed = ByteArray(32)
|
||||||
val key = ByteArray(32)
|
val key = ByteArray(32)
|
||||||
val nativeKey: ByteArray
|
val nativeKey: ByteArray?
|
||||||
val androidKey: ByteArray
|
val androidKey: ByteArray?
|
||||||
|
|
||||||
mRand!!.nextBytes(seed)
|
mRand.nextBytes(seed)
|
||||||
mRand!!.nextBytes(key)
|
mRand.nextBytes(key)
|
||||||
|
|
||||||
val aKey = AndroidAESKeyTransformer()
|
val androidAESKey = AndroidAESKeyTransformer()
|
||||||
androidKey = aKey.transformMasterKey(seed, key, rounds.toLong())
|
androidKey = androidAESKey.transformMasterKey(seed, key, rounds)
|
||||||
|
|
||||||
val nKey = NativeAESKeyTransformer()
|
val nativeAESKey = NativeAESKeyTransformer()
|
||||||
nativeKey = nKey.transformMasterKey(seed, key, rounds.toLong())
|
nativeKey = nativeAESKey.transformMasterKey(seed, key, rounds)
|
||||||
|
|
||||||
assertArrayEquals("Does not match", androidKey, nativeKey)
|
assertArrayEquals("Does not match", androidKey, nativeKey)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,4 @@ class AESTest : TestCase() {
|
|||||||
|
|
||||||
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
|
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import junit.framework.TestCase
|
|||||||
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
||||||
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
|
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
|
||||||
|
|
||||||
class HashedBlock : TestCase() {
|
class HashedBlockTest : TestCase() {
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun testBlockAligned() {
|
fun testBlockAligned() {
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.kunzisoft.keepass.tests.utils
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
|
class UnsignedIntTest: TestCase() {
|
||||||
|
|
||||||
|
fun testUInt() {
|
||||||
|
val standardInt = UnsignedInt(15).toInt()
|
||||||
|
assertEquals(15, standardInt)
|
||||||
|
val unsignedInt = UnsignedInt(-1).toLong()
|
||||||
|
assertEquals(4294967295L, unsignedInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testMaxValue() {
|
||||||
|
val maxValue = UnsignedInt.MAX_VALUE.toLong()
|
||||||
|
assertEquals(4294967295L, maxValue)
|
||||||
|
val longValue = UnsignedInt.fromLong(4294967295L).toLong()
|
||||||
|
assertEquals(longValue, maxValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testLong() {
|
||||||
|
val longValue = UnsignedInt.fromLong(50L).toInt()
|
||||||
|
assertEquals(50, longValue)
|
||||||
|
val uIntLongValue = UnsignedInt.fromLong(4294967290).toLong()
|
||||||
|
assertEquals(4294967290, uIntLongValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests
|
package com.kunzisoft.keepass.tests.utils
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
@@ -28,7 +28,7 @@ import org.junit.Assert.assertArrayEquals
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class StringDatabaseKDBUtilsTest : TestCase() {
|
class ValuesTest : TestCase() {
|
||||||
|
|
||||||
fun testReadWriteLongZero() {
|
fun testReadWriteLongZero() {
|
||||||
testReadWriteLong(0.toByte())
|
testReadWriteLong(0.toByte())
|
||||||
@@ -82,7 +82,6 @@ class StringDatabaseKDBUtilsTest : TestCase() {
|
|||||||
val dest = uIntTo4Bytes(one)
|
val dest = uIntTo4Bytes(one)
|
||||||
|
|
||||||
assertArrayEquals(orig, dest)
|
assertArrayEquals(orig, dest)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
|
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
|
||||||
@@ -186,7 +185,7 @@ class StringDatabaseKDBUtilsTest : TestCase() {
|
|||||||
|
|
||||||
val bos = ByteArrayOutputStream()
|
val bos = ByteArrayOutputStream()
|
||||||
val leos = LittleEndianDataOutputStream(bos)
|
val leos = LittleEndianDataOutputStream(bos)
|
||||||
leos.writeLong(UnsignedLong.ULONG_MAX_VALUE)
|
leos.writeLong(UnsignedLong.MAX_VALUE)
|
||||||
leos.close()
|
leos.close()
|
||||||
|
|
||||||
val uLongMax = bos.toByteArray()
|
val uLongMax = bos.toByteArray()
|
||||||
@@ -30,7 +30,7 @@ object NativeLib {
|
|||||||
fun init(): Boolean {
|
fun init(): Boolean {
|
||||||
if (!isLoaded) {
|
if (!isLoaded) {
|
||||||
try {
|
try {
|
||||||
System.loadLibrary("final-key") // TODO Rename
|
System.loadLibrary("final-key")
|
||||||
System.loadLibrary("argon2")
|
System.loadLibrary("argon2")
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -17,21 +17,20 @@
|
|||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
package com.kunzisoft.keepass.crypto.finalkey
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory;
|
import com.kunzisoft.keepass.crypto.CipherFactory.deviceBlacklisted
|
||||||
|
|
||||||
public class AESFactory {
|
object AESKeyTransformerFactory : KeyTransformer() {
|
||||||
|
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long): ByteArray? {
|
||||||
// TODO Encaspulate
|
|
||||||
public static KeyTransformer createFinalKey() {
|
|
||||||
// Prefer the native final key implementation
|
// Prefer the native final key implementation
|
||||||
if ( !CipherFactory.INSTANCE.deviceBlacklisted()
|
val keyTransformer = if (!deviceBlacklisted()
|
||||||
&& NativeAESKeyTransformer.available()) {
|
&& NativeAESKeyTransformer.available()) {
|
||||||
return new NativeAESKeyTransformer();
|
NativeAESKeyTransformer()
|
||||||
} else {
|
} else {
|
||||||
// Fall back on the android crypto implementation
|
// Fall back on the android crypto implementation
|
||||||
return new AndroidAESKeyTransformer();
|
AndroidAESKeyTransformer()
|
||||||
}
|
}
|
||||||
|
return keyTransformer.transformMasterKey(seed, key, rounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePassDX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -17,10 +17,11 @@
|
|||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
package com.kunzisoft.keepass.crypto.finalkey
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException
|
||||||
|
|
||||||
public abstract class KeyTransformer {
|
abstract class KeyTransformer {
|
||||||
public abstract byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException;
|
@Throws(IOException::class)
|
||||||
|
abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long): ByteArray?
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.crypto.CryptoUtil
|
import com.kunzisoft.keepass.crypto.CryptoUtil
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.AESFactory
|
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 com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -63,7 +63,7 @@ class AesKdf internal constructor() : KdfEngine() {
|
|||||||
seed = CryptoUtil.hashSha256(seed)
|
seed = CryptoUtil.hashSha256(seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
return AESFactory.createFinalKey().transformMasterKey(seed, currentMasterKey, rounds)
|
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun randomize(p: KdfParameters) {
|
override fun randomize(p: KdfParameters) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
package com.kunzisoft.keepass.database.element.database
|
package com.kunzisoft.keepass.database.element.database
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.AESFactory
|
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||||
@@ -149,7 +149,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
|
|
||||||
// Encrypt the master key a few times to make brute-force key-search harder
|
// Encrypt the master key a few times to make brute-force key-search harder
|
||||||
dos.write(masterSeed)
|
dos.write(masterSeed)
|
||||||
dos.write(AESFactory.createFinalKey().transformMasterKey(masterSeed2, masterKey, numRounds))
|
dos.write(AESKeyTransformerFactory.transformMasterKey(masterSeed2, masterKey, numRounds) ?: ByteArray(0))
|
||||||
|
|
||||||
finalKey = messageDigest.digest()
|
finalKey = messageDigest.digest()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
|
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
|
||||||
val blockKey = HmacBlockStream.getHmacKey64(key, UnsignedLong.ULONG_MAX_VALUE)
|
val blockKey = HmacBlockStream.getHmacKey64(key, UnsignedLong.MAX_VALUE)
|
||||||
|
|
||||||
val hmac: Mac
|
val hmac: Mac
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
|||||||
val hmac: Mac
|
val hmac: Mac
|
||||||
try {
|
try {
|
||||||
hmac = Mac.getInstance("HmacSHA256")
|
hmac = Mac.getInstance("HmacSHA256")
|
||||||
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, UnsignedLong.ULONG_MAX_VALUE), "HmacSHA256")
|
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, UnsignedLong.MAX_VALUE), "HmacSHA256")
|
||||||
hmac.init(signingKey)
|
hmac.init(signingKey)
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
throw DatabaseOutputException(e)
|
throw DatabaseOutputException(e)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class UnsignedInt(private var unsignedValue: Int) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val INT_TO_LONG_MASK: Long = 0xffffffffL
|
private const val INT_TO_LONG_MASK: Long = 0xffffffffL
|
||||||
private const val UINT_MAX_VALUE: Long = 4294967296L // 2^32
|
private const val UINT_MAX_VALUE: Long = 4294967295L // 2^32
|
||||||
|
|
||||||
val MAX_VALUE = UnsignedInt(UINT_MAX_VALUE.toInt())
|
val MAX_VALUE = UnsignedInt(UINT_MAX_VALUE.toInt())
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,6 @@ class UnsignedLong(private var unsignedValue: Long) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ULONG_MAX_VALUE: Long = -1
|
const val MAX_VALUE: Long = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user