Use a rich object to represent a message digest.

Closes #2
diff --git a/lib/crypto.dart b/lib/crypto.dart
index 63b54e2..7648c2d 100644
--- a/lib/crypto.dart
+++ b/lib/crypto.dart
@@ -8,6 +8,7 @@
 export 'src/base64/decoder.dart';
 export 'src/base64/encoder.dart';
 export 'src/crypto_utils.dart';
+export 'src/digest.dart';
 export 'src/hash.dart';
 export 'src/hmac.dart';
 export 'src/md5.dart';
diff --git a/lib/src/digest.dart b/lib/src/digest.dart
new file mode 100644
index 0000000..02af614
--- /dev/null
+++ b/lib/src/digest.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library crypto.digest;
+
+import 'dart:typed_data';
+
+import 'crypto_utils.dart';
+
+/// A message digest as computed by a [Hash] or [HMAC] function.
+class Digest {
+  /// The message digest as an array of bytes.
+  final List<int> bytes;
+
+  Digest(List<int> bytes)
+      : bytes = new Uint8List.fromList(bytes);
+
+  /// Returns whether this is equal to another digest.
+  ///
+  /// This should be used instead of manual comparisons to avoid leaking
+  /// information via timing.
+  bool operator ==(Object other) {
+    if (other is! Digest) return false;
+    if (other.bytes.length != bytes.length) return false;
+
+    var result = 0;
+    for (var i = 0; i < bytes.length; i++) {
+      result |= bytes[i] ^ other.bytes[i];
+    }
+    return result == 0;
+  }
+
+  /// The message digest as a string of hexadecimal digits.
+  String toString() => CryptoUtils.bytesToHex(bytes);
+}