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 of Uint8List
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 = SHA256Digest(); return d.process(dataToDigest); } void main(List<String> args) { final valuesToDigest = (args.isNotEmpty) ? args : ['Hello world!']; for (final data in valuesToDigest) { print('Data: "$data"'); final hash = sha256Digest(utf8.encode(data) as Uint8List); print('SHA-256: $hash'); print('SHA-256: ${bin2hex(hash)}'); // output in hexadecimal } }
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 = 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”, “Whirlpool” and “SM3”.
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 = 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 = 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.