diff --git a/CHANGELOG.md b/CHANGELOG.md index d80865a5..d49fadaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.6.2-wip * Fixed JS interop to enable WebAssembly. * Fixed native RSA public-key JWK imports to reject private key material. +* Fixed native HMAC key generation to use the hash block size by default. # 0.6.1 * Added Dart native build hooks and native asset lookup for the bundled diff --git a/lib/src/impl_ffi/impl_ffi.digest.dart b/lib/src/impl_ffi/impl_ffi.digest.dart index 9d9cd288..84846a3b 100644 --- a/lib/src/impl_ffi/impl_ffi.digest.dart +++ b/lib/src/impl_ffi/impl_ffi.digest.dart @@ -72,6 +72,11 @@ abstract class _HashImpl implements HashImpl { /// https://www.iana.org/assignments/jose/jose.xhtml String get hmacJwkAlg; + /// Recommended HMAC key length in bits. + /// + /// Web Crypto defines this as the block size of the hash function. + int get hmacBlockSizeInBits; + /// Algorithm (`alg` for JWK) when this hash algorithm is used in RSA-OAEP. /// /// For SHA-1, it returns 'RSA-OAEP'. @@ -112,6 +117,9 @@ final class _Sha1 extends _HashImpl { @override String get hmacJwkAlg => 'HS1'; + @override + int get hmacBlockSizeInBits => 512; + @override String get rsaOaepJwkAlg => 'RSA-OAEP'; @@ -131,6 +139,9 @@ final class _Sha256 extends _HashImpl { @override String get hmacJwkAlg => 'HS256'; + @override + int get hmacBlockSizeInBits => 512; + @override String get rsaOaepJwkAlg => 'RSA-OAEP-256'; @@ -150,6 +161,9 @@ final class _Sha384 extends _HashImpl { @override String get hmacJwkAlg => 'HS384'; + @override + int get hmacBlockSizeInBits => 1024; + @override String get rsaOaepJwkAlg => 'RSA-OAEP-384'; @@ -169,6 +183,9 @@ final class _Sha512 extends _HashImpl { @override String get hmacJwkAlg => 'HS512'; + @override + int get hmacBlockSizeInBits => 1024; + @override String get rsaOaepJwkAlg => 'RSA-OAEP-512'; diff --git a/lib/src/impl_ffi/impl_ffi.hmac.dart b/lib/src/impl_ffi/impl_ffi.hmac.dart index 71780f08..49ef4a96 100644 --- a/lib/src/impl_ffi/impl_ffi.hmac.dart +++ b/lib/src/impl_ffi/impl_ffi.hmac.dart @@ -74,7 +74,7 @@ Future hmacSecretKey_generateKey( int? length, }) async { final h = _HashImpl.fromHash(hash); - length ??= ssl.EVP_MD_size(h._md) * 8; + length ??= h.hmacBlockSizeInBits; final keyData = Uint8List((length / 8).ceil()); fillRandomBytes(keyData); @@ -103,7 +103,7 @@ final class _StaticHmacSecretKeyImpl implements StaticHmacSecretKeyImpl { } @override - Future generateKey(HashImpl hash, {int? length = 32}) { + Future generateKey(HashImpl hash, {int? length}) { return hmacSecretKey_generateKey(hash, length: length); } } diff --git a/lib/src/impl_js/impl_js.hmac.dart b/lib/src/impl_js/impl_js.hmac.dart index 0525f0e8..b707162c 100644 --- a/lib/src/impl_js/impl_js.hmac.dart +++ b/lib/src/impl_js/impl_js.hmac.dart @@ -102,7 +102,7 @@ final class _StaticHmacSecretKeyImpl implements StaticHmacSecretKeyImpl { } @override - Future generateKey(HashImpl hash, {int? length = 32}) { + Future generateKey(HashImpl hash, {int? length}) { return hmacSecretKey_generateKey(hash, length: length); } } diff --git a/lib/src/impl_stub/impl_stub.hmac.dart b/lib/src/impl_stub/impl_stub.hmac.dart index e4eef5e2..9d1b7f66 100644 --- a/lib/src/impl_stub/impl_stub.hmac.dart +++ b/lib/src/impl_stub/impl_stub.hmac.dart @@ -37,7 +37,7 @@ final class _StaticHmacSecretKeyImpl implements StaticHmacSecretKeyImpl { } @override - Future generateKey(HashImpl hash, {int? length = 32}) { + Future generateKey(HashImpl hash, {int? length}) { throw UnimplementedError('Not implemented'); } } diff --git a/lib/src/webcrypto/webcrypto.hmac.dart b/lib/src/webcrypto/webcrypto.hmac.dart index de927ae0..33bba38e 100644 --- a/lib/src/webcrypto/webcrypto.hmac.dart +++ b/lib/src/webcrypto/webcrypto.hmac.dart @@ -181,8 +181,9 @@ final class HmacSecretKey { /// Generate random [HmacSecretKey]. /// /// The [length] specifies the length of the secret key in bits. If omitted - /// the random key will use the same number of bits as the underlying hash - /// algorithm given in [hash]. + /// the random key will use the block size of the underlying hash algorithm + /// given in [hash] (512 bits for SHA-1 and SHA-256, and 1024 bits for + /// SHA-384 and SHA-512). /// /// **Example** /// ```dart diff --git a/test/hmac_default_key_length_test.dart b/test/hmac_default_key_length_test.dart new file mode 100644 index 00000000..9837411d --- /dev/null +++ b/test/hmac_default_key_length_test.dart @@ -0,0 +1,39 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:test/test.dart'; +import 'package:webcrypto/webcrypto.dart'; + +void main() { + group('HMAC default key length', () { + for (final testCase in [ + (name: 'SHA-1', hash: Hash.sha1, expectedBits: 512), + (name: 'SHA-256', hash: Hash.sha256, expectedBits: 512), + (name: 'SHA-384', hash: Hash.sha384, expectedBits: 1024), + (name: 'SHA-512', hash: Hash.sha512, expectedBits: 1024), + ]) { + test(testCase.name, () async { + final key = await HmacSecretKey.generateKey(testCase.hash); + + expect(await key.exportRawKey(), hasLength(testCase.expectedBits ~/ 8)); + }); + } + + test('explicit length takes precedence', () async { + final key = await HmacSecretKey.generateKey(Hash.sha512, length: 256); + + expect(await key.exportRawKey(), hasLength(32)); + }); + }); +}