• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / webcrypto.dart / 25232696112

01 May 2026 08:53PM UTC coverage: 90.337% (-0.1%) from 90.474%
25232696112

push

github

web-flow
ci: trim browser matrix and cache Android AVD (#266)

3730 of 4129 relevant lines covered (90.34%)

1.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.52
/lib/src/webcrypto/webcrypto.hmac.dart
1
// Copyright 2020 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
part of 'webcrypto.dart';
16

17
/// Key for signing/verifying with HMAC.
18
///
19
/// An [HmacSecretKey] instance holds a symmetric secret key and a
20
/// [Hash], which can be used to create and verify HMAC signatures as
21
/// specified in [FIPS PUB 180-4][1].
22
///
23
/// Instances of [HmacSecretKey] can be imported from:
24
///  * Raw bytes using [HmacSecretKey.importRawKey], and,
25
///  * [JWK] format using [HmacSecretKey.importJsonWebKey].
26
///
27
/// A random key can also be generated using [HmacSecretKey.generateKey].
28
///
29
/// **Example**
30
/// ```dart
31
/// import 'package:webcrypto/webcrypto.dart';
32
/// import 'dart:convert';
33
///
34
/// Future<void> main() async {
35
///   // Generate an HMAC secret key using SHA-256 hash algorithm.
36
///   final key = await HmacSecretKey.generateKey(Hash.sha256);
37
///
38
///   // Sign the message.
39
///   final signature = await key.signBytes(utf8.encode('Hello World!'));
40
///
41
///   // Verify the signature.
42
///   final verified = await key.verifyBytes(signature, utf8.encode('Hello World!'));
43
///   assert(verified == true, 'Signature should be valid');
44
///
45
///  // Export the key as a JSON Web Key.
46
///  final jwk = await key.exportJsonWebKey();
47
/// }
48
/// ```
49
///
50
/// [1]: https://doi.org/10.6028/NIST.FIPS.180-4
51
final class HmacSecretKey {
52
  final HmacSecretKeyImpl _impl;
53

54
  HmacSecretKey._(this._impl); // keep the constructor private.
1✔
55

56
  /// Import [HmacSecretKey] from raw [keyData].
57
  ///
58
  /// Creates an [HmacSecretKey] using [keyData] as secret key, and running
59
  /// HMAC with given [hash] algorithm.
60
  ///
61
  /// If given, [length] specifies the length of the key, this must be not be
62
  /// less than number of bits in [keyData] - 7. The [length] only allows
63
  /// cutting bits of the last byte in [keyData]. In practice this is the same
64
  /// as zero'ing the last bits in [keyData].
65
  ///
66
  /// **Example**
67
  /// ```dart
68
  /// import 'dart:convert' show utf8;
69
  /// import 'package:webcrypto/webcrypto.dart';
70
  ///
71
  /// final key = await HmacSecretKey.importRawKey(
72
  ///   base64.decode('WzIxLDg0LDEwMCw5OSwxMCwxMDUsMjIsODAsMTkwLDExNiwyMDMsMjQ5XQ=='),
73
  ///   Hash.sha256,
74
  /// );
75
  /// ```
76
  static Future<HmacSecretKey> importRawKey(
1✔
77
    List<int> keyData,
78
    Hash hash, {
79
    int? length,
80
  }) async {
81
    // These limitations are given in Web Cryptography Spec:
82
    // https://www.w3.org/TR/WebCryptoAPI/#hmac-operations
83
    if (length != null && length > keyData.length * 8) {
×
84
      throw ArgumentError.value(
×
85
        length,
86
        'length',
87
        'must be less than number of bits in keyData',
88
      );
89
    }
90
    if (length != null && length <= (keyData.length - 1) * 8) {
×
91
      throw ArgumentError.value(
×
92
        length,
93
        'length',
94
        'must be greater than number of bits in keyData - 8, you can attain '
95
            'the same effect by removing bytes from keyData',
96
      );
97
    }
98

99
    final impl = await webCryptImpl.hmacSecretKey.importRawKey(
2✔
100
      keyData,
101
      hash._impl,
1✔
102
      length: length,
103
    );
104

105
    return HmacSecretKey._(impl);
1✔
106
  }
107

108
  /// Import [HmacSecretKey] from [JSON Web Key][1].
109
  ///
110
  /// {@macro importJsonWebKey:jwk}
111
  ///
112
  /// JSON Web Keys imported using [HmacSecretKey.importJsonWebKey] must
113
  /// have `"kty": "oct"`, and the [hash] given must match the hash algorithm
114
  /// implied by the `"alg"` property of the imported [jwk].
115
  ///
116
  /// For importing a JWK with:
117
  ///  * `"alg": "HS1"` use [Hash.sha1] (**SHA-1 is weak**),
118
  ///  * `"alg": "HS256"` use [Hash.sha256],
119
  ///  * `"alg": "HS384"` use [Hash.sha384], and,
120
  ///  * `"alg": "HS512"` use [Hash.sha512].
121
  ///
122
  /// If specified the `"use"` property of the imported [jwk] must be
123
  /// `"use": "sig"`.
124
  ///
125
  /// {@macro importJsonWebKey:throws-FormatException-if-jwk}
126
  ///
127
  /// **Example**
128
  /// ```dart
129
  /// import 'package:webcrypto/webcrypto.dart';
130
  /// import 'dart:convert' show jsonEncode, jsonDecode;
131
  ///
132
  /// // JSON Web Key as a string containing JSON.
133
  /// final jwk = '{"kty": "oct", "alg": "HS256", "k": ...}';
134
  ///
135
  /// // Import secret key from decoded JSON.
136
  /// final key = await HmacSecretKey.importJsonWebKey(
137
  ///   jsonDecode(jwk),
138
  ///   Hash.sha256, // Must match the hash used the JWK key "alg"
139
  /// );
140
  ///
141
  /// // Export the key (print it in same format as it was given).
142
  /// Map<String, dynamic> keyData = await key.exportJsonWebKey();
143
  /// print(jsonEncode(keyData));
144
  /// ```
145
  ///
146
  /// [1]: https://www.rfc-editor.org/rfc/rfc7517
147
  static Future<HmacSecretKey> importJsonWebKey(
1✔
148
    // TODO: Determine if the "alg" property can be omitted, and update documentation accordingly
149
    //       also make tests covering cases where "alg" is omitted.
150
    // TODO: Determine if there is any restrictions on "use" and "key_ops".
151
    Map<String, dynamic> jwk,
152
    // TODO: Discuss if hash parameter is really necessary, it's in the JWK.
153
    //       Presumably webcrypto requires as a sanity check. Notice, that this
154
    //       should be consistent with other JWK imports, where we specify curve
155
    //       or other parameters. Either we read from JWK, or we verify that
156
    //       what is in the JWK matches what is also given.
157
    //       Note. it's not yet clear if JWK always contains key parameters.
158
    Hash hash, {
159
    int? length,
160
  }) async {
161
    /*
162
    TODO: Validate these in the native implememtation
163
    // These limitations are given in Web Cryptography Spec:
164
    // https://www.w3.org/TR/WebCryptoAPI/#hmac-operations
165
    if (length != null && length > keyData.length * 8) {
166
      throw ArgumentError.value(
167
          length, 'length', 'must be less than number of bits in keyData');
168
    }
169
    if (length != null && length <= (keyData.length - 1) * 8) {
170
      throw ArgumentError.value(
171
        length,
172
        'length',
173
        'must be greater than number of bits in keyData - 8, you can attain '
174
            'the same effect by removing bytes from keyData',
175
      );
176
    }*/
177

178
    final impl = await webCryptImpl.hmacSecretKey.importJsonWebKey(
2✔
179
      jwk,
180
      hash._impl,
1✔
181
    );
182

183
    return HmacSecretKey._(impl);
1✔
184
  }
185

186
  /// Generate random [HmacSecretKey].
187
  ///
188
  /// The [length] specifies the length of the secret key in bits. If omitted
189
  /// the random key will use the same number of bits as the underlying hash
190
  /// algorithm given in [hash].
191
  ///
192
  /// **Example**
193
  /// ```dart
194
  /// import 'package:webcrypto/webcrypto.dart';
195
  ///
196
  /// // Generate a new random HMAC secret key.
197
  /// final key = await HmacSecretKey.generate(Hash.sha256);
198
  /// ```
199
  static Future<HmacSecretKey> generateKey(Hash hash, {int? length}) async {
×
200
    if (length != null && length <= 0) {
×
201
      throw ArgumentError.value(length, 'length', 'must be positive');
×
202
    }
203

204
    final impl = await webCryptImpl.hmacSecretKey.generateKey(
×
205
      hash._impl,
×
206
      length: length,
207
    );
208

209
    return HmacSecretKey._(impl);
×
210
  }
211

212
  /// Compute an HMAC signature of given [data].
213
  ///
214
  /// This computes an HMAC signature of the [data] using hash algorithm
215
  /// and secret key material held by this [HmacSecretKey].
216
  ///
217
  /// **Example**
218
  /// ```dart
219
  /// import 'dart:convert' show base64, utf8;
220
  /// import 'package:webcrypto/webcrypto.dart';
221
  ///
222
  /// // Generate an HmacSecretKey.
223
  /// final key = await HmacSecretKey.generateKey(Hash.sha256);
224
  ///
225
  /// String stringToSign = 'example-string-to-signed';
226
  ///
227
  /// // Compute signature.
228
  /// final signature = await key.signBytes(utf8.encode(stringToSign));
229
  ///
230
  /// // Print as base64
231
  /// print(base64.encode(signature));
232
  /// ```
233
  ///
234
  /// {@template HMAC-sign:do-not-validate-using-sign}
235
  /// This method should not be used for **validating** other signatures by
236
  /// generating a new signature and then comparing the two signatures.
237
  /// While this technically works, your application might be vulnerable to
238
  /// timing attacks. To validate signatures use [verifyBytes] or [verifyStream]
239
  /// instead, these methods computes a signature and does a
240
  /// fixed-time comparison.
241
  /// {@endtemplate}
242
  Future<Uint8List> signBytes(List<int> data) async =>
1✔
243
      await _impl.signBytes(data);
2✔
244

245
  /// Compute an HMAC signature of given [data] stream.
246
  ///
247
  /// This computes an HMAC signature of the [data] stream using hash algorithm
248
  /// and secret key material held by this [HmacSecretKey].
249
  ///
250
  /// **Example**
251
  /// ```dart
252
  /// import 'dart:convert' show base64, utf8;
253
  /// import 'package:webcrypto/webcrypto.dart';
254
  ///
255
  /// // Generate an HmacSecretKey.
256
  /// final key = await HmacSecretKey.generateKey(Hash.sha256);
257
  ///
258
  /// String stringToSign = 'example-string-to-signed';
259
  ///
260
  /// // Compute signature.
261
  /// final signature = await key.signStream(Stream.fromIterable([
262
  ///   utf8.encode(stringToSign),
263
  /// ]));
264
  ///
265
  /// // Print as base64
266
  /// print(base64.encode(signature));
267
  /// ```
268
  ///
269
  /// {@macro HMAC-sign:do-not-validate-using-sign}
270
  Future<Uint8List> signStream(Stream<List<int>> data) =>
1✔
271
      _impl.signStream(data);
2✔
272

273
  /// Verify the HMAC [signature] of given [data].
274
  ///
275
  /// This computes an HMAC signature of the [data] in the same manner
276
  /// as [signBytes] and conducts a fixed-time comparison against [signature],
277
  /// returning `true` if the two signatures are equal.
278
  ///
279
  /// {@template HMAC-verify:do-not-validate-using-sign}
280
  /// It is possible to compute a signature for [data] using
281
  /// [signBytes] or [signStream] and then simply compare the two signatures.
282
  /// This is strongly discouraged as it is easy to introduce side-channels
283
  /// opening your application to timing attacks.
284
  /// Use [verifyBytes] or [verifyStream] to verify signatures.
285
  /// {@endtemplate}
286
  ///
287
  /// **Example**
288
  /// ```dart
289
  /// import 'dart:convert' show base64, utf8;
290
  /// import 'package:webcrypto/webcrypto.dart';
291
  ///
292
  /// // Generate an HmacSecretKey.
293
  /// final key = await HmacSecretKey.generateKey(Hash.sha256);
294
  ///
295
  /// String stringToSign = 'example-string-to-signed';
296
  ///
297
  /// // Compute signature.
298
  /// final signature = await key.signBytes(utf8.encode(stringToSign));
299
  ///
300
  /// // Verify signature.
301
  /// final result = await key.verifyBytes(
302
  ///   signature,
303
  ///   utf8.encode(stringToSign),
304
  /// );
305
  /// assert(result == true, 'this signature should be valid');
306
  /// ```
307
  Future<bool> verifyBytes(List<int> signature, List<int> data) =>
1✔
308
      _impl.verifyBytes(signature, data);
2✔
309

310
  /// Verify the HMAC [signature] of given [data] stream.
311
  ///
312
  /// This computes an HMAC signature of the [data] stream in the same manner
313
  /// as [signStream] and conducts a fixed-time comparison against [signature],
314
  /// returning `true` if the two signatures are equal.
315
  ///
316
  /// {@macro HMAC-verify:do-not-validate-using-sign}
317
  ///
318
  /// **Example**
319
  /// ```dart
320
  /// import 'dart:convert' show base64, utf8;
321
  /// import 'package:webcrypto/webcrypto.dart';
322
  ///
323
  /// // Generate an HmacSecretKey.
324
  /// final key = await HmacSecretKey.generateKey(Hash.sha256);
325
  ///
326
  /// String stringToSign = 'example-string-to-signed';
327
  ///
328
  /// // Compute signature.
329
  /// final signature = await key.signBytes(Stream.fromIterable([
330
  ///   utf8.encode(stringToSign),
331
  /// ]));
332
  ///
333
  /// // Verify signature.
334
  /// final result = await key.verifyStream(signature, Stream.fromIterable([
335
  ///   utf8.encode(stringToSign),
336
  /// ]));
337
  /// assert(result == true, 'this signature should be valid');
338
  /// ```
339
  Future<bool> verifyStream(List<int> signature, Stream<List<int>> data) =>
1✔
340
      _impl.verifyStream(signature, data);
2✔
341

342
  /// Export [HmacSecretKey] as raw bytes.
343
  ///
344
  /// This returns raw bytes making up the secret key. This does not encode the
345
  /// [Hash] hash algorithm used.
346
  ///
347
  /// **Example**
348
  /// ```dart
349
  /// import 'package:webcrypto/webcrypto.dart';
350
  ///
351
  /// // Generate a new random HMAC secret key.
352
  /// final key = await HmacSecretKey.generate(Hash.sha256);
353
  ///
354
  /// // Extract the secret key.
355
  /// final secretBytes = await key.exportRawKey();
356
  ///
357
  /// // Print the key as base64
358
  /// print(base64.encode(secretBytes));
359
  ///
360
  /// // If we wanted to we could import the key as follows:
361
  /// // key = await HmacSecretKey.importRawKey(secretBytes, Hash.sha256);
362
  /// ```
363
  Future<Uint8List> exportRawKey() => _impl.exportRawKey();
3✔
364

365
  /// Export [HmacSecretKey] as [JSON Web Key][1].
366
  ///
367
  /// {@macro exportJsonWebKey:returns}
368
  ///
369
  /// **Example**
370
  /// ```dart
371
  /// import 'package:webcrypto/webcrypto.dart';
372
  /// import 'dart:convert' show jsonEncode;
373
  ///
374
  /// // Generate a new random HMAC secret key.
375
  /// final key = await HmacSecretKey.generate(Hash.sha256);
376
  ///
377
  /// // Export the secret key.
378
  /// final jwk = await key.exportJsonWebKey();
379
  ///
380
  /// // The Map returned by `exportJsonWebKey()` can be converted to JSON with
381
  /// // `jsonEncode` from `dart:convert`, this will print something like:
382
  /// // {"kty": "oct", "alg": "HS256", "k": ...}
383
  /// print(jsonEncode(jwk));
384
  /// ```
385
  ///
386
  /// [1]: https://www.rfc-editor.org/rfc/rfc7517
387
  Future<Map<String, dynamic>> exportJsonWebKey() => _impl.exportJsonWebKey();
3✔
388
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc