| # HMAC with Pointy Castle |
| |
| This article describes how to calculate a HMAC using the Pointy Castle |
| package, which is an implementation of cryptographic algorithms for |
| use with the Dart programming language. |
| |
| ## Overview |
| |
| A Hash-based Message Authentication Code (HMAC) is a type of Message |
| Authentication Code (MAC) that is calculate using a digest algorithm, |
| a secret key and the message data. |
| |
| To calculate a HMAC: |
| |
| 1. Instantiate an implementation of `Hmac`, providing it the `Digest` implementation to use. |
| 2. Initialize it with the HMAC key. |
| 2. Provide it the bytes to calculate the HMAC over. |
| |
| This program calculates the HMAC SHA-256: |
| |
| ```dart |
| import 'dart:convert'; |
| import 'dart:typed_data'; |
| |
| import "package:pointycastle/export.dart"; |
| |
| Uint8List hmacSha256(Uint8List hmacKey, Uint8List data) { |
| final hmac = HMac(SHA256Digest(), 64) // for HMAC SHA-256, block length must be 64 |
| ..init(KeyParameter(hmacKey)); |
| |
| return hmac.process(data); |
| } |
| |
| void main(List<String> args) { |
| final key = utf8.encode(args[0]); // first argument is the key |
| final data = utf8.encode(args[1]); // second argument is the data |
| |
| final hmacValue = hmacSha256(key, data); |
| print('HMAC SHA-256: $hmacValue'); |
| } |
| ``` |
| |
| ## Details |
| |
| ### Implementation |
| |
| #### Using the registry |
| |
| If using the registry, invoke the `Mac` factory with the name of the |
| HMAC algorithm. The name of the HMAC algorithm is the name of the |
| digest algorithm followed by "/HMAC" (e.g. "SHA-1/HMAC"). |
| |
| ```dart |
| final hmac = new Mac("SHA-256/HMAC"); |
| ``` |
| |
| #### Without the registry |
| |
| If the registry is not used, invoke the `HMac` constructor, passing it |
| the digest implementation to use and a block length for that digest |
| algorithm. |
| |
| ```dart |
| final hmacSha256 = HMac(SHA256Digest(), 64); // for HMAC SHA-256, block length must be 64 |
| |
| final hmacSha1 = HMac(SHA1Digest(), 64); // for HMAC SHA-1, block length must be 64 |
| final hmacSha512 = HMac(SHA512Digest(), 128); // for HMAC SHA-512, block length must be 128 |
| final hmacMd2 = HMac(MD2Digest(), 16); // for HMAC MD2, block length must be 16 |
| final hmacMd5 = HMac(MD5Digest(), 64); // for HMAC MD5, block length must be 64 |
| ``` |
| |
| **Warning:** the correct block length for the digest algorithm must be |
| used. Using a different value will produce an HMAC that is incorrect. |
| The registry automatically uses the correct values, which it gets from |
| the `_DIGEST_BLOCK_LENGTH` internal static member from the `HMac` class |
| in _lib/macs/hmac.dart_. But without the registry the correct value |
| must be found and explicitly provided. |
| |
| ### Set the key |
| |
| Before processing the data, initialize the `HMac` object with the HMAC key |
| as a key parameter. |
| |
| ```dart |
| Uint8List keyBytes = ... |
| |
| hmac.init(KeyParameter(keyBytes)); |
| ``` |
| |
| ### Providing the data |
| |
| This is similar to calculating a digest. |
| |
| #### Complete data |
| |
| If all the data is available as a single sequence of bytes, pass it to |
| the `process` method to obtain the HMAC. The input data must be a |
| `Uint8List`, and the calculated HMAC is returned in a new `Uint8List`. |
| |
| ```dart |
| final Uint8List data = ... |
| |
| final hmacValue = hmac.process(data); |
| ``` |
| |
| #### Progressive data |
| |
| The data can also be provided as a sequence of individual bytes or |
| fragments of bytes. |
| |
| To provide a single byte, use the `updateByte` method. It takes a single `int`. |
| |
| To provide a fragment of bytes, use the `update` method. This method |
| takes a `Uint8List`, an offset to where the bytes start and the |
| length. Therefore, a sublist of the `Uint8List` can be provided, instead |
| of the entire `Uint8List`. |
| |
| After all the data has been provided, use the `doFinal` method to obtain the |
| HMAC. The `doFinal` method takes two arguments: a `Uint8List` where it will |
| store the HMAC and an offset to where it will start. |
| |
| The destination, after the offset position, must be large enough to |
| hold the HMAC. The number of bytes required depends on the HMAC |
| algorithm being used, and can be found using the `macSize` getter. |
| |
| ```dart |
| final chunk1 = utf8.encode('cellophane'); |
| final chunk2 = utf8.encode('world'); |
| |
| hmac.updateByte(0x48); // 'H' |
| hmac.updateByte(0x65); // 'e' |
| hmac.update(chunk1, 1, 4); |
| hmac.updateByte(0x20); // ' ' |
| hmac.update(chunk2, 0, chunk2.length); |
| hmac.updateByte(0x21); // '!' |
| |
| final hmacValue = Uint8List(hmac.macSize); // create a destination for storing the HMAC |
| |
| hmac.doFinal(hmacValue, 0); // HMAC of "Hello world!" |
| ``` |
| |
| #### Discarding provided data |
| |
| When providing the data progressively, previously provided data can be |
| discarded by invoking the `reset` method. |
| |
| Normally, reset does not need to be explicitly done because it is done |
| automatically by the `process` and `doFinal` methods. This is only |
| required if previously provided data is abandoned. |
| |
| ```dart |
| final part1 = utf8.encode('Hello '); |
| final part2 = utf8.encode('world!'); |
| |
| final result = Uint8List(hmac.macSize); |
| |
| // Without reset |
| |
| hmac.update(part1, 0, part1.length); |
| hmac.update(part2, 0, part2.length); |
| hmac.doFinal(result, 0); // result contains HMAC of "Hello world!" |
| |
| // With reset |
| |
| hmac.update(part1, 0, part1.length); |
| hmac.reset(); // *** reset discards the data from part1 |
| hmac.update(part2, 0, part2.length); |
| hmac.doFinal(result, 0); // result contains HMAC of "world!" |
| ``` |