Refactor HMAC methods

This commit is contained in:
J-Jamet
2021-03-24 19:15:45 +01:00
parent aee0500b38
commit 50d3282a65
10 changed files with 97 additions and 126 deletions

View File

@@ -17,17 +17,35 @@
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
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 {

View File

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

View File

@@ -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<UUID, UUID, GroupKDBX, EntryKDBX> {
@@ -362,13 +365,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
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<UUID, UUID, GroupKDBX, EntryKDBX> {
}
}
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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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