This article describes how to calculate digests using the Pointy Castle package, which is an implementation of cryptographic algorithms for use with the Dart programming language.
A digest is a value derived from a message. In all/most cases, the digest is a small fixed size, regardless of the size of the message. The digest value is produced by running an algorithm over the message. Pointy Castle has implementations of a number of different cryptographic digest algorithms (cryptographic hash algorithms or cryptographic hash functions). The term "hash" and "digest" are often used interchangably. And in Pointy Castle, the Digest class represents the algorithm rather than the value that is produced.
To calculate a digest value:
-
Instantiate a class that implements the
Digest
abstract class. -
Provide it the bytes to calculate the digest over. Either as a single
Uint8List
, or as fragments ofUint8List
and bytes.
This program calculates the SHA-265 digest of text strings:
import 'dart:convert';
import 'dart:typed_data';
import "package:pointycastle/export.dart";
Uint8List sha256Digest(Uint8List dataToDigest) {
final d = new SHA256Digest();
return d.process(dataToDigest);
}
void main(List<String> args) {
for (final data in args) {
print('Data: "$data"');
final hashValue = sha256Digest(utf8.encode(data));
print('SHA-256: $hashValue');
}
}
Note: these overview examples do not use the registry. For information on how to use the registry, see the following details.
If using the registry, invoke the Digest
factory with the name of
the digest algorithm.
final d = new Digest("SHA-256");
Possible names include: "MD2", "MD4", "MD5", "RIPEMD-128", "RIPEMD-160", "RIPEMD-256", "RIPEMD-320", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512", "Tiger" and "Whirlpool".
Note: these examples store the digest object in "d", since they could be for any digest algorithm. But it is better to give the variable a more meaningful name, such as "sha256".
Some digest implementations should not be instantiated using the
registry, because additional parameters need to be passed to their
constructors. These include: Blake2bDigest
, SHA3Digest
and
SHA512tDigest
.
If the registery is not used, invoke the digest implementation's constructor.
final d = new SHA256Digest(); // SHA-256
All of the available digest classes of are listed as the implementers of the Digest abstract class.
If all the data is available as a single sequence of bytes, pass it to
the process
method to obtain the digest. The input data must be a
single Uint8List
, and the calculated digest is returned in a new
Uint8List
.
final Uint8List dataToDigest = ...
final hash = d.process(dataToDigest);
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
digest. The doFinal
method takes two arguments: a Uint8List
where it will
store the digest and an offset to where it will start.
The destination, after the offset position, must be large enough to
hold the digest. The number of bytes required depends on the digest
algorithm being used, and can be found using the digestSize
getter.
final chunk1 = utf8.encode('cellophane');
final chunk2 = utf8.encode('world');
d.updateByte(0x48); // 'H'
d.updateByte(0x65); // 'e'
d.update(chunk1, 1, 4);
d.updateByte(0x20); // ' '
d.update(chunk2, 0, chunk2.length);
d.updateByte(0x21); // '!'
final hash = Uint8List(d.digestSize); // create a destination for storing the hash
d.doFinal(hash, 0); // hash 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 d = new SHA256Digest();
final hash = Uint8List(d.digestSize);
// Without rest
d.update(part1, 0, part1.length);
d.update(part2, 0, part2.length);
d.doFinal(hash, 0); // hash of "Hello world!"
// With reset
d.update(part1, 0, part1.length);
d.reset();
d.update(part2, 0, part2.length);
d.doFinal(hash, 0); // hash of "world!"
Note: do not use the resetState method that is available in the
SHA-family of implementations. The reset
method will internally
invoke the resetState method, as well as perform other operations to
reset the digester.