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.
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:
Hmac
, providing it the Digest
implementation to use.This program calculates the HMAC SHA-256:
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'); }
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”).
final hmac = new Mac("SHA-256/HMAC");
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.
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.
Before processing the data, initialize the HMac
object with the HMAC key as a key parameter.
Uint8List keyBytes = ... hmac.init(KeyParameter(keyBytes));
This is similar to calculating a digest.
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
.
final Uint8List data = ... final hmacValue = hmac.process(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.
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!"
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.
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!"