Refactor key encryption methods and add unit tests

This commit is contained in:
J-Jamet
2020-04-22 20:23:23 +02:00
parent 9663c3cadd
commit 7456e2c8f5
14 changed files with 72 additions and 48 deletions

View File

@@ -29,8 +29,8 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
class FinalKeyTest : TestCase() {
private var mRand: Random? = null
class AESKeyTest : TestCase() {
private lateinit var mRand: Random
@Throws(Exception::class)
override fun setUp() {
@@ -40,29 +40,28 @@ class FinalKeyTest : TestCase() {
}
@Throws(IOException::class)
fun testNativeAndroid() {
fun testAES() {
// Test both an old and an even number to test my flip variable
testNativeFinalKey(5)
testNativeFinalKey(6)
testAESFinalKey(5)
testAESFinalKey(6)
}
@Throws(IOException::class)
private fun testNativeFinalKey(rounds: Int) {
private fun testAESFinalKey(rounds: Long) {
val seed = ByteArray(32)
val key = ByteArray(32)
val nativeKey: ByteArray
val androidKey: ByteArray
val nativeKey: ByteArray?
val androidKey: ByteArray?
mRand!!.nextBytes(seed)
mRand!!.nextBytes(key)
mRand.nextBytes(seed)
mRand.nextBytes(key)
val aKey = AndroidAESKeyTransformer()
androidKey = aKey.transformMasterKey(seed, key, rounds.toLong())
val androidAESKey = AndroidAESKeyTransformer()
androidKey = androidAESKey.transformMasterKey(seed, key, rounds)
val nKey = NativeAESKeyTransformer()
nativeKey = nKey.transformMasterKey(seed, key, rounds.toLong())
val nativeAESKey = NativeAESKeyTransformer()
nativeKey = nativeAESKey.transformMasterKey(seed, key, rounds)
assertArrayEquals("Does not match", androidKey, nativeKey)
}
}

View File

@@ -80,6 +80,4 @@ class AESTest : TestCase() {
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
}
}

View File

@@ -33,7 +33,7 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.stream.HashedBlockInputStream
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
class HashedBlock : TestCase() {
class HashedBlockTest : TestCase() {
@Throws(IOException::class)
fun testBlockAligned() {

View File

@@ -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)
}
}

View File

@@ -17,7 +17,7 @@
* 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.stream.*
@@ -28,7 +28,7 @@ import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.*
class StringDatabaseKDBUtilsTest : TestCase() {
class ValuesTest : TestCase() {
fun testReadWriteLongZero() {
testReadWriteLong(0.toByte())
@@ -82,7 +82,6 @@ class StringDatabaseKDBUtilsTest : TestCase() {
val dest = uIntTo4Bytes(one)
assertArrayEquals(orig, dest)
}
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
@@ -186,7 +185,7 @@ class StringDatabaseKDBUtilsTest : TestCase() {
val bos = ByteArrayOutputStream()
val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(UnsignedLong.ULONG_MAX_VALUE)
leos.writeLong(UnsignedLong.MAX_VALUE)
leos.close()
val uLongMax = bos.toByteArray()

View File

@@ -30,7 +30,7 @@ object NativeLib {
fun init(): Boolean {
if (!isLoaded) {
try {
System.loadLibrary("final-key") // TODO Rename
System.loadLibrary("final-key")
System.loadLibrary("argon2")
} catch (e: UnsatisfiedLinkError) {
return false

View File

@@ -17,21 +17,20 @@
* 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 {
// TODO Encaspulate
public static KeyTransformer createFinalKey() {
object AESKeyTransformerFactory : KeyTransformer() {
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long): ByteArray? {
// Prefer the native final key implementation
if ( !CipherFactory.INSTANCE.deviceBlacklisted()
&& NativeAESKeyTransformer.available() ) {
return new NativeAESKeyTransformer();
val keyTransformer = if (!deviceBlacklisted()
&& NativeAESKeyTransformer.available()) {
NativeAESKeyTransformer()
} else {
// Fall back on the android crypto implementation
return new AndroidAESKeyTransformer();
AndroidAESKeyTransformer()
}
return keyTransformer.transformMasterKey(seed, key, rounds)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -17,10 +17,11 @@
* 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 {
public abstract byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException;
abstract class KeyTransformer {
@Throws(IOException::class)
abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long): ByteArray?
}

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources
import com.kunzisoft.keepass.R
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.utils.UnsignedInt
import java.io.IOException
@@ -63,7 +63,7 @@ class AesKdf internal constructor() : KdfEngine() {
seed = CryptoUtil.hashSha256(seed)
}
return AESFactory.createFinalKey().transformMasterKey(seed, currentMasterKey, rounds)
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
}
override fun randomize(p: KdfParameters) {

View File

@@ -19,7 +19,7 @@
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.KdfFactory
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
dos.write(masterSeed)
dos.write(AESFactory.createFinalKey().transformMasterKey(masterSeed2, masterKey, numRounds))
dos.write(AESKeyTransformerFactory.transformMasterKey(masterSeed2, masterKey, numRounds) ?: ByteArray(0))
finalKey = messageDigest.digest()
}

View File

@@ -324,7 +324,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
@Throws(IOException::class)
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
try {

View File

@@ -66,7 +66,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
val hmac: Mac
try {
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)
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException(e)

View File

@@ -65,7 +65,7 @@ class UnsignedInt(private var unsignedValue: Int) {
companion object {
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())

View File

@@ -44,6 +44,6 @@ class UnsignedLong(private var unsignedValue: Long) {
}
companion object {
const val ULONG_MAX_VALUE: Long = -1
const val MAX_VALUE: Long = -1
}
}