From 50d3282a65f32b46592bcdb5ff86634f0dfc5110 Mon Sep 17 00:00:00 2001 From: J-Jamet Date: Wed, 24 Mar 2021 19:15:45 +0100 Subject: [PATCH] Refactor HMAC methods --- .../crypto/HmacBlock.kt} | 22 +++++++- .../keepass/database/crypto/kdf/AesKdf.kt | 6 +-- .../database/element/database/DatabaseKDBX.kt | 52 +++++++++++++++++-- .../database/file/DatabaseHeaderKDBX.kt | 19 ++----- .../database/file/input/DatabaseInputKDBX.kt | 8 ++- .../file/output/DatabaseHeaderOutputKDBX.kt | 17 ++---- .../keepass/stream/HmacBlockInputStream.kt | 21 ++------ .../keepass/stream/HmacBlockOutputStream.kt | 23 ++------ .../encrypt/{CryptoUtil.kt => HashManager.kt} | 49 +---------------- .../encrypt/stream/StreamCipherFactory.kt | 6 +-- 10 files changed, 97 insertions(+), 126 deletions(-) rename app/src/main/java/com/kunzisoft/keepass/{stream/HmacBlockStream.kt => database/crypto/HmacBlock.kt} (72%) rename encrypt/src/main/java/com/kunzisoft/encrypt/{CryptoUtil.kt => HashManager.kt} (58%) diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockStream.kt b/app/src/main/java/com/kunzisoft/keepass/database/crypto/HmacBlock.kt similarity index 72% rename from app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockStream.kt rename to app/src/main/java/com/kunzisoft/keepass/database/crypto/HmacBlock.kt index 2e6ceb33c..097dd6a27 100644 --- a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockStream.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/crypto/HmacBlock.kt @@ -17,17 +17,35 @@ * along with KeePassDX. If not, see . * */ -package com.kunzisoft.keepass.stream +package com.kunzisoft.keepass.database.crypto import com.kunzisoft.encrypt.UnsignedLong import com.kunzisoft.encrypt.stream.NullOutputStream import com.kunzisoft.encrypt.stream.write8BytesLong import java.io.IOException import java.security.DigestOutputStream +import java.security.InvalidKeyException import java.security.MessageDigest import java.security.NoSuchAlgorithmException +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +object HmacBlock { + + fun getHmacSha256(blockKey: ByteArray): Mac { + val hmac: Mac + try { + hmac = Mac.getInstance("HmacSHA256") + val signingKey = SecretKeySpec(blockKey, "HmacSHA256") + hmac.init(signingKey) + } catch (e: NoSuchAlgorithmException) { + throw IOException("No HmacAlogirthm") + } catch (e: InvalidKeyException) { + throw IOException("Invalid Hmac Key") + } + return hmac + } -object HmacBlockStream { fun getHmacKey64(key: ByteArray, blockIndex: UnsignedLong): ByteArray { val hash: MessageDigest try { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/crypto/kdf/AesKdf.kt b/app/src/main/java/com/kunzisoft/keepass/database/crypto/kdf/AesKdf.kt index 4c410ed95..5fad7a99b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/crypto/kdf/AesKdf.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/crypto/kdf/AesKdf.kt @@ -19,7 +19,7 @@ */ package com.kunzisoft.keepass.database.crypto.kdf -import com.kunzisoft.encrypt.CryptoUtil +import com.kunzisoft.encrypt.HashManager import com.kunzisoft.encrypt.UnsignedLong import com.kunzisoft.encrypt.aes.AESKeyTransformerFactory import com.kunzisoft.encrypt.stream.bytes16ToUuid @@ -48,12 +48,12 @@ class AesKdf : KdfEngine() { var seed = kdfParameters.getByteArray(PARAM_SEED) if (seed != null && seed.size != 32) { - seed = CryptoUtil.hashSha256(seed) + seed = HashManager.hashSha256(seed) } var currentMasterKey = masterKey if (currentMasterKey.size != 32) { - currentMasterKey = CryptoUtil.hashSha256(currentMasterKey) + currentMasterKey = HashManager.hashSha256(currentMasterKey) } val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)?.toKotlinLong() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt index 5339171f4..5c33430e2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt @@ -22,8 +22,9 @@ package com.kunzisoft.keepass.database.element.database import android.content.res.Resources import android.util.Base64 import android.util.Log -import com.kunzisoft.encrypt.CryptoUtil +import com.kunzisoft.encrypt.HashManager import com.kunzisoft.encrypt.UnsignedInt +import com.kunzisoft.encrypt.stream.longTo8Bytes import com.kunzisoft.keepass.R import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.crypto.AesEngine @@ -56,9 +57,11 @@ import java.io.InputStream import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.util.* +import javax.crypto.Mac import javax.xml.XMLConstants import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.ParserConfigurationException +import kotlin.math.min class DatabaseKDBX : DatabaseVersioned { @@ -362,13 +365,13 @@ class DatabaseKDBX : DatabaseVersioned { var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters) if (transformedMasterKey.size != 32) { - transformedMasterKey = CryptoUtil.hashSha256(transformedMasterKey) + transformedMasterKey = HashManager.hashSha256(transformedMasterKey) } val cmpKey = ByteArray(65) System.arraycopy(masterSeed, 0, cmpKey, 0, 32) System.arraycopy(transformedMasterKey, 0, cmpKey, 32, 32) - finalKey = CryptoUtil.resizeKey(cmpKey, 0, 64, dataEngine.keyLength()) + finalKey = resizeKey(cmpKey, dataEngine.keyLength()) val messageDigest: MessageDigest try { @@ -383,6 +386,49 @@ class DatabaseKDBX : DatabaseVersioned { } } + private fun resizeKey(inBytes: ByteArray, cbOut: Int): ByteArray { + if (cbOut == 0) return ByteArray(0) + + val hash: ByteArray = if (cbOut <= 32) { + HashManager.hashSha256(inBytes, 0, 64) + } else { + HashManager.hashSha512(inBytes, 0, 64) + } + + if (cbOut == hash.size) { + return hash + } + + val ret = ByteArray(cbOut) + if (cbOut < hash.size) { + System.arraycopy(hash, 0, ret, 0, cbOut) + } else { + var pos = 0 + var r: Long = 0 + while (pos < cbOut) { + val hmac: Mac + try { + hmac = Mac.getInstance("HmacSHA256") + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } + + val pbR = longTo8Bytes(r) + val part = hmac.doFinal(pbR) + + val copy = min(cbOut - pos, part.size) + System.arraycopy(part, 0, ret, pos, copy) + pos += copy + r++ + + Arrays.fill(part, 0.toByte()) + } + } + + Arrays.fill(hash, 0.toByte()) + return ret + } + override fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? { try { val documentBuilderFactory = DocumentBuilderFactory.newInstance() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDBX.kt index 6963360c3..11992ee87 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDBX.kt @@ -24,6 +24,7 @@ import com.kunzisoft.encrypt.UnsignedInt import com.kunzisoft.encrypt.UnsignedLong import com.kunzisoft.encrypt.stream.* import com.kunzisoft.keepass.database.action.node.NodeHandler +import com.kunzisoft.keepass.database.crypto.HmacBlock import com.kunzisoft.keepass.database.crypto.VariantDictionary import com.kunzisoft.keepass.database.crypto.kdf.AesKdf import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory @@ -35,16 +36,13 @@ import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.exception.VersionDatabaseException import com.kunzisoft.keepass.stream.CopyInputStream -import com.kunzisoft.keepass.stream.HmacBlockStream import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InputStream import java.security.DigestInputStream -import java.security.InvalidKeyException import java.security.MessageDigest import java.security.NoSuchAlgorithmException import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader() { var innerRandomStreamKey: ByteArray = ByteArray(32) @@ -327,19 +325,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader( @Throws(IOException::class) fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray { - val blockKey = HmacBlockStream.getHmacKey64(key, UnsignedLong.MAX) - - val hmac: Mac - try { - hmac = Mac.getInstance("HmacSHA256") - val signingKey = SecretKeySpec(blockKey, "HmacSHA256") - hmac.init(signingKey) - } catch (e: NoSuchAlgorithmException) { - throw IOException("No HmacAlogirthm") - } catch (e: InvalidKeyException) { - throw IOException("Invalid Hmac Key") - } - + val blockKey = HmacBlock.getHmacKey64(key, UnsignedLong.MAX) + val hmac: Mac = HmacBlock.getHmacSha256(blockKey) return hmac.doFinal(header) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt index df596387c..ab90f6ec2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt @@ -26,6 +26,7 @@ import com.kunzisoft.encrypt.UnsignedLong import com.kunzisoft.encrypt.stream.* import com.kunzisoft.keepass.database.crypto.CipherEngine import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm +import com.kunzisoft.keepass.database.crypto.HmacBlock import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DeletedObject @@ -60,6 +61,7 @@ import java.util.* import java.util.zip.GZIPInputStream import javax.crypto.Cipher import javax.crypto.CipherInputStream +import javax.crypto.Mac import kotlin.math.min class DatabaseInputKDBX(cacheDirectory: File, @@ -181,7 +183,11 @@ class DatabaseInputKDBX(cacheDirectory: File, } val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException() - val headerHmac = DatabaseHeaderKDBX.computeHeaderHmac(pbHeader, hmacKey) + + val blockKey = HmacBlock.getHmacKey64(hmacKey, UnsignedLong.MAX) + val hmac: Mac = HmacBlock.getHmacSha256(blockKey) + val headerHmac = hmac.doFinal(pbHeader) + val storedHmac = databaseInputStream.readBytesLength(32) if (storedHmac.size != 32) { throw InvalidCredentialsDatabaseException() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt index de99b84a3..9cee74a25 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt @@ -22,25 +22,23 @@ package com.kunzisoft.keepass.database.file.output import com.kunzisoft.encrypt.UnsignedInt import com.kunzisoft.encrypt.UnsignedLong import com.kunzisoft.encrypt.stream.* +import com.kunzisoft.keepass.database.crypto.HmacBlock import com.kunzisoft.keepass.database.crypto.VariantDictionary import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX -import com.kunzisoft.keepass.stream.HmacBlockStream import com.kunzisoft.keepass.stream.MacOutputStream import java.io.ByteArrayOutputStream import java.io.IOException import java.io.OutputStream import java.security.DigestOutputStream -import java.security.InvalidKeyException import java.security.MessageDigest import java.security.NoSuchAlgorithmException import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec -class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class) +class DatabaseHeaderOutputKDBX @Throws(IOException::class) constructor(private val databaseKDBX: DatabaseKDBX, private val header: DatabaseHeaderKDBX, outputStream: OutputStream) { @@ -68,16 +66,7 @@ constructor(private val databaseKDBX: DatabaseKDBX, } val hmacKey = databaseKDBX.hmacKey ?: throw DatabaseOutputException("HmacKey is not defined") - val hmac: Mac - try { - hmac = Mac.getInstance("HmacSHA256") - val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, UnsignedLong.MAX), "HmacSHA256") - hmac.init(signingKey) - } catch (e: NoSuchAlgorithmException) { - throw DatabaseOutputException(e) - } catch (e: InvalidKeyException) { - throw DatabaseOutputException(e) - } + val hmac: Mac = HmacBlock.getHmacSha256(HmacBlock.getHmacKey64(hmacKey, UnsignedLong.MAX)) dos = DigestOutputStream(outputStream, md) mos = MacOutputStream(dos, hmac) diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.kt b/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.kt index 29b12bd7b..3bac4c080 100644 --- a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.kt +++ b/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.kt @@ -23,13 +23,11 @@ import com.kunzisoft.encrypt.UnsignedLong import com.kunzisoft.encrypt.stream.bytes4ToUInt import com.kunzisoft.encrypt.stream.readBytesLength import com.kunzisoft.encrypt.stream.uLongTo8Bytes +import com.kunzisoft.keepass.database.crypto.HmacBlock import java.io.IOException import java.io.InputStream -import java.security.InvalidKeyException -import java.security.NoSuchAlgorithmException import java.util.* import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec class HmacBlockInputStream(private val baseStream: InputStream, private val verify: Boolean, private val key: ByteArray) : InputStream() { @@ -104,19 +102,8 @@ class HmacBlockInputStream(private val baseStream: InputStream, private val veri buffer = baseStream.readBytesLength(blockSize.toKotlinInt()) if (verify) { - val cmpHmac: ByteArray - val blockKey = HmacBlockStream.getHmacKey64(key, blockIndex) - val hmac: Mac - try { - hmac = Mac.getInstance("HmacSHA256") - val signingKey = SecretKeySpec(blockKey, "HmacSHA256") - hmac.init(signingKey) - } catch (e: NoSuchAlgorithmException) { - throw IOException("Invalid Hmac") - } catch (e: InvalidKeyException) { - throw IOException("Invalid Hmac") - } - + val blockKey = HmacBlock.getHmacKey64(key, blockIndex) + val hmac: Mac = HmacBlock.getHmacSha256(blockKey) hmac.update(pbBlockIndex) hmac.update(pbBlockSize) @@ -124,7 +111,7 @@ class HmacBlockInputStream(private val baseStream: InputStream, private val veri hmac.update(buffer) } - cmpHmac = hmac.doFinal() + val cmpHmac: ByteArray = hmac.doFinal() Arrays.fill(blockKey, 0.toByte()) if (!cmpHmac.contentEquals(storedHmac)) { diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockOutputStream.kt b/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockOutputStream.kt index 7b6ac063b..f9c6c0e23 100644 --- a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockOutputStream.kt +++ b/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockOutputStream.kt @@ -23,12 +23,10 @@ import com.kunzisoft.encrypt.UnsignedInt import com.kunzisoft.encrypt.UnsignedLong import com.kunzisoft.encrypt.stream.uIntTo4Bytes import com.kunzisoft.encrypt.stream.uLongTo8Bytes +import com.kunzisoft.keepass.database.crypto.HmacBlock import java.io.IOException import java.io.OutputStream -import java.security.InvalidKeyException -import java.security.NoSuchAlgorithmException import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec class HmacBlockOutputStream(private val baseStream: OutputStream, private val key: ByteArray) @@ -90,20 +88,8 @@ class HmacBlockOutputStream(private val baseStream: OutputStream, val bufBlockIndex = uLongTo8Bytes(blockIndex) val blockSizeBuf = uIntTo4Bytes(UnsignedInt(bufferPos)) - val blockHmac: ByteArray - val blockKey = HmacBlockStream.getHmacKey64(key, blockIndex) - - val hmac: Mac - try { - hmac = Mac.getInstance("HmacSHA256") - val signingKey = SecretKeySpec(blockKey, "HmacSHA256") - hmac.init(signingKey) - } catch (e: NoSuchAlgorithmException) { - throw IOException("Invalid Hmac") - } catch (e: InvalidKeyException) { - throw IOException("Invalid HMAC") - } - + val blockKey = HmacBlock.getHmacKey64(key, blockIndex) + val hmac: Mac = HmacBlock.getHmacSha256(blockKey) hmac.update(bufBlockIndex) hmac.update(blockSizeBuf) @@ -111,8 +97,7 @@ class HmacBlockOutputStream(private val baseStream: OutputStream, hmac.update(buffer, 0, bufferPos) } - blockHmac = hmac.doFinal() - + val blockHmac: ByteArray = hmac.doFinal() baseStream.write(blockHmac) baseStream.write(blockSizeBuf) diff --git a/encrypt/src/main/java/com/kunzisoft/encrypt/CryptoUtil.kt b/encrypt/src/main/java/com/kunzisoft/encrypt/HashManager.kt similarity index 58% rename from encrypt/src/main/java/com/kunzisoft/encrypt/CryptoUtil.kt rename to encrypt/src/main/java/com/kunzisoft/encrypt/HashManager.kt index b66ad811c..068b51269 100644 --- a/encrypt/src/main/java/com/kunzisoft/encrypt/CryptoUtil.kt +++ b/encrypt/src/main/java/com/kunzisoft/encrypt/HashManager.kt @@ -20,59 +20,12 @@ package com.kunzisoft.encrypt import com.kunzisoft.encrypt.stream.NullOutputStream -import com.kunzisoft.encrypt.stream.longTo8Bytes import java.io.IOException import java.security.DigestOutputStream import java.security.MessageDigest import java.security.NoSuchAlgorithmException -import java.util.* -import javax.crypto.Mac -import kotlin.math.min -object CryptoUtil { - - fun resizeKey(inBytes: ByteArray, inOffset: Int, cbIn: Int, cbOut: Int): ByteArray { - if (cbOut == 0) return ByteArray(0) - - val hash: ByteArray = if (cbOut <= 32) { - hashSha256(inBytes, inOffset, cbIn) - } else { - hashSha512(inBytes, inOffset, cbIn) - } - - if (cbOut == hash.size) { - return hash - } - - val ret = ByteArray(cbOut) - if (cbOut < hash.size) { - System.arraycopy(hash, 0, ret, 0, cbOut) - } else { - var pos = 0 - var r: Long = 0 - while (pos < cbOut) { - val hmac: Mac - try { - hmac = Mac.getInstance("HmacSHA256") - } catch (e: NoSuchAlgorithmException) { - throw RuntimeException(e) - } - - val pbR = longTo8Bytes(r) - val part = hmac.doFinal(pbR) - - val copy = min(cbOut - pos, part.size) - System.arraycopy(part, 0, ret, pos, copy) - pos += copy - r++ - - Arrays.fill(part, 0.toByte()) - } - } - - Arrays.fill(hash, 0.toByte()) - return ret - } +object HashManager { fun hashSha256(data: ByteArray, offset: Int = 0, count: Int = data.size): ByteArray { return hashGen("SHA-256", data, offset, count) diff --git a/encrypt/src/main/java/com/kunzisoft/encrypt/stream/StreamCipherFactory.kt b/encrypt/src/main/java/com/kunzisoft/encrypt/stream/StreamCipherFactory.kt index 69bcb7002..607fed3c7 100644 --- a/encrypt/src/main/java/com/kunzisoft/encrypt/stream/StreamCipherFactory.kt +++ b/encrypt/src/main/java/com/kunzisoft/encrypt/stream/StreamCipherFactory.kt @@ -20,7 +20,7 @@ package com.kunzisoft.encrypt.stream import com.kunzisoft.encrypt.CrsAlgorithm -import com.kunzisoft.encrypt.CryptoUtil +import com.kunzisoft.encrypt.HashManager import org.bouncycastle.crypto.engines.ChaCha7539Engine import org.bouncycastle.crypto.engines.Salsa20Engine import org.bouncycastle.crypto.params.KeyParameter @@ -41,7 +41,7 @@ object StreamCipherFactory { private fun getSalsa20(key: ByteArray): StreamCipher { // Build stream cipher key - val key32 = CryptoUtil.hashSha256(key) + val key32 = HashManager.hashSha256(key) val keyParam = KeyParameter(key32) val ivParam = ParametersWithIV(keyParam, SALSA_IV) @@ -54,7 +54,7 @@ object StreamCipherFactory { private fun getChaCha20(key: ByteArray): StreamCipher { // Build stream cipher key - val hash = CryptoUtil.hashSha512(key) + val hash = HashManager.hashSha512(key) val key32 = ByteArray(32) val iv = ByteArray(12)