From 317be9c4ea1626234d01c04e4a48212b9271684b Mon Sep 17 00:00:00 2001 From: Luca Tremamunno Date: Tue, 30 Sep 2025 15:06:45 +0200 Subject: [PATCH] Allow arbitrary key sizes for HMAC keys --- .../icure/kryptom/crypto/IosHmacService.kt | 20 ++++++++++++----- .../com/icure/kryptom/crypto/HmacService.kt | 18 +++++++++++---- .../kotlin/com/icure/kryptom/crypto/Keys.kt | 6 ++--- .../icure/kryptom/crypto/HmacServiceTest.kt | 4 ++-- .../com/icure/kryptom/crypto/JsHmacService.kt | 20 ++++++++++++----- .../icure/kryptom/crypto/JvmHmacService.kt | 20 ++++++++++++----- .../kryptom/crypto/OpensslHmacService.kt | 22 ++++++++++++------- .../icure/kryptom/crypto/BCryptHmacService.kt | 20 ++++++++++++----- 8 files changed, 89 insertions(+), 41 deletions(-) diff --git a/lib/src/appleMain/kotlin/com/icure/kryptom/crypto/IosHmacService.kt b/lib/src/appleMain/kotlin/com/icure/kryptom/crypto/IosHmacService.kt index bac56f3..0d31d9e 100644 --- a/lib/src/appleMain/kotlin/com/icure/kryptom/crypto/IosHmacService.kt +++ b/lib/src/appleMain/kotlin/com/icure/kryptom/crypto/IosHmacService.kt @@ -10,9 +10,13 @@ import platform.CoreCrypto.kCCHmacAlgSHA256 import platform.CoreCrypto.kCCHmacAlgSHA512 object IosHmacService : HmacService { - override suspend fun generateKey(algorithm: A, keySize: Int?): HmacKey { - require(keySize == null || keySize >= algorithm.minimumKeySize) { - "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumKeySize} is required" + override suspend fun generateKey( + algorithm: A, + keySize: Int?, + acceptsShortKeySize: Boolean + ): HmacKey { + require(acceptsShortKeySize || keySize == null || keySize >= algorithm.minimumRecommendedKeySize) { + "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumRecommendedKeySize} is required" } return HmacKey(IosStrongRandom.randomBytes(keySize ?: algorithm.recommendedKeySize), algorithm) } @@ -21,9 +25,13 @@ object IosHmacService : HmacService { override suspend fun exportKey(key: HmacKey<*>): ByteArray = key.rawKey.copyOf() - override suspend fun loadKey(algorithm: A, bytes: ByteArray): HmacKey { - require(bytes.size >= algorithm.minimumKeySize) { - "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumKeySize} expected" + override suspend fun loadKey( + algorithm: A, + bytes: ByteArray, + acceptsShortKeys: Boolean + ): HmacKey { + require(acceptsShortKeys || bytes.size >= algorithm.minimumRecommendedKeySize) { + "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumRecommendedKeySize} expected" } return HmacKey(bytes.copyOf(), algorithm) } diff --git a/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/HmacService.kt b/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/HmacService.kt index a2dcb06..19e131e 100644 --- a/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/HmacService.kt +++ b/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/HmacService.kt @@ -6,10 +6,15 @@ interface HmacService { * * @param algorithm the [HmacAlgorithm]. * @param keySize the key size. If null (default behaviour), [HmacAlgorithm.recommendedKeySize] will be used. - * Note: for security reasons, the key size cannot be less than [HmacAlgorithm.minimumKeySize] - * @throws IllegalArgumentException if [keySize] is less than [HmacAlgorithm.minimumKeySize] + * Note: for general usage the key size shouldn't be less than [HmacAlgorithm.minimumRecommendedKeySize], but in + * some applications (e.g. TOTP shorter lengths are acceptable) + * @param acceptsShortKeySize if false (default) key sizes shorter than the minimum recommended key size for the algorithm will be rejected */ - suspend fun generateKey(algorithm: A, keySize: Int? = null): HmacKey + suspend fun generateKey( + algorithm: A, + keySize: Int? = null, + acceptsShortKeySize: Boolean = false + ): HmacKey /** * Exports a key to a byte array. @@ -18,8 +23,13 @@ interface HmacService { /** * Imports a key from a byte array. + * @param acceptsShortKey if false (default) keys shorter than the minimum recommended key size for the algorithm will be rejected */ - suspend fun loadKey(algorithm: A, bytes: ByteArray): HmacKey + suspend fun loadKey( + algorithm: A, + bytes: ByteArray, + acceptsShortKeys: Boolean = false + ): HmacKey /** * Generates a signature for some data using the provided key and algorithm. diff --git a/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/Keys.kt b/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/Keys.kt index b80e210..d9708a9 100644 --- a/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/Keys.kt +++ b/lib/src/commonMain/kotlin/com/icure/kryptom/crypto/Keys.kt @@ -202,7 +202,7 @@ sealed interface HmacAlgorithm { * The minimum key size for this algorithm in bytes, as specified in the [HMAC RFC](https://www.rfc-editor.org/rfc/rfc2104#section-3). * A key which size is under this length decrease the security strength of the function. */ - val minimumKeySize: Int + val minimumRecommendedKeySize: Int /** * The size of the digest produced by this algorithm in bytes. @@ -219,7 +219,7 @@ sealed interface HmacAlgorithm { */ data object HmacSha512 : HmacAlgorithm { override val recommendedKeySize: Int = 128 - override val minimumKeySize: Int = 64 + override val minimumRecommendedKeySize: Int = 64 override val digestSize: Int = 64 override val identifier: String = Identifiers.HMAC_SHA_512 } @@ -229,7 +229,7 @@ sealed interface HmacAlgorithm { */ data object HmacSha256 : HmacAlgorithm { override val recommendedKeySize: Int = 64 - override val minimumKeySize: Int = 32 + override val minimumRecommendedKeySize: Int = 32 override val digestSize: Int = 32 override val identifier: String = Identifiers.HMAC_SHA_256 } diff --git a/lib/src/commonTest/kotlin/com/icure/kryptom/crypto/HmacServiceTest.kt b/lib/src/commonTest/kotlin/com/icure/kryptom/crypto/HmacServiceTest.kt index 28a84c0..10469ce 100644 --- a/lib/src/commonTest/kotlin/com/icure/kryptom/crypto/HmacServiceTest.kt +++ b/lib/src/commonTest/kotlin/com/icure/kryptom/crypto/HmacServiceTest.kt @@ -62,14 +62,14 @@ class HmacServiceTest : StringSpec({ } "$algorithm - can generate a key with a custom size" { - val size = algorithm.minimumKeySize + val size = algorithm.minimumRecommendedKeySize val key = defaultCryptoService.hmac.generateKey(algorithm, size) defaultCryptoService.hmac.exportKey(key).size shouldBe size } "$algorithm - cannot specify a key size less than the minimum key size" { shouldThrow { - defaultCryptoService.hmac.generateKey(algorithm, algorithm.minimumKeySize - 1) + defaultCryptoService.hmac.generateKey(algorithm, algorithm.minimumRecommendedKeySize - 1) } } diff --git a/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/JsHmacService.kt b/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/JsHmacService.kt index 5c13fae..aa71e80 100644 --- a/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/JsHmacService.kt +++ b/lib/src/jsMain/kotlin/com/icure/kryptom/crypto/JsHmacService.kt @@ -21,9 +21,13 @@ object JsHmacService : HmacService { "length" to keySize * 8 ) - override suspend fun generateKey(algorithm: A, keySize: Int?): HmacKey { - require(keySize == null || keySize >= algorithm.minimumKeySize) { - "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumKeySize} is required" + override suspend fun generateKey( + algorithm: A, + keySize: Int?, + acceptsShortKeySize: Boolean + ): HmacKey { + require(acceptsShortKeySize || keySize == null || keySize >= algorithm.minimumRecommendedKeySize) { + "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumRecommendedKeySize} is required" } val requestedKeySize = keySize ?: algorithm.recommendedKeySize val generatedKey = jsCrypto.subtle.generateKey( @@ -44,9 +48,13 @@ object JsHmacService : HmacService { private suspend fun exportRawKey(rawKey: dynamic) = jsCrypto.subtle.exportKey(RAW, rawKey).await() as ArrayBuffer - override suspend fun loadKey(algorithm: A, bytes: ByteArray): HmacKey { - require(bytes.size >= algorithm.minimumKeySize) { - "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumKeySize} expected" + override suspend fun loadKey( + algorithm: A, + bytes: ByteArray, + acceptsShortKeys: Boolean + ): HmacKey { + require(acceptsShortKeys || bytes.size >= algorithm.minimumRecommendedKeySize) { + "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumRecommendedKeySize} expected" } return HmacKey( jsCrypto.subtle.importKey( diff --git a/lib/src/jvmAndAndroidMain/kotlin/com/icure/kryptom/crypto/JvmHmacService.kt b/lib/src/jvmAndAndroidMain/kotlin/com/icure/kryptom/crypto/JvmHmacService.kt index 4997203..aaf0c5d 100644 --- a/lib/src/jvmAndAndroidMain/kotlin/com/icure/kryptom/crypto/JvmHmacService.kt +++ b/lib/src/jvmAndAndroidMain/kotlin/com/icure/kryptom/crypto/JvmHmacService.kt @@ -11,9 +11,13 @@ object JvmHmacService : HmacService { HmacAlgorithm.HmacSha256 -> "HMac-SHA256" } - override suspend fun generateKey(algorithm: A, keySize: Int?): HmacKey { - require(keySize == null || keySize >= algorithm.minimumKeySize) { - "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumKeySize} is required" + override suspend fun generateKey( + algorithm: A, + keySize: Int?, + acceptsShortKeySize: Boolean + ): HmacKey { + require(acceptsShortKeySize || keySize == null || keySize >= algorithm.minimumRecommendedKeySize) { + "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumRecommendedKeySize} is required" } val keyGen: KeyGenerator = KeyGenerator.getInstance(algorithm.name) keyGen.init((keySize ?: algorithm.recommendedKeySize) * 8) @@ -24,9 +28,13 @@ object JvmHmacService : HmacService { return key.key.encoded } - override suspend fun loadKey(algorithm: A, bytes: ByteArray): HmacKey { - require(bytes.size >= algorithm.minimumKeySize) { - "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumKeySize} expected" + override suspend fun loadKey( + algorithm: A, + bytes: ByteArray, + acceptsShortKeys: Boolean + ): HmacKey { + require(acceptsShortKeys || bytes.size >= algorithm.minimumRecommendedKeySize) { + "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumRecommendedKeySize} expected" } return HmacKey(SecretKeySpec(bytes, algorithm.name), algorithm) } diff --git a/lib/src/linuxMain/kotlin/com/icure/kryptom/crypto/OpensslHmacService.kt b/lib/src/linuxMain/kotlin/com/icure/kryptom/crypto/OpensslHmacService.kt index d4e37da..290235d 100644 --- a/lib/src/linuxMain/kotlin/com/icure/kryptom/crypto/OpensslHmacService.kt +++ b/lib/src/linuxMain/kotlin/com/icure/kryptom/crypto/OpensslHmacService.kt @@ -8,9 +8,7 @@ import kotlinx.cinterop.memScoped import kotlinx.cinterop.pin import kotlinx.cinterop.ptr import kotlinx.cinterop.value -import libcrypto.EVP_DigestSignInit import libcrypto.EVP_MAX_MD_SIZE -import libcrypto.EVP_sha256 import libcrypto.EVP_sha512 import libcrypto.HMAC_CTX_free import libcrypto.HMAC_CTX_new @@ -20,9 +18,13 @@ import libcrypto.HMAC_Update @OptIn(ExperimentalForeignApi::class) object OpensslHmacService : HmacService { - override suspend fun generateKey(algorithm: A, keySize: Int?): HmacKey { - require(keySize == null || keySize >= algorithm.minimumKeySize) { - "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumKeySize} is required" + override suspend fun generateKey( + algorithm: A, + keySize: Int?, + acceptsShortKeySize: Boolean + ): HmacKey { + require(acceptsShortKeySize || keySize == null || keySize >= algorithm.minimumRecommendedKeySize) { + "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumRecommendedKeySize} is required" } return HmacKey(OpensslStrongRandom.randomBytes(keySize ?: algorithm.recommendedKeySize), algorithm) } @@ -30,9 +32,13 @@ object OpensslHmacService : HmacService { override suspend fun exportKey(key: HmacKey<*>): ByteArray = key.rawKey - override suspend fun loadKey(algorithm: A, bytes: ByteArray): HmacKey { - require(bytes.size >= algorithm.minimumKeySize) { - "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumKeySize} expected" + override suspend fun loadKey( + algorithm: A, + bytes: ByteArray, + acceptsShortKeys: Boolean + ) : HmacKey { + require(acceptsShortKeys || bytes.size >= algorithm.minimumRecommendedKeySize) { + "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumRecommendedKeySize} expected" } return HmacKey(bytes.copyOf(), algorithm) } diff --git a/lib/src/mingwMain/kotlin/com/icure/kryptom/crypto/BCryptHmacService.kt b/lib/src/mingwMain/kotlin/com/icure/kryptom/crypto/BCryptHmacService.kt index 3e36e28..98e9054 100644 --- a/lib/src/mingwMain/kotlin/com/icure/kryptom/crypto/BCryptHmacService.kt +++ b/lib/src/mingwMain/kotlin/com/icure/kryptom/crypto/BCryptHmacService.kt @@ -23,9 +23,13 @@ object BCryptHmacService : HmacService { // https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.14393.0/shared/bcrypt.h#L884C8-L884C59 private const val BCRYPT_ALG_HANDLE_HMAC_FLAG = 0x08 - override suspend fun generateKey(algorithm: A, keySize: Int?): HmacKey { - require(keySize == null || keySize >= algorithm.minimumKeySize) { - "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumKeySize} is required" + override suspend fun generateKey( + algorithm: A, + keySize: Int?, + acceptsShortKeySize: Boolean + ): HmacKey { + require(acceptsShortKeySize || keySize == null || keySize >= algorithm.minimumRecommendedKeySize) { + "Invalid key size for $algorithm. A minimal length of ${algorithm.minimumRecommendedKeySize} is required" } return HmacKey( BCryptStrongRandom.randomBytes(algorithm.recommendedKeySize), @@ -36,9 +40,13 @@ object BCryptHmacService : HmacService { override suspend fun exportKey(key: HmacKey<*>): ByteArray = key.rawKey.copyOf() - override suspend fun loadKey(algorithm: A, bytes: ByteArray): HmacKey { - require(bytes.size >= algorithm.minimumKeySize) { - "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumKeySize} expected" + override suspend fun loadKey( + algorithm: A, + bytes: ByteArray, + acceptsShortKeys: Boolean + ): HmacKey { + require(acceptsShortKeys || bytes.size >= algorithm.minimumRecommendedKeySize) { + "Invalid key length for algorithm $algorithm: got ${bytes.size} but at least ${algorithm.minimumRecommendedKeySize} expected" } return HmacKey( bytes.copyOf(),