blob: 250cb668e70a1ff6796a6bacdaa2fb473e087048 [file] [log] [blame] [view]
# 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!"
```