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:
- Instantiate an implementation of
Hmac
, providing it theDigest
implementation to use. - Initialize it with the HMAC key.
- Provide it the bytes to calculate the HMAC over.
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) // HMAC SHA-256: block must be 64 bytes
..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 = 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!"